From cd0d1d3340008b7da603324c722fd67c9df6cb6c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Jan 2022 15:27:24 +0100 Subject: [PATCH 01/16] Delete unused `pane::State` struct --- crates/workspace/src/pane.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 731db29d63..e1024b9a22 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -57,17 +57,6 @@ pub enum Event { const MAX_TAB_TITLE_LEN: usize = 24; -#[derive(Debug, Eq, PartialEq)] -pub struct State { - pub tabs: Vec, -} - -#[derive(Debug, Eq, PartialEq)] -pub struct TabState { - pub title: String, - pub active: bool, -} - pub struct Pane { item_views: Vec<(usize, Box)>, active_item: usize, From bbf634f439b8a508893cb6ad2d38cbf475ecd8ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Jan 2022 16:59:06 +0100 Subject: [PATCH 02/16] 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, From ea624c6cde8215ba6de7b11de8489d3e26f23643 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Jan 2022 18:57:31 +0100 Subject: [PATCH 03/16] Populate backward/forward stacks upon item deactivation Co-Authored-By: Max Brunsfeld --- crates/diagnostics/src/diagnostics.rs | 5 +- crates/editor/src/items.rs | 2 +- crates/workspace/src/pane.rs | 162 ++++++++++++++++++-------- crates/workspace/src/workspace.rs | 8 +- 4 files changed, 121 insertions(+), 56 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 47bea529c1..054074fb39 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -15,9 +15,9 @@ use gpui::{ use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath, WorktreeId}; -use std::{cmp::Ordering, mem, ops::Range, sync::Arc}; +use std::{cmp::Ordering, mem, ops::Range, rc::Rc, sync::Arc}; use util::TryFutureExt; -use workspace::Workspace; +use workspace::{Navigation, Workspace}; action!(Deploy); action!(OpenExcerpts); @@ -522,6 +522,7 @@ impl workspace::Item for ProjectDiagnostics { fn build_view( handle: ModelHandle, workspace: &Workspace, + _: Rc, cx: &mut ViewContext, ) -> Self::View { ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), workspace.settings(), cx) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 9b64a03567..015b4b84b6 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -133,7 +133,7 @@ impl ItemView for Editor { Some(self.clone(cx)) } - fn activated(&mut self, cx: &mut ViewContext) { + fn deactivated(&mut self, cx: &mut ViewContext) { if let Some(navigation) = self.navigation.as_ref() { navigation.push::<(), _>(None, cx); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index bc4b71c288..c7ea004956 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -11,7 +11,7 @@ use gpui::{ }; use postage::watch; use project::ProjectPath; -use std::{any::Any, cell::RefCell, cmp, rc::Rc}; +use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; action!(Split, SplitDirection); action!(ActivateItem, usize); @@ -52,8 +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")), + Binding::new("ctrl--", GoBack, Some("Pane")), + Binding::new("shift-ctrl-_", GoForward, Some("Pane")), ]); } @@ -67,7 +67,7 @@ const MAX_TAB_TITLE_LEN: usize = 24; pub struct Pane { item_views: Vec<(usize, Box)>, - active_item: usize, + active_item_index: usize, settings: watch::Receiver, navigation: Rc, } @@ -77,11 +77,24 @@ pub struct Navigation(RefCell); #[derive(Default)] struct NavigationHistory { + mode: NavigationHistoryMode, backward_stack: Vec, forward_stack: Vec, paths_by_item: HashMap, } +enum NavigationHistoryMode { + Normal, + GoingBack, + GoingForward, +} + +impl Default for NavigationHistoryMode { + fn default() -> Self { + Self::Normal + } +} + struct NavigationEntry { item_view: Box, data: Option>, @@ -91,7 +104,7 @@ impl Pane { pub fn new(settings: watch::Receiver) -> Self { Self { item_views: Vec::new(), - active_item: 0, + active_item_index: 0, settings, navigation: Default::default(), } @@ -102,13 +115,55 @@ impl Pane { } pub fn go_back(&mut self, _: &GoBack, cx: &mut ViewContext) { + if self.navigation.0.borrow().backward_stack.is_empty() { + return; + } + + if let Some(item_view) = self.active_item() { + self.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingBack; + item_view.deactivated(cx); + self.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; + } + let mut navigation = self.navigation.0.borrow_mut(); - if let Some(entry) = navigation.go_back() {} + if let Some(entry) = navigation.backward_stack.pop() { + if let Some(index) = entry + .item_view + .upgrade(cx) + .and_then(|v| self.index_for_item_view(v.as_ref())) + { + self.active_item_index = index; + drop(navigation); + self.focus_active_item(cx); + cx.notify(); + } + } } pub fn go_forward(&mut self, _: &GoForward, cx: &mut ViewContext) { + if self.navigation.0.borrow().forward_stack.is_empty() { + return; + } + + if let Some(item_view) = self.active_item() { + self.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingForward; + item_view.deactivated(cx); + self.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; + } + let mut navigation = self.navigation.0.borrow_mut(); - if let Some(entry) = navigation.go_forward() {} + if let Some(entry) = navigation.forward_stack.pop() { + if let Some(index) = entry + .item_view + .upgrade(cx) + .and_then(|v| self.index_for_item_view(v.as_ref())) + { + self.active_item_index = index; + drop(navigation); + self.focus_active_item(cx); + cx.notify(); + } + } } pub fn open_item( @@ -140,7 +195,7 @@ impl Pane { cx: &mut ViewContext, ) { item_view.added_to_pane(cx); - let item_idx = cmp::min(self.active_item + 1, self.item_views.len()); + let item_idx = cmp::min(self.active_item_index + 1, self.item_views.len()); self.item_views .insert(item_idx, (item_view.item_handle(cx).id(), item_view)); self.activate_item(item_idx, cx); @@ -160,7 +215,7 @@ impl Pane { pub fn active_item(&self) -> Option> { self.item_views - .get(self.active_item) + .get(self.active_item_index) .map(|(_, view)| view.clone()) } @@ -176,40 +231,43 @@ impl Pane { pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext) { if index < self.item_views.len() { - self.active_item = index; - self.focus_active_item(cx); - self.item_views[index].1.activated(cx); - cx.notify(); + let prev_active_item_ix = mem::replace(&mut self.active_item_index, index); + if prev_active_item_ix != self.active_item_index { + self.item_views[prev_active_item_ix].1.deactivated(cx); + self.focus_active_item(cx); + cx.notify(); + } } } pub fn activate_prev_item(&mut self, cx: &mut ViewContext) { - if self.active_item > 0 { - self.active_item -= 1; + let mut index = self.active_item_index; + if index > 0 { + index -= 1; } else if self.item_views.len() > 0 { - self.active_item = self.item_views.len() - 1; + index = self.item_views.len() - 1; } - self.focus_active_item(cx); - cx.notify(); + self.activate_item(index, cx); } pub fn activate_next_item(&mut self, cx: &mut ViewContext) { - if self.active_item + 1 < self.item_views.len() { - self.active_item += 1; + let mut index = self.active_item_index; + if index + 1 < self.item_views.len() { + index += 1; } else { - self.active_item = 0; + index = 0; } - self.focus_active_item(cx); - cx.notify(); + self.activate_item(index, cx); } pub fn close_active_item(&mut self, cx: &mut ViewContext) { if !self.item_views.is_empty() { - self.close_item(self.item_views[self.active_item].1.id(), cx) + self.close_item(self.item_views[self.active_item_index].1.id(), cx) } } pub fn close_item(&mut self, item_view_id: usize, cx: &mut ViewContext) { + let mut item_ix = 0; self.item_views.retain(|(item_id, item)| { if item.id() == item_view_id { let mut navigation = self.navigation.0.borrow_mut(); @@ -219,12 +277,21 @@ impl Pane { navigation.paths_by_item.remove(item_id); } + if item_ix == self.active_item_index { + item.deactivated(cx); + } + item_ix += 1; false } else { + item_ix += 1; true } }); - self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1)); + self.active_item_index = cmp::min( + self.active_item_index, + self.item_views.len().saturating_sub(1), + ); + if self.item_views.is_empty() { cx.emit(Event::Remove); } @@ -249,7 +316,7 @@ impl Pane { let tabs = MouseEventHandler::new::(cx.view_id(), cx, |mouse_state, cx| { let mut row = Flex::row(); for (ix, (_, item_view)) in self.item_views.iter().enumerate() { - let is_active = ix == self.active_item; + let is_active = ix == self.active_item_index; row.add_child({ let mut title = item_view.title(cx); @@ -423,29 +490,26 @@ impl View for Pane { 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 + match state.mode { + NavigationHistoryMode::Normal => { + state.backward_stack.push(NavigationEntry { + item_view: Box::new(cx.weak_handle()), + data: data.map(|data| Box::new(data) as Box), + }); + state.forward_stack.clear(); + } + NavigationHistoryMode::GoingBack => { + state.forward_stack.push(NavigationEntry { + item_view: Box::new(cx.weak_handle()), + data: data.map(|data| Box::new(data) as Box), + }); + } + NavigationHistoryMode::GoingForward => { + state.backward_stack.push(NavigationEntry { + item_view: Box::new(cx.weak_handle()), + data: data.map(|data| Box::new(data) as Box), + }); + } } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 552eb4629d..9f65062bf9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -147,7 +147,7 @@ pub trait ItemView: View { type ItemHandle: ItemHandle; fn added_to_pane(&mut self, _: Rc, _: &mut ViewContext) {} - fn activated(&mut self, _: &mut ViewContext) {} + fn deactivated(&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; @@ -210,7 +210,7 @@ pub trait ItemViewHandle { fn boxed_clone(&self) -> Box; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; fn added_to_pane(&mut self, cx: &mut ViewContext); - fn activated(&mut self, cx: &mut MutableAppContext); + fn deactivated(&self, cx: &mut MutableAppContext); fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; fn is_dirty(&self, cx: &AppContext) -> bool; @@ -363,8 +363,8 @@ impl ItemViewHandle for ViewHandle { .detach(); } - fn activated(&mut self, cx: &mut MutableAppContext) { - self.update(cx, |this, cx| this.activated(cx)); + fn deactivated(&self, cx: &mut MutableAppContext) { + self.update(cx, |this, cx| this.deactivated(cx)); } fn save(&self, cx: &mut MutableAppContext) -> Result>> { From e43d33cdad2eaca66e7c9a54fa520eb776e01715 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 17 Jan 2022 10:46:47 -0800 Subject: [PATCH 04/16] WIP - Reopen closed items when going back in nav history --- Cargo.lock | 1 + crates/workspace/Cargo.toml | 1 + crates/workspace/src/pane.rs | 77 ++++++++++++++++++++----------- crates/workspace/src/workspace.rs | 62 ++++++++++++++----------- 4 files changed, 87 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 119515de8b..03faaaf561 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5677,6 +5677,7 @@ dependencies = [ "project", "serde_json", "theme", + "util", ] [[package]] diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 74a9aea733..f3ea330ca3 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -17,6 +17,7 @@ gpui = { path = "../gpui" } language = { path = "../language" } project = { path = "../project" } theme = { path = "../theme" } +util = { path = "../util" } anyhow = "1.0.38" log = "0.4" parking_lot = "0.11.1" diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c7ea004956..6fb35ba786 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -12,6 +12,7 @@ use gpui::{ use postage::watch; use project::ProjectPath; use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; +use util::TryFutureExt; action!(Split, SplitDirection); action!(ActivateItem, usize); @@ -114,29 +115,51 @@ impl Pane { cx.emit(Event::Activate); } - pub fn go_back(&mut self, _: &GoBack, cx: &mut ViewContext) { - if self.navigation.0.borrow().backward_stack.is_empty() { - return; - } + pub fn go_back(workspace: &mut Workspace, _: &GoBack, cx: &mut ViewContext) { + let project_path = workspace.active_pane().update(cx, |pane, cx| { + let mut navigation = pane.navigation.0.borrow_mut(); + if let Some(entry) = navigation.backward_stack.pop() { + if let Some(index) = entry + .item_view + .upgrade(cx) + .and_then(|v| pane.index_for_item_view(v.as_ref())) + { + if let Some(item_view) = pane.active_item() { + pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingBack; + item_view.deactivated(cx); + pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; + } - if let Some(item_view) = self.active_item() { - self.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingBack; - item_view.deactivated(cx); - self.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; - } - - let mut navigation = self.navigation.0.borrow_mut(); - if let Some(entry) = navigation.backward_stack.pop() { - if let Some(index) = entry - .item_view - .upgrade(cx) - .and_then(|v| self.index_for_item_view(v.as_ref())) - { - self.active_item_index = index; - drop(navigation); - self.focus_active_item(cx); - cx.notify(); + pane.active_item_index = index; + drop(navigation); + pane.focus_active_item(cx); + cx.notify(); + } else { + return navigation.paths_by_item.get(&entry.item_view.id()).cloned(); + } } + + None + }); + + if let Some(project_path) = project_path { + let task = workspace.load_path(project_path, cx); + cx.spawn(|workspace, mut cx| { + async move { + let item = task.await?; + workspace.update(&mut cx, |workspace, cx| { + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, cx| { + pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingBack; + pane.open_item(item, workspace, cx); + pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; + }); + }); + Ok(()) + } + .log_err() + }) + .detach(); } } @@ -268,17 +291,17 @@ impl Pane { pub fn close_item(&mut self, item_view_id: usize, cx: &mut ViewContext) { let mut item_ix = 0; - self.item_views.retain(|(item_id, item)| { - if item.id() == item_view_id { + self.item_views.retain(|(_, item_view)| { + if item_view.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); + if let Some(path) = item_view.project_path(cx) { + navigation.paths_by_item.insert(item_view.id(), path); } else { - navigation.paths_by_item.remove(item_id); + navigation.paths_by_item.remove(&item_view.id()); } if item_ix == self.active_item_index { - item.deactivated(cx); + item_view.deactivated(cx); } item_ix += 1; false diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9f65062bf9..99ef8764e8 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -227,6 +227,7 @@ pub trait ItemViewHandle { } pub trait WeakItemViewHandle { + fn id(&self) -> usize; fn upgrade(&self, cx: &AppContext) -> Option>; } @@ -418,6 +419,10 @@ impl Clone for Box { } impl WeakItemViewHandle for WeakViewHandle { + fn id(&self) -> usize { + self.id() + } + fn upgrade(&self, cx: &AppContext) -> Option> { self.upgrade(cx) .map(|v| Box::new(v) as Box) @@ -747,40 +752,15 @@ impl Workspace { } } - #[must_use] pub fn open_path( &mut self, path: ProjectPath, cx: &mut ViewContext, ) -> Task, Arc>> { - if let Some(existing_item) = self.item_for_path(&path, cx) { - return Task::ready(Ok(self.open_item(existing_item, cx))); - } - - let worktree = match self.project.read(cx).worktree_for_id(path.worktree_id, cx) { - Some(worktree) => worktree, - None => { - return Task::ready(Err(Arc::new(anyhow!( - "worktree {} does not exist", - path.worktree_id - )))); - } - }; - - let project_path = path.clone(); - let path_openers = self.path_openers.clone(); - let open_task = worktree.update(cx, |worktree, cx| { - for opener in path_openers.iter() { - if let Some(task) = opener.open(worktree, project_path.clone(), cx) { - return task; - } - } - Task::ready(Err(anyhow!("no opener found for path {:?}", project_path))) - }); - + let load_task = self.load_path(path, cx); let pane = self.active_pane().clone().downgrade(); cx.spawn(|this, mut cx| async move { - let item = open_task.await?; + let item = load_task.await?; this.update(&mut cx, |this, cx| { let pane = pane .upgrade(&cx) @@ -790,6 +770,34 @@ impl Workspace { }) } + pub fn load_path( + &mut self, + path: ProjectPath, + cx: &mut ViewContext, + ) -> Task>> { + if let Some(existing_item) = self.item_for_path(&path, cx) { + return Task::ready(Ok(existing_item)); + } + + let worktree = match self.project.read(cx).worktree_for_id(path.worktree_id, cx) { + Some(worktree) => worktree, + None => { + return Task::ready(Err(anyhow!("worktree {} does not exist", path.worktree_id))); + } + }; + + let project_path = path.clone(); + let path_openers = self.path_openers.clone(); + worktree.update(cx, |worktree, cx| { + for opener in path_openers.iter() { + if let Some(task) = opener.open(worktree, project_path.clone(), cx) { + return task; + } + } + Task::ready(Err(anyhow!("no opener found for path {:?}", project_path))) + }) + } + fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option> { self.items .iter() From 11b7270f68f271da25d380b601bdb612d94c45e7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 17 Jan 2022 16:01:44 -0800 Subject: [PATCH 05/16] Navigate to previous positions in editors when using navigation history --- crates/editor/src/editor.rs | 13 +++ crates/editor/src/items.rs | 14 +-- crates/workspace/src/pane.rs | 139 +++++++++++++++++------------- crates/workspace/src/workspace.rs | 7 ++ 4 files changed, 107 insertions(+), 66 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0781fb1e0e..eab88c24d0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2399,6 +2399,8 @@ impl Editor { return; } + self.push_to_navigation_history(cx); + let selection = Selection { id: post_inc(&mut self.next_selection_id), start: 0, @@ -2421,6 +2423,8 @@ impl Editor { return; } + self.push_to_navigation_history(cx); + let cursor = self.buffer.read(cx).read(cx).len(); let selection = Selection { id: post_inc(&mut self.next_selection_id), @@ -2432,6 +2436,15 @@ impl Editor { self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } + fn push_to_navigation_history(&self, cx: &mut ViewContext) { + if let Some(navigation) = &self.navigation { + if let Some(last_selection) = self.selections.iter().max_by_key(|s| s.id) { + let cursor = last_selection.head(); + navigation.push(Some(cursor), cx); + } + } + } + pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { let mut selection = self.local_selections::(cx).first().unwrap().clone(); selection.set_head(self.buffer.read(cx).read(cx).len()); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 015b4b84b6..5ff90201d5 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,5 +1,4 @@ -use crate::{Autoscroll, Editor, Event}; -use crate::{MultiBuffer, ToPoint as _}; +use crate::{Anchor, Autoscroll, Editor, Event, MultiBuffer, ToOffset, ToPoint as _}; use anyhow::Result; use gpui::{ elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, @@ -106,6 +105,13 @@ impl ItemView for Editor { BufferItemHandle(self.buffer.read(cx).as_singleton().unwrap()) } + fn navigate(&mut self, data: Box, cx: &mut ViewContext) { + if let Some(anchor) = data.downcast_ref::() { + let offset = anchor.to_offset(&self.buffer.read(cx).read(cx)); + self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx); + } + } + fn title(&self, cx: &AppContext) -> String { let filename = self .buffer() @@ -134,9 +140,7 @@ impl ItemView for Editor { } fn deactivated(&mut self, cx: &mut ViewContext) { - if let Some(navigation) = self.navigation.as_ref() { - navigation.push::<(), _>(None, cx); - } + self.push_to_navigation_history(cx); } fn is_dirty(&self, cx: &AppContext) -> bool { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6fb35ba786..f745298f6b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -84,6 +84,7 @@ struct NavigationHistory { paths_by_item: HashMap, } +#[derive(Copy, Clone)] enum NavigationHistoryMode { Normal, GoingBack, @@ -116,77 +117,81 @@ impl Pane { } pub fn go_back(workspace: &mut Workspace, _: &GoBack, cx: &mut ViewContext) { - let project_path = workspace.active_pane().update(cx, |pane, cx| { - let mut navigation = pane.navigation.0.borrow_mut(); - if let Some(entry) = navigation.backward_stack.pop() { - if let Some(index) = entry - .item_view - .upgrade(cx) - .and_then(|v| pane.index_for_item_view(v.as_ref())) - { - if let Some(item_view) = pane.active_item() { - pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingBack; - item_view.deactivated(cx); - pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; - } - - pane.active_item_index = index; - drop(navigation); - pane.focus_active_item(cx); - cx.notify(); - } else { - return navigation.paths_by_item.get(&entry.item_view.id()).cloned(); - } - } - - None - }); - - if let Some(project_path) = project_path { - let task = workspace.load_path(project_path, cx); - cx.spawn(|workspace, mut cx| { - async move { - let item = task.await?; - workspace.update(&mut cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - pane.update(cx, |pane, cx| { - pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingBack; - pane.open_item(item, workspace, cx); - pane.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; - }); - }); - Ok(()) - } - .log_err() - }) - .detach(); - } + Self::navigate_history(workspace, NavigationHistoryMode::GoingBack, cx); } - pub fn go_forward(&mut self, _: &GoForward, cx: &mut ViewContext) { - if self.navigation.0.borrow().forward_stack.is_empty() { - return; - } + pub fn go_forward(workspace: &mut Workspace, _: &GoForward, cx: &mut ViewContext) { + Self::navigate_history(workspace, NavigationHistoryMode::GoingForward, cx); + } - if let Some(item_view) = self.active_item() { - self.navigation.0.borrow_mut().mode = NavigationHistoryMode::GoingForward; - item_view.deactivated(cx); - self.navigation.0.borrow_mut().mode = NavigationHistoryMode::Normal; - } + fn navigate_history( + workspace: &mut Workspace, + mode: NavigationHistoryMode, + cx: &mut ViewContext, + ) -> Option<()> { + let (project_path, entry) = workspace.active_pane().update(cx, |pane, cx| { + // Retrieve the weak item handle from the history. + let entry = pane.navigation.pop(mode)?; - let mut navigation = self.navigation.0.borrow_mut(); - if let Some(entry) = navigation.forward_stack.pop() { + // If the item is still present in this pane, then activate it. if let Some(index) = entry .item_view .upgrade(cx) - .and_then(|v| self.index_for_item_view(v.as_ref())) + .and_then(|v| pane.index_for_item_view(v.as_ref())) { - self.active_item_index = index; - drop(navigation); - self.focus_active_item(cx); + if let Some(item_view) = pane.active_item() { + pane.navigation.set_mode(mode); + item_view.deactivated(cx); + pane.navigation.set_mode(NavigationHistoryMode::Normal); + } + + pane.active_item_index = index; + pane.focus_active_item(cx); + if let Some(data) = entry.data { + pane.active_item()?.navigate(data, cx); + } cx.notify(); + None } - } + // If the item is no longer present in this pane, then retrieve its + // project path in order to reopen it. + else { + pane.navigation + .0 + .borrow_mut() + .paths_by_item + .get(&entry.item_view.id()) + .cloned() + .map(|project_path| (project_path, entry)) + } + })?; + + // If the item was no longer present, then load it again from its previous path. + let task = workspace.load_path(project_path, cx); + cx.spawn(|workspace, mut cx| { + async move { + let item = task.await?; + workspace.update(&mut cx, |workspace, cx| { + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, cx| { + pane.navigation.set_mode(mode); + let item_view = pane.open_item(item, workspace, cx); + pane.navigation.set_mode(NavigationHistoryMode::Normal); + + if let Some(data) = entry.data { + item_view.navigate(data, cx); + } + + cx.notify(); + }); + }); + Ok(()) + } + .log_err() + }) + .detach(); + + None } pub fn open_item( @@ -511,6 +516,18 @@ impl View for Pane { } impl Navigation { + fn pop(&self, mode: NavigationHistoryMode) -> Option { + match mode { + NavigationHistoryMode::Normal => None, + NavigationHistoryMode::GoingBack => self.0.borrow_mut().backward_stack.pop(), + NavigationHistoryMode::GoingForward => self.0.borrow_mut().forward_stack.pop(), + } + } + + fn set_mode(&self, mode: NavigationHistoryMode) { + self.0.borrow_mut().mode = mode; + } + pub fn push(&self, data: Option, cx: &mut ViewContext) { let mut state = self.0.borrow_mut(); match state.mode { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 99ef8764e8..e7504db451 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -33,6 +33,7 @@ use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItem use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ + any::Any, future::Future, hash::{Hash, Hasher}, path::{Path, PathBuf}, @@ -148,6 +149,7 @@ pub trait ItemView: View { fn added_to_pane(&mut self, _: Rc, _: &mut ViewContext) {} fn deactivated(&mut self, _: &mut ViewContext) {} + fn navigate(&mut self, _: Box, _: &mut ViewContext) {} fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn title(&self, cx: &AppContext) -> String; fn project_path(&self, cx: &AppContext) -> Option; @@ -211,6 +213,7 @@ pub trait ItemViewHandle { fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; fn added_to_pane(&mut self, cx: &mut ViewContext); fn deactivated(&self, cx: &mut MutableAppContext); + fn navigate(&self, data: Box, cx: &mut MutableAppContext); fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; fn is_dirty(&self, cx: &AppContext) -> bool; @@ -368,6 +371,10 @@ impl ItemViewHandle for ViewHandle { self.update(cx, |this, cx| this.deactivated(cx)); } + fn navigate(&self, data: Box, cx: &mut MutableAppContext) { + self.update(cx, |this, cx| this.navigate(data, cx)); + } + fn save(&self, cx: &mut MutableAppContext) -> Result>> { self.update(cx, |item, cx| item.save(cx)) } From d480738cc59196378f84cb30b15276fa1c0f9386 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 09:58:40 +0100 Subject: [PATCH 06/16] Skip past entries in the navigation history that can't be re-opened --- crates/workspace/src/pane.rs | 116 ++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 49 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f745298f6b..b524fc8d56 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -7,12 +7,12 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f}, keymap::Binding, platform::CursorStyle, - Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, + Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext, ViewHandle, }; use postage::watch; use project::ProjectPath; use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; -use util::TryFutureExt; +use util::ResultExt; action!(Split, SplitDirection); action!(ActivateItem, usize); @@ -42,8 +42,12 @@ 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_action(|workspace: &mut Workspace, _: &GoBack, cx| { + Pane::go_back(workspace, cx).detach(); + }); + cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { + Pane::go_forward(workspace, cx).detach(); + }); cx.add_bindings(vec![ Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")), @@ -78,20 +82,20 @@ pub struct Navigation(RefCell); #[derive(Default)] struct NavigationHistory { - mode: NavigationHistoryMode, + mode: NavigationMode, backward_stack: Vec, forward_stack: Vec, paths_by_item: HashMap, } #[derive(Copy, Clone)] -enum NavigationHistoryMode { +enum NavigationMode { Normal, GoingBack, GoingForward, } -impl Default for NavigationHistoryMode { +impl Default for NavigationMode { fn default() -> Self { Self::Normal } @@ -116,20 +120,31 @@ impl Pane { cx.emit(Event::Activate); } - pub fn go_back(workspace: &mut Workspace, _: &GoBack, cx: &mut ViewContext) { - Self::navigate_history(workspace, NavigationHistoryMode::GoingBack, cx); + pub fn go_back(workspace: &mut Workspace, cx: &mut ViewContext) -> Task<()> { + Self::navigate_history( + workspace, + workspace.active_pane().clone(), + NavigationMode::GoingBack, + cx, + ) } - pub fn go_forward(workspace: &mut Workspace, _: &GoForward, cx: &mut ViewContext) { - Self::navigate_history(workspace, NavigationHistoryMode::GoingForward, cx); + pub fn go_forward(workspace: &mut Workspace, cx: &mut ViewContext) -> Task<()> { + Self::navigate_history( + workspace, + workspace.active_pane().clone(), + NavigationMode::GoingForward, + cx, + ) } fn navigate_history( workspace: &mut Workspace, - mode: NavigationHistoryMode, + pane: ViewHandle, + mode: NavigationMode, cx: &mut ViewContext, - ) -> Option<()> { - let (project_path, entry) = workspace.active_pane().update(cx, |pane, cx| { + ) -> Task<()> { + let to_load = pane.update(cx, |pane, cx| { // Retrieve the weak item handle from the history. let entry = pane.navigation.pop(mode)?; @@ -142,7 +157,7 @@ impl Pane { if let Some(item_view) = pane.active_item() { pane.navigation.set_mode(mode); item_view.deactivated(cx); - pane.navigation.set_mode(NavigationHistoryMode::Normal); + pane.navigation.set_mode(NavigationMode::Normal); } pane.active_item_index = index; @@ -164,34 +179,37 @@ impl Pane { .cloned() .map(|project_path| (project_path, entry)) } - })?; + }); - // If the item was no longer present, then load it again from its previous path. - let task = workspace.load_path(project_path, cx); - cx.spawn(|workspace, mut cx| { - async move { - let item = task.await?; - workspace.update(&mut cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); - pane.update(cx, |pane, cx| { - pane.navigation.set_mode(mode); - let item_view = pane.open_item(item, workspace, cx); - pane.navigation.set_mode(NavigationHistoryMode::Normal); + if let Some((project_path, entry)) = to_load { + // If the item was no longer present, then load it again from its previous path. + let pane = pane.downgrade(); + let task = workspace.load_path(project_path, cx); + cx.spawn(|workspace, mut cx| async move { + let item = task.await; + if let Some(pane) = cx.read(|cx| pane.upgrade(cx)) { + if let Some(item) = item.log_err() { + workspace.update(&mut cx, |workspace, cx| { + pane.update(cx, |p, _| p.navigation.set_mode(mode)); + let item_view = workspace.open_item_in_pane(item, &pane, cx); + pane.update(cx, |p, _| p.navigation.set_mode(NavigationMode::Normal)); - if let Some(data) = entry.data { - item_view.navigate(data, cx); - } - - cx.notify(); - }); - }); - Ok(()) - } - .log_err() - }) - .detach(); - - None + if let Some(data) = entry.data { + item_view.navigate(data, cx); + } + }); + } else { + workspace + .update(&mut cx, |workspace, cx| { + Self::navigate_history(workspace, pane, mode, cx) + }) + .await; + } + } + }) + } else { + Task::ready(()) + } } pub fn open_item( @@ -516,35 +534,35 @@ impl View for Pane { } impl Navigation { - fn pop(&self, mode: NavigationHistoryMode) -> Option { + fn pop(&self, mode: NavigationMode) -> Option { match mode { - NavigationHistoryMode::Normal => None, - NavigationHistoryMode::GoingBack => self.0.borrow_mut().backward_stack.pop(), - NavigationHistoryMode::GoingForward => self.0.borrow_mut().forward_stack.pop(), + NavigationMode::Normal => None, + NavigationMode::GoingBack => self.0.borrow_mut().backward_stack.pop(), + NavigationMode::GoingForward => self.0.borrow_mut().forward_stack.pop(), } } - fn set_mode(&self, mode: NavigationHistoryMode) { + fn set_mode(&self, mode: NavigationMode) { self.0.borrow_mut().mode = mode; } pub fn push(&self, data: Option, cx: &mut ViewContext) { let mut state = self.0.borrow_mut(); match state.mode { - NavigationHistoryMode::Normal => { + NavigationMode::Normal => { state.backward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), }); state.forward_stack.clear(); } - NavigationHistoryMode::GoingBack => { + NavigationMode::GoingBack => { state.forward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), }); } - NavigationHistoryMode::GoingForward => { + NavigationMode::GoingForward => { state.backward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), From d5acbe1e3218dd5b6d9dca28453fe46519bad9eb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 09:59:28 +0100 Subject: [PATCH 07/16] Use offset to restore navigation position if anchor can't be resolved --- crates/editor/src/editor.rs | 12 ++++++++++-- crates/editor/src/items.rs | 15 +++++++++++---- crates/editor/src/multi_buffer.rs | 12 ++++++++++++ crates/text/src/text.rs | 12 ++++++------ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index eab88c24d0..d5035299fa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -426,6 +426,11 @@ struct ClipboardSelection { is_entire_line: bool, } +pub struct NavigationData { + anchor: Anchor, + offset: usize, +} + impl Editor { pub fn single_line(build_settings: BuildSettings, cx: &mut ViewContext) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); @@ -2438,9 +2443,12 @@ impl Editor { fn push_to_navigation_history(&self, cx: &mut ViewContext) { if let Some(navigation) = &self.navigation { + let buffer = self.buffer.read(cx).read(cx); if let Some(last_selection) = self.selections.iter().max_by_key(|s| s.id) { - let cursor = last_selection.head(); - navigation.push(Some(cursor), cx); + let anchor = last_selection.head(); + let offset = anchor.to_offset(&buffer); + drop(buffer); + navigation.push(Some(NavigationData { anchor, offset }), cx); } } } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 5ff90201d5..13587495bc 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,10 +1,10 @@ -use crate::{Anchor, Autoscroll, Editor, Event, MultiBuffer, ToOffset, ToPoint as _}; +use crate::{Autoscroll, Editor, Event, MultiBuffer, NavigationData, ToOffset, ToPoint as _}; use anyhow::Result; use gpui::{ elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; -use language::{Buffer, Diagnostic, File as _}; +use language::{Bias, Buffer, Diagnostic, File as _}; use postage::watch; use project::{File, ProjectPath, Worktree}; use std::fmt::Write; @@ -106,8 +106,15 @@ impl ItemView for Editor { } fn navigate(&mut self, data: Box, cx: &mut ViewContext) { - if let Some(anchor) = data.downcast_ref::() { - let offset = anchor.to_offset(&self.buffer.read(cx).read(cx)); + if let Some(data) = data.downcast_ref::() { + let buffer = self.buffer.read(cx).read(cx); + let offset = if buffer.can_resolve(&data.anchor) { + data.anchor.to_offset(&buffer) + } else { + buffer.clip_offset(data.offset, Bias::Left) + }; + + drop(buffer); self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx); } } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 30020a0d55..7ae7b56867 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1545,6 +1545,18 @@ impl MultiBufferSnapshot { panic!("excerpt not found"); } + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { + true + } else if let Some((buffer_id, buffer_snapshot)) = + self.buffer_snapshot_for_excerpt(&anchor.excerpt_id) + { + anchor.buffer_id == buffer_id && buffer_snapshot.can_resolve(&anchor.text_anchor) + } else { + false + } + } + pub fn range_contains_excerpt_boundary(&self, range: Range) -> bool { let start = range.start.to_offset(self); let end = range.end.to_offset(self); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 895e6ad471..a762b1ca39 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1131,12 +1131,6 @@ impl Buffer { } } - pub fn can_resolve(&self, anchor: &Anchor) -> bool { - *anchor == Anchor::min() - || *anchor == Anchor::max() - || self.version.observed(anchor.timestamp) - } - pub fn peek_undo_stack(&self) -> Option<&Transaction> { self.history.undo_stack.last() } @@ -1648,6 +1642,12 @@ impl BufferSnapshot { } } + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + *anchor == Anchor::min() + || *anchor == Anchor::max() + || self.version.observed(anchor.timestamp) + } + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { self.visible_text.clip_offset(offset, bias) } From bb954e29cf384a6b92c8970a406551cc290162cf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 10:03:56 +0100 Subject: [PATCH 08/16] Introduce a test to verify navigation --- crates/editor/src/editor.rs | 4 +- crates/zed/src/zed.rs | 169 +++++++++++++++++++++++++++++++++--- 2 files changed, 158 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d5035299fa..e1cdb25180 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1138,8 +1138,8 @@ impl Editor { self.update_selections(selections, autoscroll, cx); } - #[cfg(test)] - fn select_display_ranges<'a, T>(&mut self, ranges: T, cx: &mut ViewContext) + #[cfg(any(test, feature = "test-support"))] + pub fn select_display_ranges<'a, T>(&mut self, ranges: T, cx: &mut ViewContext) where T: IntoIterator>, { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 61300d1f56..1c250661cf 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -124,8 +124,8 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { #[cfg(test)] mod tests { use super::*; - use editor::Editor; - use gpui::MutableAppContext; + use editor::{DisplayPoint, Editor}; + use gpui::{MutableAppContext, TestAppContext, ViewHandle}; use project::ProjectPath; use serde_json::json; use std::{ @@ -136,11 +136,11 @@ mod tests { use theme::DEFAULT_THEME_NAME; use util::test::temp_tree; use workspace::{ - open_paths, pane, ItemView, ItemViewHandle, OpenNew, SplitDirection, WorkspaceHandle, + open_paths, pane, ItemView, ItemViewHandle, OpenNew, Pane, SplitDirection, WorkspaceHandle, }; #[gpui::test] - async fn test_open_paths_action(mut cx: gpui::TestAppContext) { + async fn test_open_paths_action(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); let dir = temp_tree(json!({ "a": { @@ -193,7 +193,7 @@ mod tests { } #[gpui::test] - async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { + async fn test_new_empty_workspace(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); cx.update(|cx| { workspace::init(cx); @@ -230,7 +230,7 @@ mod tests { } #[gpui::test] - async fn test_open_entry(mut cx: gpui::TestAppContext) { + async fn test_open_entry(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); app_state .fs @@ -350,7 +350,7 @@ mod tests { } #[gpui::test] - async fn test_open_paths(mut cx: gpui::TestAppContext) { + async fn test_open_paths(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); let fs = app_state.fs.as_fake(); fs.insert_dir("/dir1").await.unwrap(); @@ -420,7 +420,7 @@ mod tests { } #[gpui::test] - async fn test_save_conflicting_item(mut cx: gpui::TestAppContext) { + async fn test_save_conflicting_item(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); let fs = app_state.fs.as_fake(); fs.insert_tree("/root", json!({ "a.txt": "" })).await; @@ -469,7 +469,7 @@ mod tests { } #[gpui::test] - async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) { + async fn test_open_and_save_new_file(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); app_state.fs.as_fake().insert_dir("/root").await.unwrap(); let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); @@ -585,9 +585,7 @@ mod tests { } #[gpui::test] - async fn test_setting_language_when_saving_as_single_file_worktree( - mut cx: gpui::TestAppContext, - ) { + async fn test_setting_language_when_saving_as_single_file_worktree(mut cx: TestAppContext) { let app_state = cx.update(test_app_state); app_state.fs.as_fake().insert_dir("/root").await.unwrap(); let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); @@ -630,7 +628,7 @@ mod tests { } #[gpui::test] - async fn test_pane_actions(mut cx: gpui::TestAppContext) { + async fn test_pane_actions(mut cx: TestAppContext) { cx.update(|cx| pane::init(cx)); let app_state = cx.update(test_app_state); app_state @@ -693,6 +691,151 @@ mod tests { }); } + #[gpui::test] + async fn test_navigation(mut cx: TestAppContext) { + let app_state = cx.update(test_app_state); + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "a": { + "file1": "contents 1", + "file2": "contents 2", + "file3": "contents 3", + }, + }), + ) + .await; + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); + workspace + .update(&mut cx, |workspace, cx| { + workspace.add_worktree(Path::new("/root"), cx) + }) + .await + .unwrap(); + cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) + .await; + let entries = cx.read(|cx| workspace.file_project_paths(cx)); + let file1 = entries[0].clone(); + let file2 = entries[1].clone(); + let file3 = entries[2].clone(); + + let editor1 = workspace + .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) + .await + .unwrap() + .to_any() + .downcast::() + .unwrap(); + editor1.update(&mut cx, |editor, cx| { + editor.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)], cx); + }); + let editor2 = workspace + .update(&mut cx, |w, cx| w.open_path(file2.clone(), cx)) + .await + .unwrap() + .to_any() + .downcast::() + .unwrap(); + let editor3 = workspace + .update(&mut cx, |w, cx| w.open_path(file3.clone(), cx)) + .await + .unwrap() + .to_any() + .downcast::() + .unwrap(); + editor3.update(&mut cx, |editor, cx| { + editor.select_display_ranges(&[DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2)], cx); + }); + assert_eq!( + active_location(&workspace, &mut cx), + (file3.clone(), DisplayPoint::new(0, 2)) + ); + + workspace + .update(&mut cx, |w, cx| Pane::go_back(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file2.clone(), DisplayPoint::new(0, 0)) + ); + + workspace + .update(&mut cx, |w, cx| Pane::go_back(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file1.clone(), DisplayPoint::new(0, 1)) + ); + + // Go back one more time and ensure we don't navigate past the first item in the history. + workspace + .update(&mut cx, |w, cx| Pane::go_back(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file1.clone(), DisplayPoint::new(0, 1)) + ); + + workspace + .update(&mut cx, |w, cx| Pane::go_forward(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file2.clone(), DisplayPoint::new(0, 0)) + ); + + // Go forward to an item that has been closed, ensuring it gets re-opened at the same + // location. + workspace.update(&mut cx, |workspace, cx| { + workspace + .active_pane() + .update(cx, |pane, cx| pane.close_item(editor3.id(), cx)); + drop(editor3); + }); + workspace + .update(&mut cx, |w, cx| Pane::go_forward(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file3.clone(), DisplayPoint::new(0, 2)) + ); + + // Go back to an item that has been closed and removed from disk, ensuring it gets skipped. + workspace + .update(&mut cx, |workspace, cx| { + workspace + .active_pane() + .update(cx, |pane, cx| pane.close_item(editor2.id(), cx)); + drop(editor2); + app_state.fs.as_fake().remove(Path::new("/root/a/file2")) + }) + .await + .unwrap(); + workspace + .update(&mut cx, |w, cx| Pane::go_back(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file1.clone(), DisplayPoint::new(0, 1)) + ); + + fn active_location( + workspace: &ViewHandle, + cx: &mut TestAppContext, + ) -> (ProjectPath, DisplayPoint) { + workspace.update(cx, |workspace, cx| { + let item = workspace.active_item(cx).unwrap(); + let editor = item.to_any().downcast::().unwrap(); + let selections = editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)); + (item.project_path(cx).unwrap(), selections[0].start) + }) + } + } + #[gpui::test] fn test_bundled_themes(cx: &mut MutableAppContext) { let app_state = test_app_state(cx); From 4f472e14427ef2536690857b6fe81f965ac5cd87 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 10:10:16 +0100 Subject: [PATCH 09/16] :fire: --- crates/workspace/src/workspace.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e7504db451..531d976fd5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -147,7 +147,6 @@ pub trait Item: Entity + Sized { pub trait ItemView: View { type ItemHandle: ItemHandle; - fn added_to_pane(&mut self, _: Rc, _: &mut ViewContext) {} fn deactivated(&mut self, _: &mut ViewContext) {} fn navigate(&mut self, _: Box, _: &mut ViewContext) {} fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; From b7091dd5e234cdf8549d8bba726f0cbe5d34ac59 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 10:13:50 +0100 Subject: [PATCH 10/16] Fix double borrow error when closing an item --- crates/workspace/src/pane.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index b524fc8d56..31e73be50d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -316,6 +316,10 @@ impl Pane { let mut item_ix = 0; self.item_views.retain(|(_, item_view)| { if item_view.id() == item_view_id { + if item_ix == self.active_item_index { + item_view.deactivated(cx); + } + let mut navigation = self.navigation.0.borrow_mut(); if let Some(path) = item_view.project_path(cx) { navigation.paths_by_item.insert(item_view.id(), path); @@ -323,9 +327,6 @@ impl Pane { navigation.paths_by_item.remove(&item_view.id()); } - if item_ix == self.active_item_index { - item_view.deactivated(cx); - } item_ix += 1; false } else { From 0cae3e0ac065d7ab7b77069120d8d39c0d0c4236 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 10:15:37 +0100 Subject: [PATCH 11/16] Ensure focus is transferred when opening the first item in pane --- crates/workspace/src/pane.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 31e73be50d..a93ebde051 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -280,9 +280,9 @@ impl Pane { let prev_active_item_ix = mem::replace(&mut self.active_item_index, index); if prev_active_item_ix != self.active_item_index { self.item_views[prev_active_item_ix].1.deactivated(cx); - self.focus_active_item(cx); - cx.notify(); } + self.focus_active_item(cx); + cx.notify(); } } From 16b82d59f13fd9cae4e2cb5a3af0a6d03e318cc3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 18 Jan 2022 18:03:10 +0100 Subject: [PATCH 12/16] Experiment with a more general way of pushing editor navigation entries --- crates/editor/src/editor.rs | 11 +++++++++++ crates/editor/src/items.rs | 2 ++ crates/workspace/src/pane.rs | 22 +++++++++++++++++++++- crates/zed/src/zed.rs | 33 ++++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e1cdb25180..ad7870738d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3297,6 +3297,17 @@ impl Editor { } self.pause_cursor_blinking(cx); + let prev_newest_selection = self.selections.iter().max_by_key(|s| s.id); + let curr_newest_selection = selections.iter().max_by_key(|s| s.id); + if let Some((prev_selection, curr_selection)) = + prev_newest_selection.zip(curr_newest_selection) + { + if prev_selection.head().to_offset(&buffer) != curr_selection.head().to_offset(&buffer) + { + self.push_to_navigation_history(cx); + } + } + self.set_selections( Arc::from_iter(selections.into_iter().map(|selection| { let end_bias = if selection.end > selection.start { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 13587495bc..54b9e19e2f 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -115,7 +115,9 @@ impl ItemView for Editor { }; drop(buffer); + let navigation = self.navigation.take(); self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx); + self.navigation = navigation; } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a93ebde051..614f8b3808 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -11,7 +11,13 @@ use gpui::{ }; use postage::watch; use project::ProjectPath; -use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; +use std::{ + any::Any, + cell::RefCell, + cmp, mem, + rc::Rc, + time::{Duration, Instant}, +}; use util::ResultExt; action!(Split, SplitDirection); @@ -104,6 +110,7 @@ impl Default for NavigationMode { struct NavigationEntry { item_view: Box, data: Option>, + insertion_time: Option, } impl Pane { @@ -551,9 +558,20 @@ impl Navigation { let mut state = self.0.borrow_mut(); match state.mode { NavigationMode::Normal => { + let mut insertion_time = Instant::now(); + if let Some(prev_insertion_time) = + state.backward_stack.last().and_then(|e| e.insertion_time) + { + if insertion_time.duration_since(prev_insertion_time) <= Duration::from_secs(2) + { + state.backward_stack.pop(); + insertion_time = prev_insertion_time; + } + } state.backward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), + insertion_time: Some(insertion_time), }); state.forward_stack.clear(); } @@ -561,12 +579,14 @@ impl Navigation { state.forward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), + insertion_time: None, }); } NavigationMode::GoingForward => { state.backward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), + insertion_time: None, }); } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1c250661cf..2e1588790a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -755,6 +755,14 @@ mod tests { (file3.clone(), DisplayPoint::new(0, 2)) ); + workspace + .update(&mut cx, |w, cx| Pane::go_back(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file3.clone(), DisplayPoint::new(0, 0)) + ); + workspace .update(&mut cx, |w, cx| Pane::go_back(w, cx)) .await; @@ -771,10 +779,26 @@ mod tests { (file1.clone(), DisplayPoint::new(0, 1)) ); + workspace + .update(&mut cx, |w, cx| Pane::go_back(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file1.clone(), DisplayPoint::new(0, 0)) + ); + // Go back one more time and ensure we don't navigate past the first item in the history. workspace .update(&mut cx, |w, cx| Pane::go_back(w, cx)) .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file1.clone(), DisplayPoint::new(0, 0)) + ); + + workspace + .update(&mut cx, |w, cx| Pane::go_forward(w, cx)) + .await; assert_eq!( active_location(&workspace, &mut cx), (file1.clone(), DisplayPoint::new(0, 1)) @@ -801,7 +825,7 @@ mod tests { .await; assert_eq!( active_location(&workspace, &mut cx), - (file3.clone(), DisplayPoint::new(0, 2)) + (file3.clone(), DisplayPoint::new(0, 0)) ); // Go back to an item that has been closed and removed from disk, ensuring it gets skipped. @@ -822,6 +846,13 @@ mod tests { active_location(&workspace, &mut cx), (file1.clone(), DisplayPoint::new(0, 1)) ); + workspace + .update(&mut cx, |w, cx| Pane::go_forward(w, cx)) + .await; + assert_eq!( + active_location(&workspace, &mut cx), + (file3.clone(), DisplayPoint::new(0, 0)) + ); fn active_location( workspace: &ViewHandle, From f7326b8d747cb58cd737c9ba64b5d6115ef00764 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Jan 2022 15:02:49 -0800 Subject: [PATCH 13/16] Push to navigation history based on jump distance instead of time --- crates/editor/src/editor.rs | 79 ++++++++++++++++++++++-------------- crates/editor/src/items.rs | 4 +- crates/workspace/src/pane.rs | 22 +--------- crates/zed/src/zed.rs | 18 ++++---- 4 files changed, 61 insertions(+), 62 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad7870738d..6b6bc68f50 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -53,6 +53,7 @@ use workspace::{Navigation, PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; +const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10; action!(Cancel); action!(Backspace); @@ -809,6 +810,8 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; + let newest_selection = self.newest_selection_internal().unwrap().clone(); + let start; let end; let mode; @@ -843,6 +846,8 @@ impl Editor { } } + self.push_to_navigation_history(newest_selection.head(), Some(end.to_point(&buffer)), cx); + let selection = Selection { id: post_inc(&mut self.next_selection_id), start, @@ -855,7 +860,6 @@ impl Editor { self.update_selections::(Vec::new(), None, cx); } else if click_count > 1 { // Remove the newest selection since it was only added as part of this multi-click. - let newest_selection = self.newest_selection::(buffer); let mut selections = self.local_selections(cx); selections.retain(|selection| selection.id != newest_selection.id); self.update_selections::(selections, None, cx) @@ -2404,8 +2408,6 @@ impl Editor { return; } - self.push_to_navigation_history(cx); - let selection = Selection { id: post_inc(&mut self.next_selection_id), start: 0, @@ -2428,8 +2430,6 @@ impl Editor { return; } - self.push_to_navigation_history(cx); - let cursor = self.buffer.read(cx).read(cx).len(); let selection = Selection { id: post_inc(&mut self.next_selection_id), @@ -2441,15 +2441,32 @@ impl Editor { self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } - fn push_to_navigation_history(&self, cx: &mut ViewContext) { + fn push_to_navigation_history( + &self, + position: Anchor, + new_position: Option, + cx: &mut ViewContext, + ) { if let Some(navigation) = &self.navigation { let buffer = self.buffer.read(cx).read(cx); - if let Some(last_selection) = self.selections.iter().max_by_key(|s| s.id) { - let anchor = last_selection.head(); - let offset = anchor.to_offset(&buffer); - drop(buffer); - navigation.push(Some(NavigationData { anchor, offset }), cx); + let offset = position.to_offset(&buffer); + let point = position.to_point(&buffer); + drop(buffer); + + if let Some(new_position) = new_position { + let row_delta = (new_position.row as i64 - point.row as i64).abs(); + if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA { + return; + } } + + navigation.push( + Some(NavigationData { + anchor: position, + offset, + }), + cx, + ); } } @@ -3230,14 +3247,14 @@ impl Editor { &self, snapshot: &MultiBufferSnapshot, ) -> Selection { - self.pending_selection(snapshot) - .or_else(|| { - self.selections - .iter() - .max_by_key(|s| s.id) - .map(|selection| self.resolve_selection(selection, snapshot)) - }) - .unwrap() + self.resolve_selection(self.newest_selection_internal().unwrap(), snapshot) + } + + pub fn newest_selection_internal(&self) -> Option<&Selection> { + self.pending_selection + .as_ref() + .map(|s| &s.selection) + .or_else(|| self.selections.iter().max_by_key(|s| s.id)) } pub fn update_selections( @@ -3248,10 +3265,11 @@ impl Editor { ) where T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug, { + let buffer = self.buffer.read(cx).snapshot(cx); + let old_cursor_position = self.newest_selection_internal().map(|s| s.head()); selections.sort_unstable_by_key(|s| s.start); // Merge overlapping selections. - let buffer = self.buffer.read(cx).snapshot(cx); let mut i = 1; while i < selections.len() { if selections[i - 1].end >= selections[i].start { @@ -3292,22 +3310,21 @@ impl Editor { } } + if let Some(old_cursor_position) = old_cursor_position { + let new_cursor_position = selections + .iter() + .max_by_key(|s| s.id) + .map(|s| s.head().to_point(&buffer)); + if new_cursor_position.is_some() { + self.push_to_navigation_history(old_cursor_position, new_cursor_position, cx); + } + } + if let Some(autoscroll) = autoscroll { self.request_autoscroll(autoscroll, cx); } self.pause_cursor_blinking(cx); - let prev_newest_selection = self.selections.iter().max_by_key(|s| s.id); - let curr_newest_selection = selections.iter().max_by_key(|s| s.id); - if let Some((prev_selection, curr_selection)) = - prev_newest_selection.zip(curr_newest_selection) - { - if prev_selection.head().to_offset(&buffer) != curr_selection.head().to_offset(&buffer) - { - self.push_to_navigation_history(cx); - } - } - self.set_selections( Arc::from_iter(selections.into_iter().map(|selection| { let end_bias = if selection.end > selection.start { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 54b9e19e2f..147622c8d4 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -149,7 +149,9 @@ impl ItemView for Editor { } fn deactivated(&mut self, cx: &mut ViewContext) { - self.push_to_navigation_history(cx); + if let Some(selection) = self.newest_selection_internal() { + self.push_to_navigation_history(selection.head(), None, cx); + } } fn is_dirty(&self, cx: &AppContext) -> bool { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 614f8b3808..a93ebde051 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -11,13 +11,7 @@ use gpui::{ }; use postage::watch; use project::ProjectPath; -use std::{ - any::Any, - cell::RefCell, - cmp, mem, - rc::Rc, - time::{Duration, Instant}, -}; +use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; use util::ResultExt; action!(Split, SplitDirection); @@ -110,7 +104,6 @@ impl Default for NavigationMode { struct NavigationEntry { item_view: Box, data: Option>, - insertion_time: Option, } impl Pane { @@ -558,20 +551,9 @@ impl Navigation { let mut state = self.0.borrow_mut(); match state.mode { NavigationMode::Normal => { - let mut insertion_time = Instant::now(); - if let Some(prev_insertion_time) = - state.backward_stack.last().and_then(|e| e.insertion_time) - { - if insertion_time.duration_since(prev_insertion_time) <= Duration::from_secs(2) - { - state.backward_stack.pop(); - insertion_time = prev_insertion_time; - } - } state.backward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), - insertion_time: Some(insertion_time), }); state.forward_stack.clear(); } @@ -579,14 +561,12 @@ impl Navigation { state.forward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), - insertion_time: None, }); } NavigationMode::GoingForward => { state.backward_stack.push(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), - insertion_time: None, }); } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2e1588790a..f5aeec861a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -701,9 +701,9 @@ mod tests { "/root", json!({ "a": { - "file1": "contents 1", - "file2": "contents 2", - "file3": "contents 3", + "file1": "contents 1\n".repeat(20), + "file2": "contents 2\n".repeat(20), + "file3": "contents 3\n".repeat(20), }, }), ) @@ -731,7 +731,7 @@ mod tests { .downcast::() .unwrap(); editor1.update(&mut cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)], cx); + editor.select_display_ranges(&[DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)], cx); }); let editor2 = workspace .update(&mut cx, |w, cx| w.open_path(file2.clone(), cx)) @@ -748,11 +748,11 @@ mod tests { .downcast::() .unwrap(); editor3.update(&mut cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2)], cx); + editor.select_display_ranges(&[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)], cx); }); assert_eq!( active_location(&workspace, &mut cx), - (file3.clone(), DisplayPoint::new(0, 2)) + (file3.clone(), DisplayPoint::new(15, 0)) ); workspace @@ -776,7 +776,7 @@ mod tests { .await; assert_eq!( active_location(&workspace, &mut cx), - (file1.clone(), DisplayPoint::new(0, 1)) + (file1.clone(), DisplayPoint::new(10, 0)) ); workspace @@ -801,7 +801,7 @@ mod tests { .await; assert_eq!( active_location(&workspace, &mut cx), - (file1.clone(), DisplayPoint::new(0, 1)) + (file1.clone(), DisplayPoint::new(10, 0)) ); workspace @@ -844,7 +844,7 @@ mod tests { .await; assert_eq!( active_location(&workspace, &mut cx), - (file1.clone(), DisplayPoint::new(0, 1)) + (file1.clone(), DisplayPoint::new(10, 0)) ); workspace .update(&mut cx, |w, cx| Pane::go_forward(w, cx)) From 926306582be2e80f8e459b2abe78af338e555c84 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Jan 2022 16:25:22 -0800 Subject: [PATCH 14/16] Add a unit test for navigation behavior at the editor level --- crates/editor/src/editor.rs | 57 ++++++++++++++++++++++++++++++++++++ crates/workspace/src/pane.rs | 18 ++++++++---- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6b6bc68f50..b1c41f393d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4156,6 +4156,63 @@ mod tests { }); } + #[gpui::test] + fn test_navigation_history(cx: &mut gpui::MutableAppContext) { + cx.add_window(Default::default(), |cx| { + use workspace::ItemView; + let navigation = Rc::new(workspace::Navigation::default()); + let settings = EditorSettings::test(&cx); + let buffer = MultiBuffer::build_simple(&sample_text(30, 5, 'a'), cx); + let mut editor = build_editor(buffer.clone(), settings, cx); + editor.navigation = Some(navigation.clone()); + + // Move the cursor a small distance. + // Nothing is added to the navigation history. + editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx); + editor.select_display_ranges(&[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)], cx); + assert!(navigation.pop_backward().is_none()); + + // Move the cursor a large distance. + // The history can jump back to the previous position. + editor.select_display_ranges(&[DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)], cx); + let nav_entry = navigation.pop_backward().unwrap(); + editor.navigate(nav_entry.data.unwrap(), cx); + assert_eq!(nav_entry.item_view.id(), cx.view_id()); + assert_eq!( + editor.selected_display_ranges(cx), + &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)] + ); + + // Move the cursor a small distance via the mouse. + // Nothing is added to the navigation history. + editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx); + editor.end_selection(cx); + assert_eq!( + editor.selected_display_ranges(cx), + &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] + ); + assert!(navigation.pop_backward().is_none()); + + // Move the cursor a large distance via the mouse. + // The history can jump back to the previous position. + editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx); + editor.end_selection(cx); + assert_eq!( + editor.selected_display_ranges(cx), + &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)] + ); + let nav_entry = navigation.pop_backward().unwrap(); + editor.navigate(nav_entry.data.unwrap(), cx); + assert_eq!(nav_entry.item_view.id(), cx.view_id()); + assert_eq!( + editor.selected_display_ranges(cx), + &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] + ); + + editor + }); + } + #[gpui::test] fn test_cancel(cx: &mut gpui::MutableAppContext) { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a93ebde051..0a1cbdc1da 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -101,9 +101,9 @@ impl Default for NavigationMode { } } -struct NavigationEntry { - item_view: Box, - data: Option>, +pub struct NavigationEntry { + pub item_view: Box, + pub data: Option>, } impl Pane { @@ -535,11 +535,19 @@ impl View for Pane { } impl Navigation { + pub fn pop_backward(&self) -> Option { + self.0.borrow_mut().backward_stack.pop() + } + + pub fn pop_forward(&self) -> Option { + self.0.borrow_mut().forward_stack.pop() + } + fn pop(&self, mode: NavigationMode) -> Option { match mode { NavigationMode::Normal => None, - NavigationMode::GoingBack => self.0.borrow_mut().backward_stack.pop(), - NavigationMode::GoingForward => self.0.borrow_mut().forward_stack.pop(), + NavigationMode::GoingBack => self.pop_backward(), + NavigationMode::GoingForward => self.pop_forward(), } } From 2cae7060335a0bdc4f0a68b73be6627db3723aa3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Jan 2022 16:31:48 -0800 Subject: [PATCH 15/16] Limit the storage used by the navigation history --- crates/workspace/src/pane.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 0a1cbdc1da..be0e3f05fb 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,6 +1,6 @@ use super::{ItemViewHandle, SplitDirection}; use crate::{ItemHandle, ItemView, Settings, WeakItemViewHandle, Workspace}; -use collections::HashMap; +use collections::{HashMap, VecDeque}; use gpui::{ action, elements::*, @@ -23,6 +23,8 @@ action!(CloseItem, usize); action!(GoBack); action!(GoForward); +const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; + pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { pane.activate_item(action.0, cx); @@ -83,8 +85,8 @@ pub struct Navigation(RefCell); #[derive(Default)] struct NavigationHistory { mode: NavigationMode, - backward_stack: Vec, - forward_stack: Vec, + backward_stack: VecDeque, + forward_stack: VecDeque, paths_by_item: HashMap, } @@ -536,11 +538,11 @@ impl View for Pane { impl Navigation { pub fn pop_backward(&self) -> Option { - self.0.borrow_mut().backward_stack.pop() + self.0.borrow_mut().backward_stack.pop_back() } pub fn pop_forward(&self) -> Option { - self.0.borrow_mut().forward_stack.pop() + self.0.borrow_mut().forward_stack.pop_back() } fn pop(&self, mode: NavigationMode) -> Option { @@ -559,20 +561,29 @@ impl Navigation { let mut state = self.0.borrow_mut(); match state.mode { NavigationMode::Normal => { - state.backward_stack.push(NavigationEntry { + if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.backward_stack.pop_front(); + } + state.backward_stack.push_back(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), }); state.forward_stack.clear(); } NavigationMode::GoingBack => { - state.forward_stack.push(NavigationEntry { + if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.forward_stack.pop_front(); + } + state.forward_stack.push_back(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), }); } NavigationMode::GoingForward => { - state.backward_stack.push(NavigationEntry { + if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.backward_stack.pop_front(); + } + state.backward_stack.push_back(NavigationEntry { item_view: Box::new(cx.weak_handle()), data: data.map(|data| Box::new(data) as Box), }); From 05bf8f61e2cb703f409dc275dae045b525f33909 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 18 Jan 2022 17:29:33 -0800 Subject: [PATCH 16/16] Avoid changing selection in buffer navigation dialogs If an editor has highlighted_rows, autoscroll it to reveal those rows instead of its cursor positions. --- crates/editor/src/editor.rs | 46 +++++++++++++++--------- crates/go_to_line/src/go_to_line.rs | 55 +++++++++++------------------ crates/outline/src/outline.rs | 52 ++++++++++----------------- 3 files changed, 68 insertions(+), 85 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1c41f393d..96e9973df5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -638,7 +638,10 @@ impl Editor { let first_cursor_top; let last_cursor_bottom; - if autoscroll == Autoscroll::Newest { + if let Some(highlighted_rows) = &self.highlighted_rows { + first_cursor_top = highlighted_rows.start as f32; + last_cursor_bottom = first_cursor_top + 1.; + } else if autoscroll == Autoscroll::Newest { let newest_selection = self.newest_selection::(&display_map.buffer_snapshot); first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32; last_cursor_bottom = first_cursor_top + 1.; @@ -704,22 +707,33 @@ impl Editor { ) -> bool { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self.local_selections::(cx); - let mut target_left = std::f32::INFINITY; - let mut target_right = 0.0_f32; - for selection in selections { - let head = selection.head().to_display_point(&display_map); - if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 { - let start_column = head.column().saturating_sub(3); - let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3); - target_left = target_left.min( - layouts[(head.row() - start_row) as usize].x_for_index(start_column as usize), - ); - target_right = target_right.max( - layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize) - + max_glyph_width, - ); + + let mut target_left; + let mut target_right; + + if self.highlighted_rows.is_some() { + target_left = 0.0_f32; + target_right = 0.0_f32; + } else { + target_left = std::f32::INFINITY; + target_right = 0.0_f32; + for selection in selections { + let head = selection.head().to_display_point(&display_map); + if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 { + let start_column = head.column().saturating_sub(3); + let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3); + target_left = target_left.min( + layouts[(head.row() - start_row) as usize] + .x_for_index(start_column as usize), + ); + target_right = target_right.max( + layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize) + + max_glyph_width, + ); + } } } + target_right = target_right.min(scroll_width); if target_right - target_left > viewport_width { @@ -3400,7 +3414,7 @@ impl Editor { }); } - fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext) { + pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext) { self.autoscroll_request = Some(autoscroll); cx.notify(); } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 53669ea2c6..c282cd1c75 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -1,11 +1,11 @@ -use editor::{display_map::ToDisplayPoint, Autoscroll, Editor, EditorSettings}; +use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor, EditorSettings}; use gpui::{ action, elements::*, geometry::vector::Vector2F, keymap::Binding, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, }; use postage::watch; use std::sync::Arc; -use text::{Bias, Point, Selection}; +use text::{Bias, Point}; use workspace::{Settings, Workspace}; action!(Toggle); @@ -25,17 +25,11 @@ pub struct GoToLine { settings: watch::Receiver, line_editor: ViewHandle, active_editor: ViewHandle, - restore_state: Option, - line_selection_id: Option, + prev_scroll_position: Option, cursor_point: Point, max_point: Point, } -struct RestoreState { - scroll_position: Vector2F, - selections: Vec>, -} - pub enum Event { Dismissed, } @@ -65,15 +59,11 @@ impl GoToLine { cx.subscribe(&line_editor, Self::on_line_editor_event) .detach(); - let (restore_state, cursor_point, max_point) = active_editor.update(cx, |editor, cx| { - let restore_state = Some(RestoreState { - scroll_position: editor.scroll_position(cx), - selections: editor.local_selections::(cx), - }); - + let (scroll_position, cursor_point, max_point) = active_editor.update(cx, |editor, cx| { + let scroll_position = editor.scroll_position(cx); let buffer = editor.buffer().read(cx).read(cx); ( - restore_state, + Some(scroll_position), editor.newest_selection(&buffer).head(), buffer.max_point(), ) @@ -83,8 +73,7 @@ impl GoToLine { settings: settings.clone(), line_editor, active_editor, - restore_state, - line_selection_id: None, + prev_scroll_position: scroll_position, cursor_point, max_point, } @@ -105,7 +94,14 @@ impl GoToLine { } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - self.restore_state.take(); + self.prev_scroll_position.take(); + self.active_editor.update(cx, |active_editor, cx| { + if let Some(rows) = active_editor.highlighted_rows() { + let snapshot = active_editor.snapshot(cx).display_snapshot; + let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); + active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx); + } + }); cx.emit(Event::Dismissed); } @@ -139,18 +135,13 @@ impl GoToLine { column.map(|column| column.saturating_sub(1)).unwrap_or(0), ) }) { - self.line_selection_id = self.active_editor.update(cx, |active_editor, cx| { + self.active_editor.update(cx, |active_editor, cx| { let snapshot = active_editor.snapshot(cx).display_snapshot; let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); let display_point = point.to_display_point(&snapshot); let row = display_point.row(); - active_editor.select_ranges([point..point], Some(Autoscroll::Center), cx); active_editor.set_highlighted_rows(Some(row..row + 1)); - Some( - active_editor - .newest_selection::(&snapshot.buffer_snapshot) - .id, - ) + active_editor.request_autoscroll(Autoscroll::Center, cx); }); cx.notify(); } @@ -164,17 +155,11 @@ impl Entity for GoToLine { type Event = Event; fn release(&mut self, cx: &mut MutableAppContext) { - let line_selection_id = self.line_selection_id.take(); - let restore_state = self.restore_state.take(); + let scroll_position = self.prev_scroll_position.take(); self.active_editor.update(cx, |editor, cx| { editor.set_highlighted_rows(None); - if let Some((line_selection_id, restore_state)) = line_selection_id.zip(restore_state) { - let newest_selection = - editor.newest_selection::(&editor.buffer().read(cx).read(cx)); - if line_selection_id == newest_selection.id { - editor.set_scroll_position(restore_state.scroll_position, cx); - editor.update_selections(restore_state.selections, None, cx); - } + if let Some(scroll_position) = scroll_position { + editor.set_scroll_position(scroll_position, cx); } }) } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index b11abac86d..46bf088c31 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -1,6 +1,6 @@ use editor::{ - display_map::ToDisplayPoint, Anchor, AnchorRangeExt, Autoscroll, Editor, EditorSettings, - ToPoint, + display_map::ToDisplayPoint, Anchor, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, + EditorSettings, ToPoint, }; use fuzzy::StringMatch; use gpui::{ @@ -12,7 +12,7 @@ use gpui::{ AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::{Outline, Selection}; +use language::Outline; use ordered_float::OrderedFloat; use postage::watch; use std::{ @@ -45,19 +45,13 @@ struct OutlineView { active_editor: ViewHandle, outline: Outline, selected_match_index: usize, - restore_state: Option, - symbol_selection_id: Option, + prev_scroll_position: Option, matches: Vec, query_editor: ViewHandle, list_state: UniformListState, settings: watch::Receiver, } -struct RestoreState { - scroll_position: Vector2F, - selections: Vec>, -} - pub enum Event { Dismissed, } @@ -132,20 +126,12 @@ impl OutlineView { cx.subscribe(&query_editor, Self::on_query_editor_event) .detach(); - let restore_state = editor.update(cx, |editor, cx| { - Some(RestoreState { - scroll_position: editor.scroll_position(cx), - selections: editor.local_selections::(cx), - }) - }); - let mut this = Self { handle: cx.weak_handle(), - active_editor: editor, matches: Default::default(), selected_match_index: 0, - restore_state, - symbol_selection_id: None, + prev_scroll_position: Some(editor.update(cx, |editor, cx| editor.scroll_position(cx))), + active_editor: editor, outline, query_editor, list_state: Default::default(), @@ -207,39 +193,37 @@ impl OutlineView { if navigate { let selected_match = &self.matches[self.selected_match_index]; let outline_item = &self.outline.items[selected_match.candidate_id]; - self.symbol_selection_id = self.active_editor.update(cx, |active_editor, cx| { + self.active_editor.update(cx, |active_editor, cx| { let snapshot = active_editor.snapshot(cx).display_snapshot; let buffer_snapshot = &snapshot.buffer_snapshot; let start = outline_item.range.start.to_point(&buffer_snapshot); let end = outline_item.range.end.to_point(&buffer_snapshot); let display_rows = start.to_display_point(&snapshot).row() ..end.to_display_point(&snapshot).row() + 1; - active_editor.select_ranges([start..start], Some(Autoscroll::Center), cx); active_editor.set_highlighted_rows(Some(display_rows)); - Some(active_editor.newest_selection::(&buffer_snapshot).id) + active_editor.request_autoscroll(Autoscroll::Center, cx); }); } cx.notify(); } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - self.restore_state.take(); + self.prev_scroll_position.take(); + self.active_editor.update(cx, |active_editor, cx| { + if let Some(rows) = active_editor.highlighted_rows() { + let snapshot = active_editor.snapshot(cx).display_snapshot; + let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); + active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx); + } + }); cx.emit(Event::Dismissed); } fn restore_active_editor(&mut self, cx: &mut MutableAppContext) { - let symbol_selection_id = self.symbol_selection_id.take(); self.active_editor.update(cx, |editor, cx| { editor.set_highlighted_rows(None); - if let Some((symbol_selection_id, restore_state)) = - symbol_selection_id.zip(self.restore_state.as_ref()) - { - let newest_selection = - editor.newest_selection::(&editor.buffer().read(cx).read(cx)); - if symbol_selection_id == newest_selection.id { - editor.set_scroll_position(restore_state.scroll_position, cx); - editor.update_selections(restore_state.selections.clone(), None, cx); - } + if let Some(scroll_position) = self.prev_scroll_position { + editor.set_scroll_position(scroll_position, cx); } }) }