From bbf634f439b8a508893cb6ad2d38cbf475ecd8ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Jan 2022 16:59:06 +0100 Subject: [PATCH] Start laying the foundation for a per-pane navigation system --- crates/editor/src/editor.rs | 6 +- crates/editor/src/items.rs | 18 ++++-- crates/workspace/Cargo.toml | 2 +- crates/workspace/src/pane.rs | 92 +++++++++++++++++++++++++++++-- crates/workspace/src/workspace.rs | 33 +++++++++-- 5 files changed, 135 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 898f1fce42..0781fb1e0e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -41,6 +41,7 @@ use std::{ iter::{self, FromIterator}, mem, ops::{Deref, Range, RangeInclusive, Sub}, + rc::Rc, sync::Arc, time::{Duration, Instant}, }; @@ -48,7 +49,7 @@ use sum_tree::Bias; use text::rope::TextDimension; use theme::{DiagnosticStyle, EditorStyle}; use util::post_inc; -use workspace::{PathOpener, Workspace}; +use workspace::{Navigation, PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; @@ -377,6 +378,7 @@ pub struct Editor { mode: EditorMode, placeholder_text: Option>, highlighted_rows: Option>, + navigation: Option>, } pub struct EditorSnapshot { @@ -457,6 +459,7 @@ impl Editor { let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx); clone.scroll_position = self.scroll_position; clone.scroll_top_anchor = self.scroll_top_anchor.clone(); + clone.navigation = self.navigation.clone(); clone } @@ -506,6 +509,7 @@ impl Editor { mode: EditorMode::Full, placeholder_text: None, highlighted_rows: None, + navigation: None, }; let selection = Selection { id: post_inc(&mut this.next_selection_id), diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8abcc76ef9..9b64a03567 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -10,11 +10,12 @@ use postage::watch; use project::{File, ProjectPath, Worktree}; use std::fmt::Write; use std::path::Path; +use std::rc::Rc; use text::{Point, Selection}; use util::TryFutureExt; use workspace::{ - ItemHandle, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView, WeakItemHandle, - Workspace, + ItemHandle, ItemView, ItemViewHandle, Navigation, PathOpener, Settings, StatusItemView, + WeakItemHandle, Workspace, }; pub struct BufferOpener; @@ -46,16 +47,19 @@ impl ItemHandle for BufferItemHandle { &self, window_id: usize, workspace: &Workspace, + navigation: Rc, cx: &mut MutableAppContext, ) -> Box { let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); let weak_buffer = buffer.downgrade(); Box::new(cx.add_view(window_id, |cx| { - Editor::for_buffer( + let mut editor = Editor::for_buffer( buffer, crate::settings_builder(weak_buffer, workspace.settings()), cx, - ) + ); + editor.navigation = Some(navigation); + editor })) } @@ -129,6 +133,12 @@ impl ItemView for Editor { Some(self.clone(cx)) } + fn activated(&mut self, cx: &mut ViewContext) { + if let Some(navigation) = self.navigation.as_ref() { + navigation.push::<(), _>(None, cx); + } + } + fn is_dirty(&self, cx: &AppContext) -> bool { self.buffer().read(cx).read(cx).is_dirty() } diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index e85fefdabd..74a9aea733 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "workspace" version = "0.1.0" -edition = "2018" +edition = "2021" [lib] path = "src/workspace.rs" diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e1024b9a22..bc4b71c288 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,5 +1,6 @@ use super::{ItemViewHandle, SplitDirection}; -use crate::{ItemHandle, Settings, Workspace}; +use crate::{ItemHandle, ItemView, Settings, WeakItemViewHandle, Workspace}; +use collections::HashMap; use gpui::{ action, elements::*, @@ -9,7 +10,8 @@ use gpui::{ Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, }; use postage::watch; -use std::cmp; +use project::ProjectPath; +use std::{any::Any, cell::RefCell, cmp, rc::Rc}; action!(Split, SplitDirection); action!(ActivateItem, usize); @@ -17,6 +19,8 @@ action!(ActivatePrevItem); action!(ActivateNextItem); action!(CloseActiveItem); action!(CloseItem, usize); +action!(GoBack); +action!(GoForward); pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { @@ -37,6 +41,8 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &Split, cx| { pane.split(action.0, cx); }); + cx.add_action(Pane::go_back); + cx.add_action(Pane::go_forward); cx.add_bindings(vec![ Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")), @@ -46,6 +52,8 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")), Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")), Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")), + Binding::new("ctrl-", GoBack, Some("Pane")), + Binding::new("ctrl-shift-_", GoForward, Some("Pane")), ]); } @@ -61,6 +69,22 @@ pub struct Pane { item_views: Vec<(usize, Box)>, active_item: usize, settings: watch::Receiver, + navigation: Rc, +} + +#[derive(Default)] +pub struct Navigation(RefCell); + +#[derive(Default)] +struct NavigationHistory { + backward_stack: Vec, + forward_stack: Vec, + paths_by_item: HashMap, +} + +struct NavigationEntry { + item_view: Box, + data: Option>, } impl Pane { @@ -69,6 +93,7 @@ impl Pane { item_views: Vec::new(), active_item: 0, settings, + navigation: Default::default(), } } @@ -76,6 +101,16 @@ impl Pane { cx.emit(Event::Activate); } + pub fn go_back(&mut self, _: &GoBack, cx: &mut ViewContext) { + let mut navigation = self.navigation.0.borrow_mut(); + if let Some(entry) = navigation.go_back() {} + } + + pub fn go_forward(&mut self, _: &GoForward, cx: &mut ViewContext) { + let mut navigation = self.navigation.0.borrow_mut(); + if let Some(entry) = navigation.go_forward() {} + } + pub fn open_item( &mut self, item_handle: T, @@ -93,14 +128,15 @@ impl Pane { } } - let item_view = item_handle.add_view(cx.window_id(), workspace, cx); + let item_view = + item_handle.add_view(cx.window_id(), workspace, self.navigation.clone(), cx); self.add_item_view(item_view.boxed_clone(), cx); item_view } pub fn add_item_view( &mut self, - item_view: Box, + mut item_view: Box, cx: &mut ViewContext, ) { item_view.added_to_pane(cx); @@ -142,6 +178,7 @@ impl Pane { if index < self.item_views.len() { self.active_item = index; self.focus_active_item(cx); + self.item_views[index].1.activated(cx); cx.notify(); } } @@ -172,8 +209,21 @@ impl Pane { } } - pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext) { - self.item_views.retain(|(_, item)| item.id() != item_id); + pub fn close_item(&mut self, item_view_id: usize, cx: &mut ViewContext) { + self.item_views.retain(|(item_id, item)| { + if item.id() == item_view_id { + let mut navigation = self.navigation.0.borrow_mut(); + if let Some(path) = item.project_path(cx) { + navigation.paths_by_item.insert(*item_id, path); + } else { + navigation.paths_by_item.remove(item_id); + } + + false + } else { + true + } + }); self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1)); if self.item_views.is_empty() { cx.emit(Event::Remove); @@ -369,3 +419,33 @@ impl View for Pane { self.focus_active_item(cx); } } + +impl Navigation { + pub fn push(&self, data: Option, cx: &mut ViewContext) { + let mut state = self.0.borrow_mut(); + state.backward_stack.push(NavigationEntry { + item_view: Box::new(cx.weak_handle()), + data: data.map(|data| Box::new(data) as Box), + }); + } +} + +impl NavigationHistory { + fn go_back(&mut self) -> Option<&NavigationEntry> { + if let Some(backward) = self.backward_stack.pop() { + self.forward_stack.push(backward); + self.forward_stack.last() + } else { + None + } + } + + fn go_forward(&mut self) -> Option<&NavigationEntry> { + if let Some(forward) = self.forward_stack.pop() { + self.backward_stack.push(forward); + self.backward_stack.last() + } else { + None + } + } +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index fe540186a9..552eb4629d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -36,6 +36,7 @@ use std::{ future::Future, hash::{Hash, Hasher}, path::{Path, PathBuf}, + rc::Rc, sync::Arc, }; use theme::{Theme, ThemeRegistry}; @@ -135,6 +136,7 @@ pub trait Item: Entity + Sized { fn build_view( handle: ModelHandle, workspace: &Workspace, + navigation: Rc, cx: &mut ViewContext, ) -> Self::View; @@ -144,6 +146,8 @@ pub trait Item: Entity + Sized { pub trait ItemView: View { type ItemHandle: ItemHandle; + fn added_to_pane(&mut self, _: Rc, _: &mut ViewContext) {} + fn activated(&mut self, _: &mut ViewContext) {} fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn title(&self, cx: &AppContext) -> String; fn project_path(&self, cx: &AppContext) -> Option; @@ -185,6 +189,7 @@ pub trait ItemHandle: Send + Sync { &self, window_id: usize, workspace: &Workspace, + navigation: Rc, cx: &mut MutableAppContext, ) -> Box; fn boxed_clone(&self) -> Box; @@ -204,7 +209,8 @@ pub trait ItemViewHandle { fn project_path(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; - fn added_to_pane(&self, cx: &mut ViewContext); + fn added_to_pane(&mut self, cx: &mut ViewContext); + fn activated(&mut self, cx: &mut MutableAppContext); fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; fn is_dirty(&self, cx: &AppContext) -> bool; @@ -220,6 +226,10 @@ pub trait ItemViewHandle { ) -> Task>; } +pub trait WeakItemViewHandle { + fn upgrade(&self, cx: &AppContext) -> Option>; +} + impl ItemHandle for ModelHandle { fn id(&self) -> usize { self.id() @@ -229,9 +239,12 @@ impl ItemHandle for ModelHandle { &self, window_id: usize, workspace: &Workspace, + navigation: Rc, cx: &mut MutableAppContext, ) -> Box { - Box::new(cx.add_view(window_id, |cx| T::build_view(self.clone(), workspace, cx))) + Box::new(cx.add_view(window_id, |cx| { + T::build_view(self.clone(), workspace, navigation, cx) + })) } fn boxed_clone(&self) -> Box { @@ -260,9 +273,10 @@ impl ItemHandle for Box { &self, window_id: usize, workspace: &Workspace, + navigation: Rc, cx: &mut MutableAppContext, ) -> Box { - ItemHandle::add_view(self.as_ref(), window_id, workspace, cx) + ItemHandle::add_view(self.as_ref(), window_id, workspace, navigation, cx) } fn boxed_clone(&self) -> Box { @@ -330,7 +344,7 @@ impl ItemViewHandle for ViewHandle { .map(|handle| Box::new(handle) as Box) } - fn added_to_pane(&self, cx: &mut ViewContext) { + fn added_to_pane(&mut self, cx: &mut ViewContext) { cx.subscribe(self, |pane, item, event, cx| { if T::should_close_item_on_event(event) { pane.close_item(item.id(), cx); @@ -349,6 +363,10 @@ impl ItemViewHandle for ViewHandle { .detach(); } + fn activated(&mut self, cx: &mut MutableAppContext) { + self.update(cx, |this, cx| this.activated(cx)); + } + fn save(&self, cx: &mut MutableAppContext) -> Result>> { self.update(cx, |item, cx| item.save(cx)) } @@ -399,6 +417,13 @@ impl Clone for Box { } } +impl WeakItemViewHandle for WeakViewHandle { + fn upgrade(&self, cx: &AppContext) -> Option> { + self.upgrade(cx) + .map(|v| Box::new(v) as Box) + } +} + #[derive(Clone)] pub struct WorkspaceParams { pub project: ModelHandle,