@@ -95,8 +95,8 @@
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g B": "editor::BlameHover",
- "g t": "pane::ActivateNextItem",
- "g shift-t": "pane::ActivatePreviousItem",
+ "g t": "vim::GoToTab",
+ "g shift-t": "vim::GoToPreviousTab",
"g d": "editor::GoToDefinition",
"g shift-d": "editor::GoToDeclaration",
"g y": "editor::GoToTypeDefinition",
@@ -28,7 +28,7 @@ use editor::Editor;
use editor::{Anchor, SelectionEffects};
use editor::{Bias, ToPoint};
use editor::{display_map::ToDisplayPoint, movement};
-use gpui::{Context, Window, actions};
+use gpui::{Action, Context, Window, actions};
use language::{Point, SelectionGoal};
use log::error;
use multi_buffer::MultiBufferRow;
@@ -94,6 +94,10 @@ actions!(
Redo,
/// Undoes all changes to the most recently changed line.
UndoLastLine,
+ /// Go to tab page (with count support).
+ GoToTab,
+ /// Go to previous tab page (with count support).
+ GoToPreviousTab,
]
);
@@ -116,6 +120,8 @@ 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);
@@ -984,6 +990,54 @@ 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 {
@@ -2119,4 +2173,80 @@ mod test {
Mode::Normal,
);
}
+
+ #[gpui::test]
+ async fn test_go_to_tab_with_count(cx: &mut gpui::TestAppContext) {
+ let mut cx = VimTestContext::new(cx, true).await;
+
+ // Open 4 tabs.
+ cx.simulate_keystrokes(": tabnew");
+ cx.simulate_keystrokes("enter");
+ cx.simulate_keystrokes(": tabnew");
+ cx.simulate_keystrokes("enter");
+ cx.simulate_keystrokes(": tabnew");
+ cx.simulate_keystrokes("enter");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.items(cx).count(), 4);
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3);
+ });
+
+ cx.simulate_keystrokes("1 g t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 0);
+ });
+
+ cx.simulate_keystrokes("3 g t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 2);
+ });
+
+ cx.simulate_keystrokes("4 g t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3);
+ });
+
+ cx.simulate_keystrokes("1 g t");
+ cx.simulate_keystrokes("g t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 1);
+ });
+ }
+
+ #[gpui::test]
+ async fn test_go_to_previous_tab_with_count(cx: &mut gpui::TestAppContext) {
+ let mut cx = VimTestContext::new(cx, true).await;
+
+ // Open 4 tabs.
+ cx.simulate_keystrokes(": tabnew");
+ cx.simulate_keystrokes("enter");
+ cx.simulate_keystrokes(": tabnew");
+ cx.simulate_keystrokes("enter");
+ cx.simulate_keystrokes(": tabnew");
+ cx.simulate_keystrokes("enter");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.items(cx).count(), 4);
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3);
+ });
+
+ cx.simulate_keystrokes("2 g shift-t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 1);
+ });
+
+ cx.simulate_keystrokes("g shift-t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 0);
+ });
+
+ // Wraparound: gT from first tab should go to last.
+ cx.simulate_keystrokes("g shift-t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 3);
+ });
+
+ cx.simulate_keystrokes("6 g shift-t");
+ cx.workspace(|workspace, _, cx| {
+ assert_eq!(workspace.active_pane().read(cx).active_item_index(), 1);
+ });
+ }
}