From 95cf153ad75a697f89b0b3affaafb51e1346e69e Mon Sep 17 00:00:00 2001 From: fantacell Date: Tue, 24 Jun 2025 18:51:41 +0200 Subject: [PATCH] Simulate helix line wrapping (#32763) In helix the `f`, `F`, `t`, `T`, left and right motions wrap lines. I added that by default. Release Notes: - vim: The `use_multiline_find` setting is replaced by binding to the correct action in the keymap: ``` "f": ["vim::PushFindForward", { "before": false, "multiline": true }], "t": ["vim::PushFindForward", { "before": true, "multiline": true }], "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }], "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }], ``` - helix: `f`/`t`/`shift-f`/`shift-t`/`h`/`l`/`left`/`right` are now multiline by default (like helix) --- assets/keymaps/vim.json | 16 +++++-- assets/settings/default.json | 1 - crates/vim/src/helix.rs | 33 ++++++++++++++ crates/vim/src/normal.rs | 84 ------------------------------------ crates/vim/src/state.rs | 10 +++-- crates/vim/src/vim.rs | 17 ++++---- docs/src/vim.md | 3 +- 7 files changed, 60 insertions(+), 104 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index dde0770848..6b95839e2a 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -85,10 +85,10 @@ "[ {": ["vim::UnmatchedBackward", { "char": "{" }], "] )": ["vim::UnmatchedForward", { "char": ")" }], "[ (": ["vim::UnmatchedBackward", { "char": "(" }], - "f": ["vim::PushFindForward", { "before": false }], - "t": ["vim::PushFindForward", { "before": true }], - "shift-f": ["vim::PushFindBackward", { "after": false }], - "shift-t": ["vim::PushFindBackward", { "after": true }], + "f": ["vim::PushFindForward", { "before": false, "multiline": false }], + "t": ["vim::PushFindForward", { "before": true, "multiline": false }], + "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": false }], + "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": false }], "m": "vim::PushMark", "'": ["vim::PushJump", { "line": true }], "`": ["vim::PushJump", { "line": false }], @@ -368,6 +368,10 @@ "escape": "editor::Cancel", "ctrl-[": "editor::Cancel", ":": "command_palette::Toggle", + "left": "vim::WrappingLeft", + "right": "vim::WrappingRight", + "h": "vim::WrappingLeft", + "l": "vim::WrappingRight", "shift-d": "vim::DeleteToEndOfLine", "shift-j": "vim::JoinLines", "y": "editor::Copy", @@ -385,6 +389,10 @@ "shift-p": ["vim::Paste", { "before": true }], "u": "vim::Undo", "ctrl-r": "vim::Redo", + "f": ["vim::PushFindForward", { "before": false, "multiline": true }], + "t": ["vim::PushFindForward", { "before": true, "multiline": true }], + "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }], + "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }], "r": "vim::PushReplace", "s": "vim::Substitute", "shift-s": "vim::SubstituteLine", diff --git a/assets/settings/default.json b/assets/settings/default.json index 3dd85198d9..858055fbe6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1734,7 +1734,6 @@ "default_mode": "normal", "toggle_relative_line_numbers": false, "use_system_clipboard": "always", - "use_multiline_find": false, "use_smartcase_find": false, "highlight_on_yank_duration": 200, "custom_digraphs": {}, diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 2e7c371d35..8c1ab3297e 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -435,4 +435,37 @@ mod test { // Mode::HelixNormal, // ); // } + + #[gpui::test] + async fn test_f_and_t(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! {" + The quˇick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("f z"); + + cx.assert_state( + indoc! {" + The qu«ick brown + fox jumps over + the lazˇ»y dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("2 T r"); + + cx.assert_state( + indoc! {" + The quick br«ˇown + fox jumps over + the laz»y dog."}, + Mode::HelixNormal, + ); + } } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 5d4dcacd6c..ff9b347e41 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -1532,90 +1532,6 @@ mod test { } } - #[gpui::test] - async fn test_f_and_t_multiline(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_multiline_find = Some(true); - }); - }); - - cx.assert_binding( - "f l", - indoc! {" - ˇfunction print() { - console.log('ok') - } - "}, - Mode::Normal, - indoc! {" - function print() { - consoˇle.log('ok') - } - "}, - Mode::Normal, - ); - - cx.assert_binding( - "t l", - indoc! {" - ˇfunction print() { - console.log('ok') - } - "}, - Mode::Normal, - indoc! {" - function print() { - consˇole.log('ok') - } - "}, - Mode::Normal, - ); - } - - #[gpui::test] - async fn test_capital_f_and_capital_t_multiline(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_multiline_find = Some(true); - }); - }); - - cx.assert_binding( - "shift-f p", - indoc! {" - function print() { - console.ˇlog('ok') - } - "}, - Mode::Normal, - indoc! {" - function ˇprint() { - console.log('ok') - } - "}, - Mode::Normal, - ); - - cx.assert_binding( - "shift-t p", - indoc! {" - function print() { - console.ˇlog('ok') - } - "}, - Mode::Normal, - indoc! {" - function pˇrint() { - console.log('ok') - } - "}, - Mode::Normal, - ); - } - #[gpui::test] async fn test_f_and_t_smartcase(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 46dafdd6c8..c4be034871 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -86,9 +86,11 @@ pub enum Operator { }, FindForward { before: bool, + multiline: bool, }, FindBackward { after: bool, + multiline: bool, }, Sneak { first_char: Option, @@ -994,12 +996,12 @@ impl Operator { Operator::Replace => "r", Operator::Digraph { .. } => "^K", Operator::Literal { .. } => "^V", - Operator::FindForward { before: false } => "f", - Operator::FindForward { before: true } => "t", + Operator::FindForward { before: false, .. } => "f", + Operator::FindForward { before: true, .. } => "t", Operator::Sneak { .. } => "s", Operator::SneakBackward { .. } => "S", - Operator::FindBackward { after: false } => "F", - Operator::FindBackward { after: true } => "T", + Operator::FindBackward { after: false, .. } => "F", + Operator::FindBackward { after: true, .. } => "T", Operator::AddSurrounds { .. } => "ys", Operator::ChangeSurrounds { .. } => "cs", Operator::DeleteSurrounds => "ds", diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 6447300ed4..6b5d41f12e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -72,6 +72,7 @@ struct PushObject { #[serde(deny_unknown_fields)] struct PushFindForward { before: bool, + multiline: bool, } #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] @@ -79,6 +80,7 @@ struct PushFindForward { #[serde(deny_unknown_fields)] struct PushFindBackward { after: bool, + multiline: bool, } #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] @@ -500,6 +502,7 @@ impl Vim { vim.push_operator( Operator::FindForward { before: action.before, + multiline: action.multiline, }, window, cx, @@ -510,6 +513,7 @@ impl Vim { vim.push_operator( Operator::FindBackward { after: action.after, + multiline: action.multiline, }, window, cx, @@ -1513,11 +1517,11 @@ impl Vim { } match self.active_operator() { - Some(Operator::FindForward { before }) => { + Some(Operator::FindForward { before, multiline }) => { let find = Motion::FindForward { before, char: text.chars().next().unwrap(), - mode: if VimSettings::get_global(cx).use_multiline_find { + mode: if multiline { FindRange::MultiLine } else { FindRange::SingleLine @@ -1527,11 +1531,11 @@ impl Vim { Vim::globals(cx).last_find = Some(find.clone()); self.motion(find, window, cx) } - Some(Operator::FindBackward { after }) => { + Some(Operator::FindBackward { after, multiline }) => { let find = Motion::FindBackward { after, char: text.chars().next().unwrap(), - mode: if VimSettings::get_global(cx).use_multiline_find { + mode: if multiline { FindRange::MultiLine } else { FindRange::SingleLine @@ -1729,7 +1733,6 @@ struct VimSettings { pub default_mode: Mode, pub toggle_relative_line_numbers: bool, pub use_system_clipboard: UseSystemClipboard, - pub use_multiline_find: bool, pub use_smartcase_find: bool, pub custom_digraphs: HashMap>, pub highlight_on_yank_duration: u64, @@ -1741,7 +1744,6 @@ struct VimSettingsContent { pub default_mode: Option, pub toggle_relative_line_numbers: Option, pub use_system_clipboard: Option, - pub use_multiline_find: Option, pub use_smartcase_find: Option, pub custom_digraphs: Option>>, pub highlight_on_yank_duration: Option, @@ -1794,9 +1796,6 @@ impl Settings for VimSettings { use_system_clipboard: settings .use_system_clipboard .ok_or_else(Self::missing_default)?, - use_multiline_find: settings - .use_multiline_find - .ok_or_else(Self::missing_default)?, use_smartcase_find: settings .use_smartcase_find .ok_or_else(Self::missing_default)?, diff --git a/docs/src/vim.md b/docs/src/vim.md index 2055e6d68d..3d3a1bac01 100644 --- a/docs/src/vim.md +++ b/docs/src/vim.md @@ -561,7 +561,7 @@ You can change the following settings to modify vim mode's behavior: | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | default_mode | The default mode to start in. One of "normal", "insert", "replace", "visual", "visual_line", "visual_block", "helix_normal". | "normal" | | use_system_clipboard | Determines how system clipboard is used:
  • "always": use for all operations
  • "never": only use when explicitly specified
  • "on_yank": use for yank operations
| "always" | -| use_multiline_find | If `true`, `f` and `t` motions extend across multiple lines. | false | +| use_multiline_find | deprecated | | use_smartcase_find | If `true`, `f` and `t` motions are case-insensitive when the target letter is lowercase. | false | | toggle_relative_line_numbers | If `true`, line numbers are relative in normal mode and absolute in insert mode, giving you the best of both options. | false | | custom_digraphs | An object that allows you to add custom digraphs. Read below for an example. | {} | @@ -586,7 +586,6 @@ Here's an example of these settings changed: "vim": { "default_mode": "insert", "use_system_clipboard": "never", - "use_multiline_find": true, "use_smartcase_find": true, "toggle_relative_line_numbers": true, "highlight_on_yank_duration": 50,