Add | as a bracket and a motion

Conrad Irwin created

Although vim/nvim doesn't have | as brackets, it's common in langauges like Rust
and Ruby, and I expect it to work.

Change summary

assets/keymaps/vim.json                                                | 224 
crates/vim/src/motion.rs                                               |  12 
crates/vim/src/object.rs                                               |  62 
crates/vim/test_data/test_multiline_surrounding_character_objects.json |   2 
4 files changed, 119 insertions(+), 181 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -39,6 +39,7 @@
       "w": "vim::NextWordStart",
       "{": "vim::StartOfParagraph",
       "}": "vim::EndOfParagraph",
+      "|": "vim::GoToColumn",
       "shift-w": [
         "vim::NextWordStart",
         {
@@ -97,14 +98,8 @@
       "ctrl-o": "pane::GoBack",
       "ctrl-i": "pane::GoForward",
       "ctrl-]": "editor::GoToDefinition",
-      "escape": [
-        "vim::SwitchMode",
-        "Normal"
-      ],
-      "ctrl+[": [
-        "vim::SwitchMode",
-        "Normal"
-      ],
+      "escape": ["vim::SwitchMode", "Normal"],
+      "ctrl+[": ["vim::SwitchMode", "Normal"],
       "v": "vim::ToggleVisual",
       "shift-v": "vim::ToggleVisualLine",
       "ctrl-v": "vim::ToggleVisualBlock",
@@ -233,123 +228,36 @@
         }
       ],
       // Count support
-      "1": [
-        "vim::Number",
-        1
-      ],
-      "2": [
-        "vim::Number",
-        2
-      ],
-      "3": [
-        "vim::Number",
-        3
-      ],
-      "4": [
-        "vim::Number",
-        4
-      ],
-      "5": [
-        "vim::Number",
-        5
-      ],
-      "6": [
-        "vim::Number",
-        6
-      ],
-      "7": [
-        "vim::Number",
-        7
-      ],
-      "8": [
-        "vim::Number",
-        8
-      ],
-      "9": [
-        "vim::Number",
-        9
-      ],
+      "1": ["vim::Number", 1],
+      "2": ["vim::Number", 2],
+      "3": ["vim::Number", 3],
+      "4": ["vim::Number", 4],
+      "5": ["vim::Number", 5],
+      "6": ["vim::Number", 6],
+      "7": ["vim::Number", 7],
+      "8": ["vim::Number", 8],
+      "9": ["vim::Number", 9],
       // window related commands (ctrl-w X)
-      "ctrl-w left": [
-        "workspace::ActivatePaneInDirection",
-        "Left"
-      ],
-      "ctrl-w right": [
-        "workspace::ActivatePaneInDirection",
-        "Right"
-      ],
-      "ctrl-w up": [
-        "workspace::ActivatePaneInDirection",
-        "Up"
-      ],
-      "ctrl-w down": [
-        "workspace::ActivatePaneInDirection",
-        "Down"
-      ],
-      "ctrl-w h": [
-        "workspace::ActivatePaneInDirection",
-        "Left"
-      ],
-      "ctrl-w l": [
-        "workspace::ActivatePaneInDirection",
-        "Right"
-      ],
-      "ctrl-w k": [
-        "workspace::ActivatePaneInDirection",
-        "Up"
-      ],
-      "ctrl-w j": [
-        "workspace::ActivatePaneInDirection",
-        "Down"
-      ],
-      "ctrl-w ctrl-h": [
-        "workspace::ActivatePaneInDirection",
-        "Left"
-      ],
-      "ctrl-w ctrl-l": [
-        "workspace::ActivatePaneInDirection",
-        "Right"
-      ],
-      "ctrl-w ctrl-k": [
-        "workspace::ActivatePaneInDirection",
-        "Up"
-      ],
-      "ctrl-w ctrl-j": [
-        "workspace::ActivatePaneInDirection",
-        "Down"
-      ],
-      "ctrl-w shift-left": [
-        "workspace::SwapPaneInDirection",
-        "Left"
-      ],
-      "ctrl-w shift-right": [
-        "workspace::SwapPaneInDirection",
-        "Right"
-      ],
-      "ctrl-w shift-up": [
-        "workspace::SwapPaneInDirection",
-        "Up"
-      ],
-      "ctrl-w shift-down": [
-        "workspace::SwapPaneInDirection",
-        "Down"
-      ],
-      "ctrl-w shift-h": [
-        "workspace::SwapPaneInDirection",
-        "Left"
-      ],
-      "ctrl-w shift-l": [
-        "workspace::SwapPaneInDirection",
-        "Right"
-      ],
-      "ctrl-w shift-k": [
-        "workspace::SwapPaneInDirection",
-        "Up"
-      ],
-      "ctrl-w shift-j": [
-        "workspace::SwapPaneInDirection",
-        "Down"
-      ],
+      "ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
+      "ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
+      "ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
+      "ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
+      "ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
+      "ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
+      "ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
+      "ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
+      "ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
+      "ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
+      "ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
+      "ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
+      "ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
+      "ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
+      "ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
+      "ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
+      "ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
+      "ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
+      "ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
+      "ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
       "ctrl-w g t": "pane::ActivateNextItem",
       "ctrl-w ctrl-g t": "pane::ActivateNextItem",
       "ctrl-w g shift-t": "pane::ActivatePrevItem",
@@ -371,14 +279,8 @@
       "ctrl-w ctrl-q": "pane::CloseAllItems",
       "ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
       "ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
-      "ctrl-w n": [
-        "workspace::NewFileInDirection",
-        "Up"
-      ],
-      "ctrl-w ctrl-n": [
-        "workspace::NewFileInDirection",
-        "Up"
-      ]
+      "ctrl-w n": ["workspace::NewFileInDirection", "Up"],
+      "ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"]
     }
   },
   {
@@ -393,21 +295,12 @@
     "context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
     "bindings": {
       ".": "vim::Repeat",
-      "c": [
-        "vim::PushOperator",
-        "Change"
-      ],
+      "c": ["vim::PushOperator", "Change"],
       "shift-c": "vim::ChangeToEndOfLine",
-      "d": [
-        "vim::PushOperator",
-        "Delete"
-      ],
+      "d": ["vim::PushOperator", "Delete"],
       "shift-d": "vim::DeleteToEndOfLine",
       "shift-j": "vim::JoinLines",
-      "y": [
-        "vim::PushOperator",
-        "Yank"
-      ],
+      "y": ["vim::PushOperator", "Yank"],
       "shift-y": "vim::YankLine",
       "i": "vim::InsertBefore",
       "shift-i": "vim::InsertFirstNonWhitespace",
@@ -443,10 +336,7 @@
           "backwards": true
         }
       ],
-      "r": [
-        "vim::PushOperator",
-        "Replace"
-      ],
+      "r": ["vim::PushOperator", "Replace"],
       "s": "vim::Substitute",
       "shift-s": "vim::SubstituteLine",
       "> >": "editor::Indent",
@@ -458,10 +348,7 @@
   {
     "context": "Editor && VimCount",
     "bindings": {
-      "0": [
-        "vim::Number",
-        0
-      ]
+      "0": ["vim::Number", 0]
     }
   },
   {
@@ -497,12 +384,15 @@
       "'": "vim::Quotes",
       "`": "vim::BackQuotes",
       "\"": "vim::DoubleQuotes",
+      "|": "vim::VerticalBars",
       "(": "vim::Parentheses",
       ")": "vim::Parentheses",
+      "b": "vim::Parentheses",
       "[": "vim::SquareBrackets",
       "]": "vim::SquareBrackets",
       "{": "vim::CurlyBrackets",
       "}": "vim::CurlyBrackets",
+      "shift-b": "vim::CurlyBrackets",
       "<": "vim::AngleBrackets",
       ">": "vim::AngleBrackets"
     }
@@ -548,22 +438,10 @@
       "shift-i": "vim::InsertBefore",
       "shift-a": "vim::InsertAfter",
       "shift-j": "vim::JoinLines",
-      "r": [
-        "vim::PushOperator",
-        "Replace"
-      ],
-      "ctrl-c": [
-        "vim::SwitchMode",
-        "Normal"
-      ],
-      "escape": [
-        "vim::SwitchMode",
-        "Normal"
-      ],
-      "ctrl+[": [
-        "vim::SwitchMode",
-        "Normal"
-      ],
+      "r": ["vim::PushOperator", "Replace"],
+      "ctrl-c": ["vim::SwitchMode", "Normal"],
+      "escape": ["vim::SwitchMode", "Normal"],
+      "ctrl+[": ["vim::SwitchMode", "Normal"],
       ">": "editor::Indent",
       "<": "editor::Outdent",
       "i": [
@@ -602,14 +480,8 @@
     "bindings": {
       "tab": "vim::Tab",
       "enter": "vim::Enter",
-      "escape": [
-        "vim::SwitchMode",
-        "Normal"
-      ],
-      "ctrl+[": [
-        "vim::SwitchMode",
-        "Normal"
-      ]
+      "escape": ["vim::SwitchMode", "Normal"],
+      "ctrl+[": ["vim::SwitchMode", "Normal"]
     }
   },
   {

crates/vim/src/motion.rs 🔗

@@ -40,6 +40,7 @@ pub enum Motion {
     NextLineStart,
     StartOfLineDownward,
     EndOfLineDownward,
+    GoToColumn,
 }
 
 #[derive(Clone, Deserialize, PartialEq)]
@@ -119,6 +120,7 @@ actions!(
         NextLineStart,
         StartOfLineDownward,
         EndOfLineDownward,
+        GoToColumn,
     ]
 );
 impl_actions!(
@@ -215,6 +217,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
         motion(Motion::EndOfLineDownward, cx)
     });
+    cx.add_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
     cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
         repeat_motion(action.backwards, cx)
     })
@@ -292,6 +295,7 @@ impl Motion {
             | Right
             | StartOfLine { .. }
             | EndOfLineDownward
+            | GoToColumn
             | NextWordStart { .. }
             | PreviousWordStart { .. }
             | FirstNonWhitespace { .. }
@@ -317,6 +321,7 @@ impl Motion {
             | EndOfParagraph
             | StartOfLineDownward
             | EndOfLineDownward
+            | GoToColumn
             | NextWordStart { .. }
             | PreviousWordStart { .. }
             | FirstNonWhitespace { .. }
@@ -346,6 +351,7 @@ impl Motion {
             | StartOfLineDownward
             | StartOfParagraph
             | EndOfParagraph
+            | GoToColumn
             | NextWordStart { .. }
             | PreviousWordStart { .. }
             | FirstNonWhitespace { .. }
@@ -429,6 +435,7 @@ impl Motion {
             NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
             StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None),
             EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None),
+            GoToColumn => (go_to_column(map, point, times), SelectionGoal::None),
         };
 
         (new_point != point || infallible).then_some((new_point, goal))
@@ -919,6 +926,11 @@ fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) ->
     first_non_whitespace(map, false, correct_line)
 }
 
+fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
+    let correct_line = start_of_relative_buffer_row(map, point, 0);
+    right(map, correct_line, times.saturating_sub(1))
+}
+
 pub(crate) fn next_line_end(
     map: &DisplaySnapshot,
     mut point: DisplayPoint,

crates/vim/src/object.rs 🔗

@@ -20,6 +20,7 @@ pub enum Object {
     Quotes,
     BackQuotes,
     DoubleQuotes,
+    VerticalBars,
     Parentheses,
     SquareBrackets,
     CurlyBrackets,
@@ -40,6 +41,7 @@ actions!(
         Quotes,
         BackQuotes,
         DoubleQuotes,
+        VerticalBars,
         Parentheses,
         SquareBrackets,
         CurlyBrackets,
@@ -64,6 +66,7 @@ pub fn init(cx: &mut AppContext) {
     });
     cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx));
     cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
+    cx.add_action(|_: &mut Workspace, _: &VerticalBars, cx: _| object(Object::VerticalBars, cx));
 }
 
 fn object(object: Object, cx: &mut WindowContext) {
@@ -79,9 +82,11 @@ fn object(object: Object, cx: &mut WindowContext) {
 impl Object {
     pub fn is_multiline(self) -> bool {
         match self {
-            Object::Word { .. } | Object::Quotes | Object::BackQuotes | Object::DoubleQuotes => {
-                false
-            }
+            Object::Word { .. }
+            | Object::Quotes
+            | Object::BackQuotes
+            | Object::VerticalBars
+            | Object::DoubleQuotes => false,
             Object::Sentence
             | Object::Parentheses
             | Object::AngleBrackets
@@ -96,6 +101,7 @@ impl Object {
             Object::Quotes
             | Object::BackQuotes
             | Object::DoubleQuotes
+            | Object::VerticalBars
             | Object::Parentheses
             | Object::SquareBrackets
             | Object::CurlyBrackets
@@ -111,6 +117,7 @@ impl Object {
             | Object::Quotes
             | Object::BackQuotes
             | Object::DoubleQuotes
+            | Object::VerticalBars
             | Object::Parentheses
             | Object::SquareBrackets
             | Object::CurlyBrackets
@@ -142,6 +149,9 @@ impl Object {
             Object::DoubleQuotes => {
                 surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
             }
+            Object::VerticalBars => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '|', '|')
+            }
             Object::Parentheses => {
                 surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
             }
@@ -568,7 +578,10 @@ fn surrounding_markers(
 mod test {
     use indoc::indoc;
 
-    use crate::test::{ExemptionFeatures, NeovimBackedTestContext};
+    use crate::{
+        state::Mode,
+        test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
+    };
 
     const WORD_LOCATIONS: &'static str = indoc! {"
         The quick ˇbrowˇnˇ•••
@@ -940,6 +953,47 @@ mod test {
             .await;
     }
 
+    #[gpui::test]
+    async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new(cx, true).await;
+        cx.set_state(
+            indoc! {"
+            fn boop() {
+                baz(ˇ|a, b| { bar(|j, k| { })})
+            }"
+            },
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes(["c", "i", "|"]);
+        cx.assert_state(
+            indoc! {"
+            fn boop() {
+                baz(|ˇ| { bar(|j, k| { })})
+            }"
+            },
+            Mode::Insert,
+        );
+        cx.simulate_keystrokes(["escape", "1", "8", "|"]);
+        cx.assert_state(
+            indoc! {"
+            fn boop() {
+                baz(|| { bar(ˇ|j, k| { })})
+            }"
+            },
+            Mode::Normal,
+        );
+
+        cx.simulate_keystrokes(["v", "a", "|"]);
+        cx.assert_state(
+            indoc! {"
+            fn boop() {
+                baz(|| { bar(«|j, k| ˇ»{ })})
+            }"
+            },
+            Mode::Visual,
+        );
+    }
+
     #[gpui::test]
     async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await;

crates/vim/test_data/test_multiline_surrounding_character_objects.json 🔗

@@ -8,7 +8,7 @@
 {"Key":"i"}
 {"Key":"{"}
 {"Get":{"state":"func empty(a string) bool {\n     if a == \"\" {\n«         return true\nˇ»     }\n     return false\n}","mode":"Visual"}}
-{"Put":{"state":"func empty(a string) bool {\n     if a == \"\" {\n         ˇreturn true\n     }\n     return false\n}"}}
+{"Put":{"state":"func empty(a string) bool {\n     if a == \"\" ˇ{\n         return true\n     }\n     return false\n}"}}
 {"Key":"v"}
 {"Key":"i"}
 {"Key":"{"}