Merge pull request #2210 from zed-industries/fix-enter-vim-normal-mode

Kay Simmons created

Fix enter in normal mode acting incorrectly

Change summary

assets/keymaps/vim.json              |  1 
crates/vim/src/motion.rs             | 50 ++++++++++++++++++++++++++---
crates/vim/src/normal.rs             | 21 ++++++------
crates/vim/test_data/test_enter.json |  1 
4 files changed, 57 insertions(+), 16 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -27,6 +27,7 @@
             "h": "vim::Left",
             "backspace": "vim::Backspace",
             "j": "vim::Down",
+            "enter": "vim::NextLineStart",
             "k": "vim::Up",
             "l": "vim::Right",
             "$": "vim::EndOfLine",

crates/vim/src/motion.rs 🔗

@@ -36,6 +36,7 @@ pub enum Motion {
     Matching,
     FindForward { before: bool, text: Arc<str> },
     FindBackward { after: bool, text: Arc<str> },
+    NextLineStart,
 }
 
 #[derive(Clone, Deserialize, PartialEq)]
@@ -74,6 +75,7 @@ actions!(
         StartOfDocument,
         EndOfDocument,
         Matching,
+        NextLineStart,
     ]
 );
 impl_actions!(vim, [NextWordStart, NextWordEnd, PreviousWordStart]);
@@ -111,6 +113,7 @@ pub fn init(cx: &mut MutableAppContext) {
          &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
          cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
     );
+    cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx))
 }
 
 pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
@@ -138,15 +141,43 @@ pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
 impl Motion {
     pub fn linewise(&self) -> bool {
         use Motion::*;
-        matches!(
-            self,
-            Down | Up | StartOfDocument | EndOfDocument | CurrentLine
-        )
+        match self {
+            Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart => true,
+            EndOfLine
+            | NextWordEnd { .. }
+            | Matching
+            | FindForward { .. }
+            | Left
+            | Backspace
+            | Right
+            | StartOfLine
+            | NextWordStart { .. }
+            | PreviousWordStart { .. }
+            | FirstNonWhitespace
+            | FindBackward { .. } => false,
+        }
     }
 
     pub fn infallible(&self) -> bool {
         use Motion::*;
-        matches!(self, StartOfDocument | CurrentLine | EndOfDocument)
+        match self {
+            StartOfDocument | EndOfDocument | CurrentLine => true,
+            Down
+            | Up
+            | EndOfLine
+            | NextWordEnd { .. }
+            | Matching
+            | FindForward { .. }
+            | Left
+            | Backspace
+            | Right
+            | StartOfLine
+            | NextWordStart { .. }
+            | PreviousWordStart { .. }
+            | FirstNonWhitespace
+            | FindBackward { .. }
+            | NextLineStart => false,
+        }
     }
 
     pub fn inclusive(&self) -> bool {
@@ -160,7 +191,8 @@ impl Motion {
             | EndOfLine
             | NextWordEnd { .. }
             | Matching
-            | FindForward { .. } => true,
+            | FindForward { .. }
+            | NextLineStart => true,
             Left
             | Backspace
             | Right
@@ -214,6 +246,7 @@ impl Motion {
                 find_backward(map, point, *after, text.clone(), times),
                 SelectionGoal::None,
             ),
+            NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
         };
 
         (new_point != point || infallible).then_some((new_point, goal))
@@ -543,3 +576,8 @@ fn find_backward(
         })
         .unwrap_or(from)
 }
+
+fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
+    let new_row = (point.row() + times as u32).min(map.max_buffer_row());
+    map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left)
+}

crates/vim/src/normal.rs 🔗

@@ -473,6 +473,7 @@ pub(crate) fn normal_replace(text: Arc<str>, cx: &mut MutableAppContext) {
 
 #[cfg(test)]
 mod test {
+    use gpui::TestAppContext;
     use indoc::indoc;
 
     use crate::{
@@ -515,15 +516,15 @@ mod test {
         .await;
     }
 
-    // #[gpui::test]
-    // async fn test_enter(cx: &mut gpui::TestAppContext) {
-    //     let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
-    //     cx.assert_all(indoc! {"
-    //         ˇThe qˇuick broˇwn
-    //         ˇfox jumps"
-    //     })
-    //     .await;
-    // }
+    #[gpui::test]
+    async fn test_enter(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
+        cx.assert_all(indoc! {"
+            ˇThe qˇuick broˇwn
+            ˇfox jumps"
+        })
+        .await;
+    }
 
     #[gpui::test]
     async fn test_k(cx: &mut gpui::TestAppContext) {
@@ -1030,7 +1031,7 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_percent(cx: &mut gpui::TestAppContext) {
+    async fn test_percent(cx: &mut TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
         cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
         cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")

crates/vim/test_data/test_enter.json 🔗

@@ -0,0 +1 @@
+[{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"}]