diff --git a/crates/tab_switcher/src/tab_switcher_tests.rs b/crates/tab_switcher/src/tab_switcher_tests.rs index 5b8b9224192324cf0145417b252dcee3a07ddcc3..4c6cdce17ee32c558c203c51f608e3a654a344cd 100644 --- a/crates/tab_switcher/src/tab_switcher_tests.rs +++ b/crates/tab_switcher/src/tab_switcher_tests.rs @@ -223,8 +223,8 @@ async fn test_close_selected_item(cx: &mut gpui::TestAppContext) { // 1.txt | [3.txt] | 2.txt | 4.txt // // With 3.txt being the active item in the pane. - cx.dispatch_action(ActivatePreviousItem); - cx.dispatch_action(ActivatePreviousItem); + cx.dispatch_action(ActivatePreviousItem::default()); + cx.dispatch_action(ActivatePreviousItem::default()); cx.run_until_parked(); cx.simulate_modifiers_change(Modifiers::control()); diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 5e6337adda0325f6d7e5249da1a04c3317a3c051..362fed2df3543c5571f83db2c964a8c17fcebcb3 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -1660,9 +1660,13 @@ fn generate_commands(_: &App) -> Vec { action.range.replace(range.clone()); Some(Box::new(action)) }), - VimCommand::new(("bn", "ext"), workspace::ActivateNextItem).count(), - VimCommand::new(("bN", "ext"), workspace::ActivatePreviousItem).count(), - VimCommand::new(("bp", "revious"), workspace::ActivatePreviousItem).count(), + VimCommand::new(("bn", "ext"), workspace::ActivateNextItem::default()).count(), + VimCommand::new(("bN", "ext"), workspace::ActivatePreviousItem::default()).count(), + VimCommand::new( + ("bp", "revious"), + workspace::ActivatePreviousItem::default(), + ) + .count(), VimCommand::new(("bf", "irst"), workspace::ActivateItem(0)), VimCommand::new(("br", "ewind"), workspace::ActivateItem(0)), VimCommand::new(("bl", "ast"), workspace::ActivateLastItem), @@ -1670,9 +1674,13 @@ fn generate_commands(_: &App) -> Vec { VimCommand::str(("ls", ""), "tab_switcher::ToggleAll"), VimCommand::new(("new", ""), workspace::NewFileSplitHorizontal), VimCommand::new(("vne", "w"), workspace::NewFileSplitVertical), - VimCommand::new(("tabn", "ext"), workspace::ActivateNextItem).count(), - VimCommand::new(("tabp", "revious"), workspace::ActivatePreviousItem).count(), - VimCommand::new(("tabN", "ext"), workspace::ActivatePreviousItem).count(), + VimCommand::new(("tabn", "ext"), workspace::ActivateNextItem::default()).count(), + VimCommand::new( + ("tabp", "revious"), + workspace::ActivatePreviousItem::default(), + ) + .count(), + VimCommand::new(("tabN", "ext"), workspace::ActivatePreviousItem::default()).count(), VimCommand::new( ("tabc", "lose"), workspace::CloseActiveItem { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index eb2248ded0e574ca0d30206237694d50bc7f152e..05046899b6164f7c5884e3ad64ad69caaeb2015f 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -449,7 +449,10 @@ pub fn init(cx: &mut App) { ); } else { // If no count is provided, go to the next tab. - window.dispatch_action(workspace::pane::ActivateNextItem.boxed_clone(), cx); + window.dispatch_action( + workspace::pane::ActivateNextItem::default().boxed_clone(), + cx, + ); } }); @@ -473,7 +476,10 @@ pub fn init(cx: &mut App) { } } else { // No count provided, go to the previous tab. - window.dispatch_action(workspace::pane::ActivatePreviousItem.boxed_clone(), cx); + window.dispatch_action( + workspace::pane::ActivatePreviousItem::default().boxed_clone(), + cx, + ); } }); }) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c270ab63c38dabaf802a0ef668fb2fd6ec841721..deb7e1efef37acff992d8f5be5825741e887b979 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -48,7 +48,9 @@ use ui::{ IconDecorationKind, Indicator, PopoverMenu, PopoverMenuHandle, Tab, TabBar, TabPosition, Tooltip, prelude::*, right_click_menu, }; -use util::{ResultExt, debug_panic, maybe, paths::PathStyle, truncate_and_remove_front}; +use util::{ + ResultExt, debug_panic, maybe, paths::PathStyle, serde::default_true, truncate_and_remove_front, +}; /// A selected entry in e.g. project panel. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -229,13 +231,41 @@ split_structs!( SplitVertical => "Splits the pane vertically." ); +/// Activates the previous item in the pane. +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = pane)] +#[serde(deny_unknown_fields, default)] +pub struct ActivatePreviousItem { + /// Whether to wrap from the first item to the last item. + #[serde(default = "default_true")] + pub wrap_around: bool, +} + +impl Default for ActivatePreviousItem { + fn default() -> Self { + Self { wrap_around: true } + } +} + +/// Activates the next item in the pane. +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = pane)] +#[serde(deny_unknown_fields, default)] +pub struct ActivateNextItem { + /// Whether to wrap from the last item to the first item. + #[serde(default = "default_true")] + pub wrap_around: bool, +} + +impl Default for ActivateNextItem { + fn default() -> Self { + Self { wrap_around: true } + } +} + actions!( pane, [ - /// Activates the previous item in the pane. - ActivatePreviousItem, - /// Activates the next item in the pane. - ActivateNextItem, /// Activates the last item in the pane. ActivateLastItem, /// Switches to the alternate file. @@ -1477,14 +1507,14 @@ impl Pane { pub fn activate_previous_item( &mut self, - _: &ActivatePreviousItem, + action: &ActivatePreviousItem, window: &mut Window, cx: &mut Context, ) { let mut index = self.active_item_index; if index > 0 { index -= 1; - } else if !self.items.is_empty() { + } else if action.wrap_around && !self.items.is_empty() { index = self.items.len() - 1; } self.activate_item(index, true, true, window, cx); @@ -1492,14 +1522,14 @@ impl Pane { pub fn activate_next_item( &mut self, - _: &ActivateNextItem, + action: &ActivateNextItem, window: &mut Window, cx: &mut Context, ) { let mut index = self.active_item_index; if index + 1 < self.items.len() { index += 1; - } else { + } else if action.wrap_around { index = 0; } self.activate_item(index, true, true, window, cx); @@ -8585,6 +8615,51 @@ mod tests { ); } + #[gpui::test] + async fn test_activate_item_with_wrap_around(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", false, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", false, cx); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_next_item(&ActivateNextItem { wrap_around: false }, window, cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_next_item(&ActivateNextItem::default(), window, cx); + }); + assert_item_labels(&pane, ["A*", "B", "C"], cx); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_previous_item(&ActivatePreviousItem { wrap_around: false }, window, cx); + }); + assert_item_labels(&pane, ["A*", "B", "C"], cx); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_previous_item(&ActivatePreviousItem::default(), window, cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_previous_item(&ActivatePreviousItem { wrap_around: false }, window, cx); + }); + assert_item_labels(&pane, ["A", "B*", "C"], cx); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_next_item(&ActivateNextItem { wrap_around: false }, window, cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + } + fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { let settings_store = SettingsStore::test(cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index fe100182ca032e48d569d8a055bb4fa5fd7643d7..33d1befe38e3b48a39377547bd433398b37d6a77 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7083,7 +7083,7 @@ impl Workspace { )) .on_action(cx.listener(Workspace::toggle_centered_layout)) .on_action(cx.listener( - |workspace: &mut Workspace, _action: &pane::ActivateNextItem, window, cx| { + |workspace: &mut Workspace, action: &pane::ActivateNextItem, window, cx| { if let Some(active_dock) = workspace.active_dock(window, cx) { let dock = active_dock.read(cx); if let Some(active_panel) = dock.active_panel() { @@ -7101,14 +7101,17 @@ impl Workspace { } if let Some(pane) = recent_pane { + let wrap_around = action.wrap_around; pane.update(cx, |pane, cx| { let current_index = pane.active_item_index(); let items_len = pane.items_len(); if items_len > 0 { let next_index = if current_index + 1 < items_len { current_index + 1 - } else { + } else if wrap_around { 0 + } else { + return; }; pane.activate_item( next_index, false, false, window, cx, @@ -7124,7 +7127,7 @@ impl Workspace { }, )) .on_action(cx.listener( - |workspace: &mut Workspace, _action: &pane::ActivatePreviousItem, window, cx| { + |workspace: &mut Workspace, action: &pane::ActivatePreviousItem, window, cx| { if let Some(active_dock) = workspace.active_dock(window, cx) { let dock = active_dock.read(cx); if let Some(active_panel) = dock.active_panel() { @@ -7142,14 +7145,17 @@ impl Workspace { } if let Some(pane) = recent_pane { + let wrap_around = action.wrap_around; pane.update(cx, |pane, cx| { let current_index = pane.active_item_index(); let items_len = pane.items_len(); if items_len > 0 { let prev_index = if current_index > 0 { current_index - 1 - } else { + } else if wrap_around { items_len.saturating_sub(1) + } else { + return; }; pane.activate_item( prev_index, false, false, window, cx, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 992ce084e83aaa36c087d1273ab878b77c9beea3..49593dda129623064c998176942442bbdcb65f7c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4579,7 +4579,10 @@ mod tests { assert_key_bindings_for( window.into(), cx, - vec![("backspace", &ActionB), ("{", &ActivatePreviousItem)], + vec![ + ("backspace", &ActionB), + ("{", &ActivatePreviousItem::default()), + ], line!(), ); }