Simulate helix line wrapping (#32763)

fantacell created

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)

Change summary

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(-)

Detailed changes

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",

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": {},

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,
+        );
+    }
 }

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::<VimSettings>(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::<VimSettings>(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;

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<char>,
@@ -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",

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<String, Arc<str>>,
     pub highlight_on_yank_duration: u64,
@@ -1741,7 +1744,6 @@ struct VimSettingsContent {
     pub default_mode: Option<ModeContent>,
     pub toggle_relative_line_numbers: Option<bool>,
     pub use_system_clipboard: Option<UseSystemClipboard>,
-    pub use_multiline_find: Option<bool>,
     pub use_smartcase_find: Option<bool>,
     pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
     pub highlight_on_yank_duration: Option<u64>,
@@ -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)?,

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:<br><ul><li>"always": use for all operations</li><li>"never": only use when explicitly specified</li><li>"on_yank": use for yank operations</li></ul> | "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,