diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index bd3980ecbd..47bd8baa15 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -1,4 +1,10 @@ [ + { + "context": "ProjectPanel || Editor", + "bindings": { + "ctrl-6": "pane::AlternateFile" + } + }, { "context": "Editor && VimControl && !VimWaiting && !menu", "bindings": { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index bdcf6f6eb5..68bae37e4e 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -330,6 +330,7 @@ pub trait ItemHandle: 'static + Send { fn serialized_item_kind(&self) -> Option<&'static str>; fn show_toolbar(&self, cx: &AppContext) -> bool; fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option>; + fn downgrade_item(&self) -> Box; } pub trait WeakItemHandle: Send + Sync { @@ -702,6 +703,10 @@ impl ItemHandle for View { fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option> { self.read(cx).pixel_position_of_cursor(cx) } + + fn downgrade_item(&self) -> Box { + Box::new(self.downgrade()) + } } impl From> for AnyView { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aa16940998..c048bb84b8 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -18,6 +18,7 @@ use gpui::{ MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render, ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView, WindowContext, }; +use itertools::Itertools; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -114,6 +115,7 @@ actions!( ActivatePrevItem, ActivateNextItem, ActivateLastItem, + AlternateFile, CloseCleanItems, CloseItemsToTheLeft, CloseItemsToTheRight, @@ -183,6 +185,10 @@ impl fmt::Debug for Event { /// responsible for managing item tabs, focus and zoom states and drag and drop features. /// Can be split, see `PaneGroup` for more details. pub struct Pane { + alternate_file_items: ( + Option>, + Option>, + ), focus_handle: FocusHandle, items: Vec>, activation_history: Vec, @@ -286,6 +292,7 @@ impl Pane { let handle = cx.view().downgrade(); Self { + alternate_file_items: (None, None), focus_handle, items: Vec::new(), activation_history: Vec::new(), @@ -390,6 +397,39 @@ impl Pane { } } + fn alternate_file(&mut self, cx: &mut ViewContext) { + let (_, alternative) = &self.alternate_file_items; + if let Some(alternative) = alternative { + let existing = self + .items() + .find_position(|item| item.item_id() == alternative.id()); + if let Some((ix, _)) = existing { + self.activate_item(ix, true, true, cx); + } else { + if let Some(upgraded) = alternative.upgrade() { + self.add_item(upgraded, true, true, None, cx); + } + } + } + } + + pub fn track_alternate_file_items(&mut self) { + if let Some(item) = self.active_item().map(|item| item.downgrade_item()) { + let (current, _) = &self.alternate_file_items; + match current { + Some(current) => { + if current.id() != item.id() { + self.alternate_file_items = + (Some(item), self.alternate_file_items.0.take()); + } + } + None => { + self.alternate_file_items = (Some(item), None); + } + } + } + } + pub fn has_focus(&self, cx: &WindowContext) -> bool { // We not only check whether our focus handle contains focus, but also // whether the active_item might have focus, because we might have just activated an item @@ -1981,6 +2021,9 @@ impl Render for Pane { .size_full() .flex_none() .overflow_hidden() + .on_action(cx.listener(|pane, _: &AlternateFile, cx| { + pane.alternate_file(cx); + })) .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx))) .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx))) .on_action( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ac84ac8d4c..ede10867c4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2499,6 +2499,9 @@ impl Workspace { self.zoomed_position = None; cx.emit(Event::ZoomChanged); self.update_active_view_for_followers(cx); + pane.model.update(cx, |pane, _| { + pane.track_alternate_file_items(); + }); cx.notify(); } @@ -2516,6 +2519,9 @@ impl Workspace { } pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::ActivateItem { local } => { + pane.model.update(cx, |pane, _| { + pane.track_alternate_file_items(); + }); if *local { self.unfollow(&pane, cx); }