Start laying the foundation for a per-pane navigation system

This commit is contained in:
Antonio Scandurra 2022-01-17 16:59:06 +01:00
parent cd0d1d3340
commit bbf634f439
5 changed files with 135 additions and 16 deletions

View File

@ -41,6 +41,7 @@ use std::{
iter::{self, FromIterator}, iter::{self, FromIterator},
mem, mem,
ops::{Deref, Range, RangeInclusive, Sub}, ops::{Deref, Range, RangeInclusive, Sub},
rc::Rc,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -48,7 +49,7 @@ use sum_tree::Bias;
use text::rope::TextDimension; use text::rope::TextDimension;
use theme::{DiagnosticStyle, EditorStyle}; use theme::{DiagnosticStyle, EditorStyle};
use util::post_inc; use util::post_inc;
use workspace::{PathOpener, Workspace}; use workspace::{Navigation, PathOpener, Workspace};
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const MAX_LINE_LEN: usize = 1024; const MAX_LINE_LEN: usize = 1024;
@ -377,6 +378,7 @@ pub struct Editor {
mode: EditorMode, mode: EditorMode,
placeholder_text: Option<Arc<str>>, placeholder_text: Option<Arc<str>>,
highlighted_rows: Option<Range<u32>>, highlighted_rows: Option<Range<u32>>,
navigation: Option<Rc<Navigation>>,
} }
pub struct EditorSnapshot { pub struct EditorSnapshot {
@ -457,6 +459,7 @@ impl Editor {
let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx); let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx);
clone.scroll_position = self.scroll_position; clone.scroll_position = self.scroll_position;
clone.scroll_top_anchor = self.scroll_top_anchor.clone(); clone.scroll_top_anchor = self.scroll_top_anchor.clone();
clone.navigation = self.navigation.clone();
clone clone
} }
@ -506,6 +509,7 @@ impl Editor {
mode: EditorMode::Full, mode: EditorMode::Full,
placeholder_text: None, placeholder_text: None,
highlighted_rows: None, highlighted_rows: None,
navigation: None,
}; };
let selection = Selection { let selection = Selection {
id: post_inc(&mut this.next_selection_id), id: post_inc(&mut this.next_selection_id),

View File

@ -10,11 +10,12 @@ use postage::watch;
use project::{File, ProjectPath, Worktree}; use project::{File, ProjectPath, Worktree};
use std::fmt::Write; use std::fmt::Write;
use std::path::Path; use std::path::Path;
use std::rc::Rc;
use text::{Point, Selection}; use text::{Point, Selection};
use util::TryFutureExt; use util::TryFutureExt;
use workspace::{ use workspace::{
ItemHandle, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView, WeakItemHandle, ItemHandle, ItemView, ItemViewHandle, Navigation, PathOpener, Settings, StatusItemView,
Workspace, WeakItemHandle, Workspace,
}; };
pub struct BufferOpener; pub struct BufferOpener;
@ -46,16 +47,19 @@ impl ItemHandle for BufferItemHandle {
&self, &self,
window_id: usize, window_id: usize,
workspace: &Workspace, workspace: &Workspace,
navigation: Rc<Navigation>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Box<dyn ItemViewHandle> { ) -> Box<dyn ItemViewHandle> {
let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx));
let weak_buffer = buffer.downgrade(); let weak_buffer = buffer.downgrade();
Box::new(cx.add_view(window_id, |cx| { Box::new(cx.add_view(window_id, |cx| {
Editor::for_buffer( let mut editor = Editor::for_buffer(
buffer, buffer,
crate::settings_builder(weak_buffer, workspace.settings()), crate::settings_builder(weak_buffer, workspace.settings()),
cx, cx,
) );
editor.navigation = Some(navigation);
editor
})) }))
} }
@ -129,6 +133,12 @@ impl ItemView for Editor {
Some(self.clone(cx)) Some(self.clone(cx))
} }
fn activated(&mut self, cx: &mut ViewContext<Self>) {
if let Some(navigation) = self.navigation.as_ref() {
navigation.push::<(), _>(None, cx);
}
}
fn is_dirty(&self, cx: &AppContext) -> bool { fn is_dirty(&self, cx: &AppContext) -> bool {
self.buffer().read(cx).read(cx).is_dirty() self.buffer().read(cx).read(cx).is_dirty()
} }

View File

@ -1,7 +1,7 @@
[package] [package]
name = "workspace" name = "workspace"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2021"
[lib] [lib]
path = "src/workspace.rs" path = "src/workspace.rs"

View File

@ -1,5 +1,6 @@
use super::{ItemViewHandle, SplitDirection}; use super::{ItemViewHandle, SplitDirection};
use crate::{ItemHandle, Settings, Workspace}; use crate::{ItemHandle, ItemView, Settings, WeakItemViewHandle, Workspace};
use collections::HashMap;
use gpui::{ use gpui::{
action, action,
elements::*, elements::*,
@ -9,7 +10,8 @@ use gpui::{
Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, Entity, MutableAppContext, Quad, RenderContext, View, ViewContext,
}; };
use postage::watch; use postage::watch;
use std::cmp; use project::ProjectPath;
use std::{any::Any, cell::RefCell, cmp, rc::Rc};
action!(Split, SplitDirection); action!(Split, SplitDirection);
action!(ActivateItem, usize); action!(ActivateItem, usize);
@ -17,6 +19,8 @@ action!(ActivatePrevItem);
action!(ActivateNextItem); action!(ActivateNextItem);
action!(CloseActiveItem); action!(CloseActiveItem);
action!(CloseItem, usize); action!(CloseItem, usize);
action!(GoBack);
action!(GoForward);
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { 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| { cx.add_action(|pane: &mut Pane, action: &Split, cx| {
pane.split(action.0, cx); pane.split(action.0, cx);
}); });
cx.add_action(Pane::go_back);
cx.add_action(Pane::go_forward);
cx.add_bindings(vec![ cx.add_bindings(vec![
Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")), 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 down", Split(SplitDirection::Down), Some("Pane")),
Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")), Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
Binding::new("cmd-k right", Split(SplitDirection::Right), 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<dyn ItemViewHandle>)>, item_views: Vec<(usize, Box<dyn ItemViewHandle>)>,
active_item: usize, active_item: usize,
settings: watch::Receiver<Settings>, settings: watch::Receiver<Settings>,
navigation: Rc<Navigation>,
}
#[derive(Default)]
pub struct Navigation(RefCell<NavigationHistory>);
#[derive(Default)]
struct NavigationHistory {
backward_stack: Vec<NavigationEntry>,
forward_stack: Vec<NavigationEntry>,
paths_by_item: HashMap<usize, ProjectPath>,
}
struct NavigationEntry {
item_view: Box<dyn WeakItemViewHandle>,
data: Option<Box<dyn Any>>,
} }
impl Pane { impl Pane {
@ -69,6 +93,7 @@ impl Pane {
item_views: Vec::new(), item_views: Vec::new(),
active_item: 0, active_item: 0,
settings, settings,
navigation: Default::default(),
} }
} }
@ -76,6 +101,16 @@ impl Pane {
cx.emit(Event::Activate); cx.emit(Event::Activate);
} }
pub fn go_back(&mut self, _: &GoBack, cx: &mut ViewContext<Self>) {
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<Self>) {
let mut navigation = self.navigation.0.borrow_mut();
if let Some(entry) = navigation.go_forward() {}
}
pub fn open_item<T>( pub fn open_item<T>(
&mut self, &mut self,
item_handle: T, 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); self.add_item_view(item_view.boxed_clone(), cx);
item_view item_view
} }
pub fn add_item_view( pub fn add_item_view(
&mut self, &mut self,
item_view: Box<dyn ItemViewHandle>, mut item_view: Box<dyn ItemViewHandle>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
item_view.added_to_pane(cx); item_view.added_to_pane(cx);
@ -142,6 +178,7 @@ impl Pane {
if index < self.item_views.len() { if index < self.item_views.len() {
self.active_item = index; self.active_item = index;
self.focus_active_item(cx); self.focus_active_item(cx);
self.item_views[index].1.activated(cx);
cx.notify(); cx.notify();
} }
} }
@ -172,8 +209,21 @@ impl Pane {
} }
} }
pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext<Self>) { pub fn close_item(&mut self, item_view_id: usize, cx: &mut ViewContext<Self>) {
self.item_views.retain(|(_, item)| item.id() != item_id); 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)); self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1));
if self.item_views.is_empty() { if self.item_views.is_empty() {
cx.emit(Event::Remove); cx.emit(Event::Remove);
@ -369,3 +419,33 @@ impl View for Pane {
self.focus_active_item(cx); self.focus_active_item(cx);
} }
} }
impl Navigation {
pub fn push<D: 'static + Any, T: ItemView>(&self, data: Option<D>, cx: &mut ViewContext<T>) {
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<dyn Any>),
});
}
}
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
}
}
}

View File

@ -36,6 +36,7 @@ use std::{
future::Future, future::Future,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc,
sync::Arc, sync::Arc,
}; };
use theme::{Theme, ThemeRegistry}; use theme::{Theme, ThemeRegistry};
@ -135,6 +136,7 @@ pub trait Item: Entity + Sized {
fn build_view( fn build_view(
handle: ModelHandle<Self>, handle: ModelHandle<Self>,
workspace: &Workspace, workspace: &Workspace,
navigation: Rc<Navigation>,
cx: &mut ViewContext<Self::View>, cx: &mut ViewContext<Self::View>,
) -> Self::View; ) -> Self::View;
@ -144,6 +146,8 @@ pub trait Item: Entity + Sized {
pub trait ItemView: View { pub trait ItemView: View {
type ItemHandle: ItemHandle; type ItemHandle: ItemHandle;
fn added_to_pane(&mut self, _: Rc<Navigation>, _: &mut ViewContext<Self>) {}
fn activated(&mut self, _: &mut ViewContext<Self>) {}
fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle;
fn title(&self, cx: &AppContext) -> String; fn title(&self, cx: &AppContext) -> String;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@ -185,6 +189,7 @@ pub trait ItemHandle: Send + Sync {
&self, &self,
window_id: usize, window_id: usize,
workspace: &Workspace, workspace: &Workspace,
navigation: Rc<Navigation>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Box<dyn ItemViewHandle>; ) -> Box<dyn ItemViewHandle>;
fn boxed_clone(&self) -> Box<dyn ItemHandle>; fn boxed_clone(&self) -> Box<dyn ItemHandle>;
@ -204,7 +209,8 @@ pub trait ItemViewHandle {
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn boxed_clone(&self) -> Box<dyn ItemViewHandle>; fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
fn added_to_pane(&self, cx: &mut ViewContext<Pane>); fn added_to_pane(&mut self, cx: &mut ViewContext<Pane>);
fn activated(&mut self, cx: &mut MutableAppContext);
fn id(&self) -> usize; fn id(&self) -> usize;
fn to_any(&self) -> AnyViewHandle; fn to_any(&self) -> AnyViewHandle;
fn is_dirty(&self, cx: &AppContext) -> bool; fn is_dirty(&self, cx: &AppContext) -> bool;
@ -220,6 +226,10 @@ pub trait ItemViewHandle {
) -> Task<anyhow::Result<()>>; ) -> Task<anyhow::Result<()>>;
} }
pub trait WeakItemViewHandle {
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemViewHandle>>;
}
impl<T: Item> ItemHandle for ModelHandle<T> { impl<T: Item> ItemHandle for ModelHandle<T> {
fn id(&self) -> usize { fn id(&self) -> usize {
self.id() self.id()
@ -229,9 +239,12 @@ impl<T: Item> ItemHandle for ModelHandle<T> {
&self, &self,
window_id: usize, window_id: usize,
workspace: &Workspace, workspace: &Workspace,
navigation: Rc<Navigation>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Box<dyn ItemViewHandle> { ) -> Box<dyn ItemViewHandle> {
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<dyn ItemHandle> { fn boxed_clone(&self) -> Box<dyn ItemHandle> {
@ -260,9 +273,10 @@ impl ItemHandle for Box<dyn ItemHandle> {
&self, &self,
window_id: usize, window_id: usize,
workspace: &Workspace, workspace: &Workspace,
navigation: Rc<Navigation>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Box<dyn ItemViewHandle> { ) -> Box<dyn ItemViewHandle> {
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<dyn ItemHandle> { fn boxed_clone(&self) -> Box<dyn ItemHandle> {
@ -330,7 +344,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
.map(|handle| Box::new(handle) as Box<dyn ItemViewHandle>) .map(|handle| Box::new(handle) as Box<dyn ItemViewHandle>)
} }
fn added_to_pane(&self, cx: &mut ViewContext<Pane>) { fn added_to_pane(&mut self, cx: &mut ViewContext<Pane>) {
cx.subscribe(self, |pane, item, event, cx| { cx.subscribe(self, |pane, item, event, cx| {
if T::should_close_item_on_event(event) { if T::should_close_item_on_event(event) {
pane.close_item(item.id(), cx); pane.close_item(item.id(), cx);
@ -349,6 +363,10 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
.detach(); .detach();
} }
fn activated(&mut self, cx: &mut MutableAppContext) {
self.update(cx, |this, cx| this.activated(cx));
}
fn save(&self, cx: &mut MutableAppContext) -> Result<Task<Result<()>>> { fn save(&self, cx: &mut MutableAppContext) -> Result<Task<Result<()>>> {
self.update(cx, |item, cx| item.save(cx)) self.update(cx, |item, cx| item.save(cx))
} }
@ -399,6 +417,13 @@ impl Clone for Box<dyn ItemHandle> {
} }
} }
impl<T: ItemView> WeakItemViewHandle for WeakViewHandle<T> {
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemViewHandle>> {
self.upgrade(cx)
.map(|v| Box::new(v) as Box<dyn ItemViewHandle>)
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct WorkspaceParams { pub struct WorkspaceParams {
pub project: ModelHandle<Project>, pub project: ModelHandle<Project>,