From 35f5eb1fe79cb7ed5932f2b6bb236c1b960af276 Mon Sep 17 00:00:00 2001 From: Pranav Joglekar Date: Thu, 16 Oct 2025 11:01:12 +0530 Subject: [PATCH] vim: Add gt and gT bindings for Markdown preview mode (#39854) ### 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 --- assets/keymaps/vim.json | 8 +++--- crates/vim/src/normal.rs | 53 ++-------------------------------------- crates/vim/src/vim.rs | 45 +++++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 56 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index c90b439c6abb60f4e3d826c171d7e2491fce1d90..542492a0d4803bfbdaaa98bbe98caa4a43e7c35b 100644 --- a/assets/keymaps/vim.json +++ b/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" } }, { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 9386eab58a389b4917cdf33078ac7397ffd01796..c79fd7984fb5ad0194a0c4cdd7ba5bb1ac11198c 100644 --- a/crates/vim/src/normal.rs +++ b/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::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) { - let count = Vim::take_count(cx); - Vim::take_forced_motion(cx); - - if let Some(tab_index) = count { - // gt goes to tab (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, - ) { - 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}; diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index d4c81737fdbda08a1a2861c184035a784960fe52..1d8aed62936711cdd048ee1817d2d2aad475f628 100644 --- a/crates/vim/src/vim.rs +++ b/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 { + // gt goes to tab (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(); }