From f67abd2943907e7558ae1ec768ae7ad62f3cdab4 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 7 Mar 2024 21:44:20 -0500 Subject: [PATCH] vim: smartcase find option (#9033) Release Notes: - Added option `use_smartcase_find` to the vim-mode --- assets/settings/default.json | 3 +- crates/vim/src/motion.rs | 96 ++++++++++++++++---- crates/vim/src/normal.rs | 42 +++++++++ crates/vim/src/vim.rs | 4 + docs/src/configuring_zed__configuring_vim.md | 4 +- 5 files changed, 127 insertions(+), 22 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 81d010ba9b..700a7147d0 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -595,7 +595,8 @@ // Vim settings "vim": { "use_system_clipboard": "always", - "use_multiline_find": false + "use_multiline_find": false, + "use_smartcase_find": false }, // The server to connect to. If the environment variable // ZED_SERVER_URL is set, it will override this setting. diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 37a5ae48d5..a421259de6 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -73,11 +73,13 @@ pub enum Motion { before: bool, char: char, mode: FindRange, + smartcase: bool, }, FindBackward { after: bool, char: char, mode: FindRange, + smartcase: bool, }, RepeatFind { last_find: Box, @@ -604,30 +606,54 @@ impl Motion { ), Matching => (matching(map, point), SelectionGoal::None), // t f - FindForward { before, char, mode } => { - return find_forward(map, point, *before, *char, times, *mode) + FindForward { + before, + char, + mode, + smartcase, + } => { + return find_forward(map, point, *before, *char, times, *mode, *smartcase) .map(|new_point| (new_point, SelectionGoal::None)) } // T F - FindBackward { after, char, mode } => ( - find_backward(map, point, *after, *char, times, *mode), + FindBackward { + after, + char, + mode, + smartcase, + } => ( + find_backward(map, point, *after, *char, times, *mode, *smartcase), SelectionGoal::None, ), // ; -- repeat the last find done with t, f, T, F RepeatFind { last_find } => match **last_find { - Motion::FindForward { before, char, mode } => { - let mut new_point = find_forward(map, point, before, char, times, mode); + Motion::FindForward { + before, + char, + mode, + smartcase, + } => { + let mut new_point = + find_forward(map, point, before, char, times, mode, smartcase); if new_point == Some(point) { - new_point = find_forward(map, point, before, char, times + 1, mode); + new_point = + find_forward(map, point, before, char, times + 1, mode, smartcase); } return new_point.map(|new_point| (new_point, SelectionGoal::None)); } - Motion::FindBackward { after, char, mode } => { - let mut new_point = find_backward(map, point, after, char, times, mode); + Motion::FindBackward { + after, + char, + mode, + smartcase, + } => { + let mut new_point = + find_backward(map, point, after, char, times, mode, smartcase); if new_point == point { - new_point = find_backward(map, point, after, char, times + 1, mode); + new_point = + find_backward(map, point, after, char, times + 1, mode, smartcase); } (new_point, SelectionGoal::None) @@ -636,19 +662,33 @@ impl Motion { }, // , -- repeat the last find done with t, f, T, F, in opposite direction RepeatFindReversed { last_find } => match **last_find { - Motion::FindForward { before, char, mode } => { - let mut new_point = find_backward(map, point, before, char, times, mode); + Motion::FindForward { + before, + char, + mode, + smartcase, + } => { + let mut new_point = + find_backward(map, point, before, char, times, mode, smartcase); if new_point == point { - new_point = find_backward(map, point, before, char, times + 1, mode); + new_point = + find_backward(map, point, before, char, times + 1, mode, smartcase); } (new_point, SelectionGoal::None) } - Motion::FindBackward { after, char, mode } => { - let mut new_point = find_forward(map, point, after, char, times, mode); + Motion::FindBackward { + after, + char, + mode, + smartcase, + } => { + let mut new_point = + find_forward(map, point, after, char, times, mode, smartcase); if new_point == Some(point) { - new_point = find_forward(map, point, after, char, times + 1, mode); + new_point = + find_forward(map, point, after, char, times + 1, mode, smartcase); } return new_point.map(|new_point| (new_point, SelectionGoal::None)); @@ -1368,6 +1408,7 @@ fn find_forward( target: char, times: usize, mode: FindRange, + smartcase: bool, ) -> Option { let mut to = from; let mut found = false; @@ -1375,7 +1416,7 @@ fn find_forward( for _ in 0..times { found = false; let new_to = find_boundary(map, to, mode, |_, right| { - found = right == target; + found = is_character_match(target, right, smartcase); found }); if to == new_to { @@ -1403,19 +1444,22 @@ fn find_backward( target: char, times: usize, mode: FindRange, + smartcase: bool, ) -> DisplayPoint { let mut to = from; for _ in 0..times { - let new_to = - find_preceding_boundary_display_point(map, to, mode, |_, right| right == target); + let new_to = find_preceding_boundary_display_point(map, to, mode, |_, right| { + is_character_match(target, right, smartcase) + }); if to == new_to { break; } to = new_to; } - if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) { + let next = map.buffer_snapshot.chars_at(to.to_point(map)).next(); + if next.is_some() && is_character_match(target, next.unwrap(), smartcase) { if after { *to.column_mut() += 1; map.clip_point(to, Bias::Right) @@ -1427,6 +1471,18 @@ fn find_backward( } } +fn is_character_match(target: char, other: char, smartcase: bool) -> bool { + if smartcase { + if target.is_uppercase() { + target == other + } else { + target == other.to_ascii_lowercase() + } + } else { + target == other + } +} + fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint { let correct_line = start_of_relative_buffer_row(map, point, times as isize); first_non_whitespace(map, false, correct_line) diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 0ad47693e5..6706554f5f 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -1020,6 +1020,48 @@ mod test { ); } + #[gpui::test] + async fn test_f_and_t_smartcase(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| { + s.use_smartcase_find = Some(true); + }); + }); + + cx.assert_binding( + ["f", "p"], + indoc! {"ˇfmt.Println(\"Hello, World!\")"}, + Mode::Normal, + indoc! {"fmt.ˇPrintln(\"Hello, World!\")"}, + Mode::Normal, + ); + + cx.assert_binding( + ["shift-f", "p"], + indoc! {"fmt.Printlnˇ(\"Hello, World!\")"}, + Mode::Normal, + indoc! {"fmt.ˇPrintln(\"Hello, World!\")"}, + Mode::Normal, + ); + + cx.assert_binding( + ["t", "p"], + indoc! {"ˇfmt.Println(\"Hello, World!\")"}, + Mode::Normal, + indoc! {"fmtˇ.Println(\"Hello, World!\")"}, + Mode::Normal, + ); + + cx.assert_binding( + ["shift-t", "p"], + indoc! {"fmt.Printlnˇ(\"Hello, World!\")"}, + Mode::Normal, + indoc! {"fmt.Pˇrintln(\"Hello, World!\")"}, + Mode::Normal, + ); + } + #[gpui::test] async fn test_percent(cx: &mut TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 39fec5d0c8..bb82bf2e1b 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -487,6 +487,7 @@ impl Vim { } else { FindRange::SingleLine }, + smartcase: VimSettings::get_global(cx).use_smartcase_find, }; Vim::update(cx, |vim, _| { vim.workspace_state.last_find = Some(find.clone()) @@ -502,6 +503,7 @@ impl Vim { } else { FindRange::SingleLine }, + smartcase: VimSettings::get_global(cx).use_smartcase_find, }; Vim::update(cx, |vim, _| { vim.workspace_state.last_find = Some(find.clone()) @@ -642,12 +644,14 @@ struct VimSettings { // some magic where yy is system and dd is not. pub use_system_clipboard: UseSystemClipboard, pub use_multiline_find: bool, + pub use_smartcase_find: bool, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] struct VimSettingsContent { pub use_system_clipboard: Option, pub use_multiline_find: Option, + pub use_smartcase_find: Option, } impl Settings for VimSettings { diff --git a/docs/src/configuring_zed__configuring_vim.md b/docs/src/configuring_zed__configuring_vim.md index c43e324c53..c84bc6fa60 100644 --- a/docs/src/configuring_zed__configuring_vim.md +++ b/docs/src/configuring_zed__configuring_vim.md @@ -172,7 +172,9 @@ Some vim settings are available to modify the default vim behavior: // "on_yank": use system clipboard for yank operations "use_system_clipboard": "always", // Enable multi-line find for `f` and `t` motions - "use_multiline_find": false + "use_multiline_find": false, + // Enable smartcase find for `f` and `t` motions + "use_smartcase_find": false } } ```