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 <conrad.irwin@gmail.com>
This commit is contained in:
Robert Falkén 2024-05-10 19:40:08 +02:00 committed by GitHub
parent 0d26beb91b
commit 80d3eafa30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 0 deletions

View File

@ -1,4 +1,10 @@
[ [
{
"context": "ProjectPanel || Editor",
"bindings": {
"ctrl-6": "pane::AlternateFile"
}
},
{ {
"context": "Editor && VimControl && !VimWaiting && !menu", "context": "Editor && VimControl && !VimWaiting && !menu",
"bindings": { "bindings": {

View File

@ -330,6 +330,7 @@ pub trait ItemHandle: 'static + Send {
fn serialized_item_kind(&self) -> Option<&'static str>; fn serialized_item_kind(&self) -> Option<&'static str>;
fn show_toolbar(&self, cx: &AppContext) -> bool; fn show_toolbar(&self, cx: &AppContext) -> bool;
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>; fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
} }
pub trait WeakItemHandle: Send + Sync { pub trait WeakItemHandle: Send + Sync {
@ -702,6 +703,10 @@ impl<T: Item> ItemHandle for View<T> {
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> { fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
self.read(cx).pixel_position_of_cursor(cx) self.read(cx).pixel_position_of_cursor(cx)
} }
fn downgrade_item(&self) -> Box<dyn WeakItemHandle> {
Box::new(self.downgrade())
}
} }
impl From<Box<dyn ItemHandle>> for AnyView { impl From<Box<dyn ItemHandle>> for AnyView {

View File

@ -18,6 +18,7 @@ use gpui::{
MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render, ScrollHandle, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render, ScrollHandle,
Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView, WindowContext, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView, WindowContext,
}; };
use itertools::Itertools;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{Project, ProjectEntryId, ProjectPath}; use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize; use serde::Deserialize;
@ -114,6 +115,7 @@ actions!(
ActivatePrevItem, ActivatePrevItem,
ActivateNextItem, ActivateNextItem,
ActivateLastItem, ActivateLastItem,
AlternateFile,
CloseCleanItems, CloseCleanItems,
CloseItemsToTheLeft, CloseItemsToTheLeft,
CloseItemsToTheRight, CloseItemsToTheRight,
@ -183,6 +185,10 @@ impl fmt::Debug for Event {
/// responsible for managing item tabs, focus and zoom states and drag and drop features. /// responsible for managing item tabs, focus and zoom states and drag and drop features.
/// Can be split, see `PaneGroup` for more details. /// Can be split, see `PaneGroup` for more details.
pub struct Pane { pub struct Pane {
alternate_file_items: (
Option<Box<dyn WeakItemHandle>>,
Option<Box<dyn WeakItemHandle>>,
),
focus_handle: FocusHandle, focus_handle: FocusHandle,
items: Vec<Box<dyn ItemHandle>>, items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<EntityId>, activation_history: Vec<EntityId>,
@ -286,6 +292,7 @@ impl Pane {
let handle = cx.view().downgrade(); let handle = cx.view().downgrade();
Self { Self {
alternate_file_items: (None, None),
focus_handle, focus_handle,
items: Vec::new(), items: Vec::new(),
activation_history: Vec::new(), activation_history: Vec::new(),
@ -390,6 +397,39 @@ impl Pane {
} }
} }
fn alternate_file(&mut self, cx: &mut ViewContext<Pane>) {
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 { pub fn has_focus(&self, cx: &WindowContext) -> bool {
// We not only check whether our focus handle contains focus, but also // 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 // 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() .size_full()
.flex_none() .flex_none()
.overflow_hidden() .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, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
.on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx))) .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
.on_action( .on_action(

View File

@ -2499,6 +2499,9 @@ impl Workspace {
self.zoomed_position = None; self.zoomed_position = None;
cx.emit(Event::ZoomChanged); cx.emit(Event::ZoomChanged);
self.update_active_view_for_followers(cx); self.update_active_view_for_followers(cx);
pane.model.update(cx, |pane, _| {
pane.track_alternate_file_items();
});
cx.notify(); cx.notify();
} }
@ -2516,6 +2519,9 @@ impl Workspace {
} }
pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::Remove => self.remove_pane(pane, cx),
pane::Event::ActivateItem { local } => { pane::Event::ActivateItem { local } => {
pane.model.update(cx, |pane, _| {
pane.track_alternate_file_items();
});
if *local { if *local {
self.unfollow(&pane, cx); self.unfollow(&pane, cx);
} }