From c093bc8aa80bd3bfed8da9a115c693b22ca24cd5 Mon Sep 17 00:00:00 2001 From: Andy Weiss <831355+rokob@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:39:24 -0400 Subject: [PATCH] Fix search/replace start of line anchor (#13920) This is related to #9428 I noticed that doing a search and replace for the beginning of a line `^` results in the trailing line being included in the search. This seems to be because of the way the range is generated for generating matches being the up to the start of the trailing line rather than up to the end of the last line. I added a test and took a stab at fixing it but it is a bit yolo as this is the first time I've seen this codebase. --- crates/vim/src/normal/search.rs | 49 ++++++++++++++++++- .../test_replace_with_range_at_start.json | 16 ++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 crates/vim/test_data/test_replace_with_range_at_start.json diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 6ac123ea1e..c579d8ff7e 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -2,6 +2,7 @@ use std::{ops::Range, sync::OnceLock, time::Duration}; use gpui::{actions, impl_actions, ViewContext}; use language::Point; +use multi_buffer::MultiBufferRow; use regex::Regex; use search::{buffer_search, BufferSearchBar, SearchOptions}; use serde_derive::Deserialize; @@ -362,9 +363,11 @@ fn replace_command( if let Some(editor) = editor.as_mut() { editor.update(cx, |editor, cx| { let snapshot = &editor.snapshot(cx).buffer_snapshot; + let end_row = MultiBufferRow(range.end.saturating_sub(1) as u32); + let end_point = Point::new(end_row.0, snapshot.line_len(end_row)); let range = snapshot .anchor_before(Point::new(range.start.saturating_sub(1) as u32, 0)) - ..snapshot.anchor_before(Point::new(range.end as u32, 0)); + ..snapshot.anchor_after(end_point); editor.set_search_within_ranges(&[range], cx) }) } @@ -727,6 +730,50 @@ mod test { }); } + // cargo test -p vim --features neovim test_replace_with_range_at_start + #[gpui::test] + async fn test_replace_with_range_at_start(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! { + "ˇa + a + a + a + a + a + a + " + }) + .await; + cx.simulate_shared_keystrokes(": 2 , 5 s / ^ / b").await; + cx.simulate_shared_keystrokes("enter").await; + cx.shared_state().await.assert_eq(indoc! { + "a + ba + ba + ba + ˇba + a + a + " + }); + cx.executor().advance_clock(Duration::from_millis(250)); + cx.run_until_parked(); + + cx.simulate_shared_keystrokes("/ a enter").await; + cx.shared_state().await.assert_eq(indoc! { + "a + ba + ba + ba + bˇa + a + a + " + }); + } + // cargo test -p vim --features neovim test_replace_with_range #[gpui::test] async fn test_replace_with_range(cx: &mut gpui::TestAppContext) { diff --git a/crates/vim/test_data/test_replace_with_range_at_start.json b/crates/vim/test_data/test_replace_with_range_at_start.json new file mode 100644 index 0000000000..e3810a7ba2 --- /dev/null +++ b/crates/vim/test_data/test_replace_with_range_at_start.json @@ -0,0 +1,16 @@ +{"Put":{"state":"ˇa\na\na\na\na\na\na\n "}} +{"Key":":"} +{"Key":"2"} +{"Key":","} +{"Key":"5"} +{"Key":"s"} +{"Key":"/"} +{"Key":"^"} +{"Key":"/"} +{"Key":"b"} +{"Key":"enter"} +{"Get":{"state":"a\nba\nba\nba\nˇba\na\na\n ","mode":"Normal"}} +{"Key":"/"} +{"Key":"a"} +{"Key":"enter"} +{"Get":{"state":"a\nba\nba\nba\nbˇa\na\na\n ","mode":"Normal"}}