vim: Add gt and gT bindings for Markdown preview mode (#39854)

Pranav Joglekar and Conrad Irwin created

### What does this PR do?
- Adds default keybindings `gt` for navigating to the next tab and `gT`
for navigating to the previous tab in markdown viewer mode

### Why do we need this change?
- While previewing markdown files, the default vim bindings (`gt` and
`gT`) do not work for navigating between tabs. These bindings work
everywhere else, which provides a non-consistent experience for the
user.

### How do we do this change?
- Update the vim mode bindings to explicitly add handling for this mode

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

assets/keymaps/vim.json  |  8 +++---
crates/vim/src/normal.rs | 53 +----------------------------------------
crates/vim/src/vim.rs    | 45 ++++++++++++++++++++++++++++++++++
3 files changed, 50 insertions(+), 56 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -95,8 +95,6 @@
       "g g": "vim::StartOfDocument",
       "g h": "editor::Hover",
       "g B": "editor::BlameHover",
-      "g t": "vim::GoToTab",
-      "g shift-t": "vim::GoToPreviousTab",
       "g d": "editor::GoToDefinition",
       "g shift-d": "editor::GoToDeclaration",
       "g y": "editor::GoToTypeDefinition",
@@ -811,7 +809,7 @@
     }
   },
   {
-    "context": "VimControl || !Editor && !Terminal",
+    "context": "VimControl && !menu || !Editor && !Terminal",
     "bindings": {
       // window related commands (ctrl-w X)
       "ctrl-w": null,
@@ -865,7 +863,9 @@
       "ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
       "ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
       "ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
-      "ctrl-w n": "workspace::NewFileSplitHorizontal"
+      "ctrl-w n": "workspace::NewFileSplitHorizontal",
+      "g t": "vim::GoToTab",
+      "g shift-t": "vim::GoToPreviousTab"
     }
   },
   {

crates/vim/src/normal.rs 🔗

@@ -28,7 +28,7 @@ use editor::Editor;
 use editor::{Anchor, SelectionEffects};
 use editor::{Bias, ToPoint};
 use editor::{display_map::ToDisplayPoint, movement};
-use gpui::{Action, Context, Window, actions};
+use gpui::{Context, Window, actions};
 use language::{Point, SelectionGoal};
 use log::error;
 use multi_buffer::MultiBufferRow;
@@ -123,8 +123,6 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
     Vim::action(editor, cx, Vim::toggle_comments);
     Vim::action(editor, cx, Vim::paste);
     Vim::action(editor, cx, Vim::show_location);
-    Vim::action(editor, cx, Vim::go_to_tab);
-    Vim::action(editor, cx, Vim::go_to_previous_tab);
 
     Vim::action(editor, cx, |vim, _: &DeleteLeft, window, cx| {
         vim.record_current_action(cx);
@@ -1014,55 +1012,8 @@ impl Vim {
             self.switch_mode(Mode::Insert, true, window, cx);
         }
     }
-
-    fn go_to_tab(&mut self, _: &GoToTab, window: &mut Window, cx: &mut Context<Self>) {
-        let count = Vim::take_count(cx);
-        Vim::take_forced_motion(cx);
-
-        if let Some(tab_index) = count {
-            // <count>gt goes to tab <count> (1-based).
-            let zero_based_index = tab_index.saturating_sub(1);
-            window.dispatch_action(
-                workspace::pane::ActivateItem(zero_based_index).boxed_clone(),
-                cx,
-            );
-        } else {
-            // If no count is provided, go to the next tab.
-            window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx);
-        }
-    }
-
-    fn go_to_previous_tab(
-        &mut self,
-        _: &GoToPreviousTab,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let count = Vim::take_count(cx);
-        Vim::take_forced_motion(cx);
-
-        if let Some(count) = count {
-            // gT with count goes back that many tabs with wraparound (not the same as gt!).
-            if let Some(workspace) = self.workspace(window) {
-                let pane = workspace.read(cx).active_pane().read(cx);
-                let item_count = pane.items().count();
-                if item_count > 0 {
-                    let current_index = pane.active_item_index();
-                    let target_index = (current_index as isize - count as isize)
-                        .rem_euclid(item_count as isize)
-                        as usize;
-                    window.dispatch_action(
-                        workspace::pane::ActivateItem(target_index).boxed_clone(),
-                        cx,
-                    );
-                }
-            }
-        } else {
-            // No count provided, go to the previous tab.
-            window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), cx);
-        }
-    }
 }
+
 #[cfg(test)]
 mod test {
     use gpui::{KeyBinding, TestAppContext, UpdateGlobal};

crates/vim/src/vim.rs 🔗

@@ -51,7 +51,10 @@ use vim_mode_setting::HelixModeSetting;
 use vim_mode_setting::VimModeSetting;
 use workspace::{self, Pane, Workspace};
 
-use crate::state::ReplayableAction;
+use crate::{
+    normal::{GoToPreviousTab, GoToTab},
+    state::ReplayableAction,
+};
 
 /// Number is used to manage vim's count. Pushing a digit
 /// multiplies the current value by 10 and adds the digit.
@@ -409,6 +412,46 @@ pub fn init(cx: &mut App) {
                 cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))
             })
         });
+        workspace.register_action(|_, _: &GoToTab, window, cx| {
+            let count = Vim::take_count(cx);
+            Vim::take_forced_motion(cx);
+
+            if let Some(tab_index) = count {
+                // <count>gt goes to tab <count> (1-based).
+                let zero_based_index = tab_index.saturating_sub(1);
+                window.dispatch_action(
+                    workspace::pane::ActivateItem(zero_based_index).boxed_clone(),
+                    cx,
+                );
+            } else {
+                // If no count is provided, go to the next tab.
+                window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx);
+            }
+        });
+
+        workspace.register_action(|workspace, _: &GoToPreviousTab, window, cx| {
+            let count = Vim::take_count(cx);
+            Vim::take_forced_motion(cx);
+
+            if let Some(count) = count {
+                // gT with count goes back that many tabs with wraparound (not the same as gt!).
+                let pane = workspace.active_pane().read(cx);
+                let item_count = pane.items().count();
+                if item_count > 0 {
+                    let current_index = pane.active_item_index();
+                    let target_index = (current_index as isize - count as isize)
+                        .rem_euclid(item_count as isize)
+                        as usize;
+                    window.dispatch_action(
+                        workspace::pane::ActivateItem(target_index).boxed_clone(),
+                        cx,
+                    );
+                }
+            } else {
+                // No count provided, go to the previous tab.
+                window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), cx);
+            }
+        });
     })
     .detach();
 }