@@ -1660,9 +1660,13 @@ fn generate_commands(_: &App) -> Vec<VimCommand> {
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> {
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 {
@@ -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<Self>,
) {
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<Self>,
) {
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);
@@ -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,