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 6ac123ea1e446cf2ddc0f9679e8cdb9e338a3974..c579d8ff7e322fb6540d365aef236025b9596536 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 0000000000000000000000000000000000000000..e3810a7ba2bbe9f610e9d5c6a5ee5601b0ba5355 --- /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"}}