From 80d3eafa303cc4921cadcddf59f263e23f132da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Falk=C3=A9n?= Date: Fri, 10 May 2024 19:40:08 +0200 Subject: [PATCH] Alternate files with ctrl-6 (#11367) This is my stab at #7709 I realize the code is flawed. There's no test coverage, I'm using `clone()` and there are probably better ways to hook into the events. Also, I didn't know what context to use for the keybinding. But maybe with some pointers from someone who actually know what they're doing, I can get this shippable. Release Notes: - vim: Added ctrl-6 for [alternate-file](https://vimhelp.org/editing.txt.html#CTRL-%5E) to navigate back and forth between two buffers. https://github.com/zed-industries/zed/assets/261929/2d10494e-5668-4988-b7b4-417c922d6c61 --------- Co-authored-by: Conrad Irwin --- assets/keymaps/vim.json | 6 +++++ crates/workspace/src/item.rs | 5 ++++ crates/workspace/src/pane.rs | 43 +++++++++++++++++++++++++++++++ crates/workspace/src/workspace.rs | 6 +++++ 4 files changed, 60 insertions(+) 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); }