From ed28bd3f95658a54048eb59ea6f57174a1eb2689 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 16:57:13 -0600 Subject: [PATCH] Combine Workspace and WorkspaceView Co-Authored-By: Max Brunsfeld --- gpui/src/app.rs | 129 +++++++++++--- zed/src/editor/buffer_view.rs | 4 +- zed/src/file_finder.rs | 83 +++++---- zed/src/workspace/mod.rs | 15 +- zed/src/workspace/workspace.rs | 194 --------------------- zed/src/workspace/workspace_view.rs | 260 ++++++++++++++++++++++------ zed/src/worktree.rs | 2 +- 7 files changed, 369 insertions(+), 318 deletions(-) delete mode 100644 zed/src/workspace/workspace.rs diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 02a1626a304c546f89fd8385fbef057e643ab985..ac4c4e69b1782a416f5058025db0eafb974b7e7a 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -379,7 +379,8 @@ pub struct MutableAppContext { next_window_id: usize, next_task_id: usize, subscriptions: HashMap>, - observations: HashMap>, + model_observations: HashMap>, + view_observations: HashMap>, async_observations: HashMap>, window_invalidations: HashMap, presenters_and_platform_windows: @@ -420,7 +421,8 @@ impl MutableAppContext { next_window_id: 0, next_task_id: 0, subscriptions: HashMap::new(), - observations: HashMap::new(), + model_observations: HashMap::new(), + view_observations: HashMap::new(), async_observations: HashMap::new(), window_invalidations: HashMap::new(), presenters_and_platform_windows: HashMap::new(), @@ -871,13 +873,13 @@ impl MutableAppContext { for model_id in dropped_models { self.ctx.models.remove(&model_id); self.subscriptions.remove(&model_id); - self.observations.remove(&model_id); + self.model_observations.remove(&model_id); self.async_observations.remove(&model_id); } for (window_id, view_id) in dropped_views { self.subscriptions.remove(&view_id); - self.observations.remove(&view_id); + self.model_observations.remove(&view_id); self.async_observations.remove(&view_id); if let Some(window) = self.ctx.windows.get_mut(&window_id) { self.window_invalidations @@ -1004,11 +1006,11 @@ impl MutableAppContext { } fn notify_model_observers(&mut self, observed_id: usize) { - if let Some(observations) = self.observations.remove(&observed_id) { + if let Some(observations) = self.model_observations.remove(&observed_id) { if self.ctx.models.contains_key(&observed_id) { for mut observation in observations { let alive = match &mut observation { - Observation::FromModel { model_id, callback } => { + ModelObservation::FromModel { model_id, callback } => { if let Some(mut model) = self.ctx.models.remove(model_id) { callback(model.as_any_mut(), observed_id, self, *model_id); self.ctx.models.insert(*model_id, model); @@ -1017,7 +1019,7 @@ impl MutableAppContext { false } } - Observation::FromView { + ModelObservation::FromView { window_id, view_id, callback, @@ -1049,7 +1051,7 @@ impl MutableAppContext { }; if alive { - self.observations + self.model_observations .entry(observed_id) .or_default() .push(observation); @@ -1072,6 +1074,44 @@ impl MutableAppContext { .updated .insert(view_id); + if let Some(observations) = self.view_observations.remove(&view_id) { + if self.ctx.models.contains_key(&view_id) { + for mut observation in observations { + let alive = if let Some(mut view) = self + .ctx + .windows + .get_mut(&observation.window_id) + .and_then(|w| w.views.remove(&observation.view_id)) + { + (observation.callback)( + view.as_any_mut(), + view_id, + window_id, + self, + observation.window_id, + observation.view_id, + ); + self.ctx + .windows + .get_mut(&observation.window_id) + .unwrap() + .views + .insert(observation.view_id, view); + true + } else { + false + }; + + if alive { + self.view_observations + .entry(view_id) + .or_default() + .push(observation); + } + } + } + } + if let Entry::Occupied(mut entry) = self.async_observations.entry(view_id) { if entry.get_mut().blocking_send(()).is_err() { entry.remove_entry(); @@ -1576,10 +1616,10 @@ impl<'a, T: Entity> ModelContext<'a, T> { F: 'static + FnMut(&mut T, ModelHandle, &mut ModelContext), { self.app - .observations + .model_observations .entry(handle.model_id) .or_default() - .push(Observation::FromModel { + .push(ModelObservation::FromModel { model_id: self.model_id, callback: Box::new(move |model, observed_id, app, model_id| { let model = model.downcast_mut().expect("downcast is type safe"); @@ -1812,7 +1852,7 @@ impl<'a, T: View> ViewContext<'a, T> { window_id: self.window_id, view_id: self.view_id, callback: Box::new(move |view, payload, app, window_id, view_id| { - if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) { + if let Some(emitter_handle) = emitter_handle.upgrade(&app) { let model = view.downcast_mut().expect("downcast is type safe"); let payload = payload.downcast_ref().expect("downcast is type safe"); let mut ctx = ViewContext::new(app, window_id, view_id); @@ -1829,16 +1869,16 @@ impl<'a, T: View> ViewContext<'a, T> { }); } - pub fn observe(&mut self, handle: &ModelHandle, mut callback: F) + pub fn observe_model(&mut self, handle: &ModelHandle, mut callback: F) where S: Entity, F: 'static + FnMut(&mut T, ModelHandle, &mut ViewContext), { self.app - .observations + .model_observations .entry(handle.id()) .or_default() - .push(Observation::FromView { + .push(ModelObservation::FromView { window_id: self.window_id, view_id: self.view_id, callback: Box::new(move |view, observed_id, app, window_id, view_id| { @@ -1850,6 +1890,38 @@ impl<'a, T: View> ViewContext<'a, T> { }); } + pub fn observe_view(&mut self, handle: &ViewHandle, mut callback: F) + where + S: View, + F: 'static + FnMut(&mut T, ViewHandle, &mut ViewContext), + { + self.app + .view_observations + .entry(handle.id()) + .or_default() + .push(ViewObservation { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new( + move |view, + observed_view_id, + observed_window_id, + app, + observing_window_id, + observing_view_id| { + let view = view.downcast_mut().expect("downcast is type safe"); + let observed_handle = ViewHandle::new( + observed_view_id, + observed_window_id, + &app.ctx.ref_counts, + ); + let mut ctx = ViewContext::new(app, observing_window_id, observing_view_id); + callback(view, observed_handle, &mut ctx); + }, + ), + }); + } + pub fn notify(&mut self) { self.app.notify_view(self.window_id, self.view_id); } @@ -1918,6 +1990,12 @@ impl<'a, T: View> ViewContext<'a, T> { } } +impl AsRef for &AppContext { + fn as_ref(&self) -> &AppContext { + self + } +} + impl AsRef for ViewContext<'_, M> { fn as_ref(&self) -> &AppContext { &self.app.ctx @@ -2346,8 +2424,9 @@ impl WeakViewHandle { } } - pub fn upgrade(&self, app: &AppContext) -> Option> { - if app + pub fn upgrade(&self, ctx: impl AsRef) -> Option> { + let ctx = ctx.as_ref(); + if ctx .windows .get(&self.window_id) .and_then(|w| w.views.get(&self.view_id)) @@ -2356,7 +2435,7 @@ impl WeakViewHandle { Some(ViewHandle::new( self.window_id, self.view_id, - &app.ref_counts, + &ctx.ref_counts, )) } else { None @@ -2496,7 +2575,7 @@ enum Subscription { }, } -enum Observation { +enum ModelObservation { FromModel { model_id: usize, callback: Box, @@ -2508,6 +2587,12 @@ enum Observation { }, } +struct ViewObservation { + window_id: usize, + view_id: usize, + callback: Box, +} + type FutureHandler = Box, &mut MutableAppContext) -> Box>; struct StreamHandler { @@ -2639,7 +2724,7 @@ mod tests { assert_eq!(app.ctx.models.len(), 1); assert!(app.subscriptions.is_empty()); - assert!(app.observations.is_empty()); + assert!(app.model_observations.is_empty()); }); } @@ -2842,7 +2927,7 @@ mod tests { assert_eq!(app.ctx.windows[&window_id].views.len(), 2); assert!(app.subscriptions.is_empty()); - assert!(app.observations.is_empty()); + assert!(app.model_observations.is_empty()); }) } @@ -2988,7 +3073,7 @@ mod tests { let model = app.add_model(|_| Model::default()); view.update(app, |_, c| { - c.observe(&model, |me, observed, c| { + c.observe_model(&model, |me, observed, c| { me.events.push(observed.read(c).count) }); }); @@ -3032,7 +3117,7 @@ mod tests { let observed_model = app.add_model(|_| Model); observing_view.update(app, |_, ctx| { - ctx.observe(&observed_model, |_, _, _| {}); + ctx.observe_model(&observed_model, |_, _, _| {}); }); observing_model.update(app, |_, ctx| { ctx.observe(&observed_model, |_, _, _| {}); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index b5eaf7c4ec905c4452a388b5fc2f7a215b212458..32013256c796f565ac76bd90eab9a12bbd2be240 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -138,7 +138,7 @@ impl BufferView { file.observe_from_view(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged)); } - ctx.observe(&buffer, Self::on_buffer_changed); + ctx.observe_model(&buffer, Self::on_buffer_changed); ctx.subscribe_to_model(&buffer, Self::on_buffer_event); let display_map = ctx.add_model(|ctx| { DisplayMap::new( @@ -147,7 +147,7 @@ impl BufferView { ctx, ) }); - ctx.observe(&display_map, Self::on_display_map_changed); + ctx.observe_model(&display_map, Self::on_display_map_changed); let (selection_set_id, _) = buffer.update(ctx, |buffer, ctx| { buffer.add_selection_set( diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 0884cbdf5a118be6dd4a499cecc66ee92893c538..844d5968c2bf47174cf67846d92ca5183777caa5 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -2,7 +2,7 @@ use crate::{ editor::{buffer_view, BufferView}, settings::Settings, util, watch, - workspace::{Workspace, WorkspaceView}, + workspace::WorkspaceView, worktree::{match_paths, PathMatch, Worktree}, }; use gpui::{ @@ -11,8 +11,8 @@ use gpui::{ fonts::{Properties, Weight}, geometry::vector::vec2f, keymap::{self, Binding}, - AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext, - ViewHandle, WeakViewHandle, + AppContext, Axis, Border, Entity, MutableAppContext, View, ViewContext, ViewHandle, + WeakViewHandle, }; use std::{ cmp, @@ -26,7 +26,7 @@ use std::{ pub struct FileFinder { handle: WeakViewHandle, settings: watch::Receiver, - workspace: ModelHandle, + workspace: WeakViewHandle, query_buffer: ViewHandle, search_count: usize, latest_search_id: usize, @@ -255,15 +255,11 @@ impl FileFinder { fn toggle(workspace_view: &mut WorkspaceView, _: &(), ctx: &mut ViewContext) { workspace_view.toggle_modal(ctx, |ctx, workspace_view| { - let handle = ctx.add_view(|ctx| { - Self::new( - workspace_view.settings.clone(), - workspace_view.workspace.clone(), - ctx, - ) - }); - ctx.subscribe_to_view(&handle, Self::on_event); - handle + let workspace = ctx.handle(); + let finder = + ctx.add_view(|ctx| Self::new(workspace_view.settings.clone(), workspace, ctx)); + ctx.subscribe_to_view(&finder, Self::on_event); + finder }); } @@ -288,10 +284,10 @@ impl FileFinder { pub fn new( settings: watch::Receiver, - workspace: ModelHandle, + workspace: ViewHandle, ctx: &mut ViewContext, ) -> Self { - ctx.observe(&workspace, Self::workspace_updated); + ctx.observe_view(&workspace, Self::workspace_updated); let query_buffer = ctx.add_view(|ctx| BufferView::single_line(settings.clone(), ctx)); ctx.subscribe_to_view(&query_buffer, Self::on_query_buffer_event); @@ -301,7 +297,7 @@ impl FileFinder { Self { handle: ctx.handle().downgrade(), settings, - workspace, + workspace: workspace.downgrade(), query_buffer, search_count: 0, latest_search_id: 0, @@ -314,7 +310,7 @@ impl FileFinder { } } - fn workspace_updated(&mut self, _: ModelHandle, ctx: &mut ViewContext) { + fn workspace_updated(&mut self, _: ViewHandle, ctx: &mut ViewContext) { self.spawn_search(self.query_buffer.read(ctx).text(ctx.as_ref()), ctx); } @@ -390,9 +386,10 @@ impl FileFinder { ctx.emit(Event::Selected(*tree_id, path.clone())); } - fn spawn_search(&mut self, query: String, ctx: &mut ViewContext) { + fn spawn_search(&mut self, query: String, ctx: &mut ViewContext) -> Option<()> { let snapshots = self .workspace + .upgrade(&ctx)? .read(ctx) .worktrees() .iter() @@ -420,6 +417,8 @@ impl FileFinder { }); ctx.spawn(task, Self::update_matches).detach(); + + Some(()) } fn update_matches( @@ -443,6 +442,7 @@ impl FileFinder { fn worktree<'a>(&'a self, tree_id: usize, app: &'a AppContext) -> Option<&'a Worktree> { self.workspace + .upgrade(app)? .read(app) .worktrees() .get(&tree_id) @@ -453,11 +453,7 @@ impl FileFinder { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor, settings, - test::temp_tree, - workspace::{Workspace, WorkspaceView}, - }; + use crate::{editor, settings, test::temp_tree, workspace::WorkspaceView}; use gpui::App; use serde_json::json; use std::fs; @@ -476,20 +472,22 @@ mod tests { }); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx)); - let (window_id, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + let (window_id, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings, ctx); + workspace.open_path(tmp_dir.path().into(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; app.dispatch_action( window_id, - vec![workspace_view.id()], + vec![workspace.id()], "file_finder:toggle".into(), (), ); let finder = app.read(|ctx| { - workspace_view + workspace .read(ctx) .modal() .cloned() @@ -507,16 +505,16 @@ mod tests { .condition(&app, |finder, _| finder.matches.len() == 2) .await; - let active_pane = app.read(|ctx| workspace_view.read(ctx).active_pane().clone()); + let active_pane = app.read(|ctx| workspace.read(ctx).active_pane().clone()); app.dispatch_action( window_id, - vec![workspace_view.id(), finder.id()], + vec![workspace.id(), finder.id()], "menu:select_next", (), ); app.dispatch_action( window_id, - vec![workspace_view.id(), finder.id()], + vec![workspace.id(), finder.id()], "file_finder:confirm", (), ); @@ -543,7 +541,11 @@ mod tests { "hiccup": "", })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings.clone(), ctx); + workspace.open_path(tmp_dir.path().into(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let (_, finder) = @@ -596,7 +598,11 @@ mod tests { fs::write(&file_path, "").unwrap(); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![file_path], ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings.clone(), ctx); + workspace.open_path(file_path, ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let (_, finder) = @@ -633,11 +639,14 @@ mod tests { "dir2": { "a.txt": "" } })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| { - Workspace::new( - vec![tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], + + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings.clone(), ctx); + smol::block_on(workspace.open_paths( + &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], ctx, - ) + )); + workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index 504bb9a8c05b4965487820d745682e49395a0165..f651c31f533251e1c48c3008cee292f43248724f 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -1,11 +1,9 @@ pub mod pane; pub mod pane_group; -pub mod workspace; pub mod workspace_view; pub use pane::*; pub use pane_group::*; -pub use workspace::*; pub use workspace_view::*; use crate::{ @@ -68,9 +66,8 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { log::info!("open new workspace"); // Add a new workspace if necessary - let workspace = app.add_model(|ctx| Workspace::new(vec![], ctx)); app.add_window(|ctx| { - let view = WorkspaceView::new(workspace, params.settings.clone(), ctx); + let mut view = WorkspaceView::new(0, params.settings.clone(), ctx); let open_paths = view.open_paths(¶ms.paths, ctx); ctx.foreground().spawn(open_paths).detach(); view @@ -133,15 +130,7 @@ mod tests { let workspace_view_1 = app .root_view::(app.window_ids().next().unwrap()) .unwrap(); - assert_eq!( - workspace_view_1 - .read(app) - .workspace - .read(app) - .worktrees() - .len(), - 2 - ); + assert_eq!(workspace_view_1.read(app).worktrees().len(), 2); app.dispatch_global_action( "workspace:open_paths", diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs deleted file mode 100644 index 58f9786eb5b03cfb57f99900abbf287611f8d7b0..0000000000000000000000000000000000000000 --- a/zed/src/workspace/workspace.rs +++ /dev/null @@ -1,194 +0,0 @@ -use super::ItemViewHandle; -use crate::{ - editor::{Buffer, BufferView}, - settings::Settings, - time::ReplicaId, - watch, - worktree::{Worktree, WorktreeHandle as _}, -}; -use anyhow::anyhow; -use futures_core::future::LocalBoxFuture; -use gpui::{AppContext, Entity, ModelContext, ModelHandle}; -use smol::prelude::*; -use std::{collections::hash_map::Entry, future}; -use std::{ - collections::{HashMap, HashSet}, - path::{Path, PathBuf}, - sync::Arc, -}; - -pub struct Workspace { - replica_id: ReplicaId, - worktrees: HashSet>, - buffers: HashMap< - (usize, u64), - postage::watch::Receiver, Arc>>>, - >, -} - -impl Workspace { - pub fn new(paths: Vec, ctx: &mut ModelContext) -> Self { - let mut workspace = Self { - replica_id: 0, - worktrees: Default::default(), - buffers: Default::default(), - }; - workspace.open_paths(&paths, ctx); - workspace - } - - pub fn worktrees(&self) -> &HashSet> { - &self.worktrees - } - - pub fn worktree_scans_complete(&self, ctx: &AppContext) -> impl Future + 'static { - let futures = self - .worktrees - .iter() - .map(|worktree| worktree.read(ctx).scan_complete()) - .collect::>(); - async move { - for future in futures { - future.await; - } - } - } - - pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool { - paths.iter().all(|path| self.contains_path(&path, app)) - } - - pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool { - self.worktrees - .iter() - .any(|worktree| worktree.read(app).contains_abs_path(path)) - } - - pub fn open_paths( - &mut self, - paths: &[PathBuf], - ctx: &mut ModelContext, - ) -> Vec<(usize, Arc)> { - paths - .iter() - .cloned() - .map(move |path| self.open_path(path, ctx)) - .collect() - } - - fn open_path(&mut self, path: PathBuf, ctx: &mut ModelContext) -> (usize, Arc) { - for tree in self.worktrees.iter() { - if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) { - return (tree.id(), relative_path.into()); - } - } - - let worktree = ctx.add_model(|ctx| Worktree::new(path.clone(), ctx)); - let worktree_id = worktree.id(); - ctx.observe(&worktree, Self::on_worktree_updated); - self.worktrees.insert(worktree); - ctx.notify(); - (worktree_id, Path::new("").into()) - } - - pub fn open_entry( - &mut self, - (worktree_id, path): (usize, Arc), - window_id: usize, - settings: watch::Receiver, - ctx: &mut ModelContext, - ) -> LocalBoxFuture<'static, Result, Arc>> { - let worktree = match self.worktrees.get(&worktree_id).cloned() { - Some(worktree) => worktree, - None => { - return future::ready(Err(Arc::new(anyhow!( - "worktree {} does not exist", - worktree_id - )))) - .boxed_local(); - } - }; - - let inode = match worktree.read(ctx).inode_for_path(&path) { - Some(inode) => inode, - None => { - return future::ready(Err(Arc::new(anyhow!("path {:?} does not exist", path)))) - .boxed_local(); - } - }; - - let file = match worktree.file(path.clone(), ctx.as_ref()) { - Some(file) => file, - None => { - return future::ready(Err(Arc::new(anyhow!("path {:?} does not exist", path)))) - .boxed_local() - } - }; - - if let Entry::Vacant(entry) = self.buffers.entry((worktree_id, inode)) { - let (mut tx, rx) = postage::watch::channel(); - entry.insert(rx); - let history = file.load_history(ctx.as_ref()); - let replica_id = self.replica_id; - let buffer = ctx - .background_executor() - .spawn(async move { Ok(Buffer::from_history(replica_id, history.await?)) }); - ctx.spawn(buffer, move |_, from_history_result, ctx| { - *tx.borrow_mut() = Some(match from_history_result { - Ok(buffer) => Ok(ctx.add_model(|_| buffer)), - Err(error) => Err(Arc::new(error)), - }) - }) - .detach() - } - - let mut watch = self.buffers.get(&(worktree_id, inode)).unwrap().clone(); - ctx.spawn( - async move { - loop { - if let Some(load_result) = watch.borrow().as_ref() { - return load_result.clone(); - } - watch.next().await; - } - }, - move |_, load_result, ctx| { - load_result.map(|buffer_handle| { - Box::new(ctx.as_mut().add_view(window_id, |ctx| { - BufferView::for_buffer(buffer_handle, Some(file), settings, ctx) - })) as Box - }) - }, - ) - .boxed_local() - } - - fn on_worktree_updated(&mut self, _: ModelHandle, ctx: &mut ModelContext) { - ctx.notify(); - } -} - -impl Entity for Workspace { - type Event = (); -} - -#[cfg(test)] -pub trait WorkspaceHandle { - fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)>; -} - -#[cfg(test)] -impl WorkspaceHandle for ModelHandle { - fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)> { - self.read(app) - .worktrees() - .iter() - .flat_map(|tree| { - let tree_id = tree.id(); - tree.read(app) - .files(0) - .map(move |f| (tree_id, f.path().clone())) - }) - .collect::>() - } -} diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 8df9be4d57fb4f67f0b70986b618e8bc26d9efe7..91e1e3940395b833656f47e7f05c083113972928 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -1,5 +1,12 @@ -use super::{pane, Pane, PaneGroup, SplitDirection, Workspace}; -use crate::{settings::Settings, watch}; +use super::{pane, Pane, PaneGroup, SplitDirection}; +use crate::{ + editor::{Buffer, BufferView}, + settings::Settings, + time::ReplicaId, + watch, + worktree::{Worktree, WorktreeHandle}, +}; +use anyhow::anyhow; use futures_core::{future::LocalBoxFuture, Future}; use gpui::{ color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, @@ -7,8 +14,10 @@ use gpui::{ ViewHandle, }; use log::error; +use smol::prelude::*; use std::{ - collections::HashSet, + collections::{hash_map::Entry, HashMap, HashSet}, + future, path::{Path, PathBuf}, sync::Arc, }; @@ -123,23 +132,26 @@ pub struct State { } pub struct WorkspaceView { - pub workspace: ModelHandle, pub settings: watch::Receiver, modal: Option, center: PaneGroup, panes: Vec>, active_pane: ViewHandle, loading_entries: HashSet<(usize, Arc)>, + replica_id: ReplicaId, + worktrees: HashSet>, + buffers: HashMap< + (usize, u64), + postage::watch::Receiver, Arc>>>, + >, } impl WorkspaceView { pub fn new( - workspace: ModelHandle, + replica_id: ReplicaId, settings: watch::Receiver, ctx: &mut ViewContext, ) -> Self { - ctx.observe(&workspace, Self::workspace_updated); - let pane = ctx.add_view(|_| Pane::new(settings.clone())); let pane_id = pane.id(); ctx.subscribe_to_view(&pane, move |me, _, event, ctx| { @@ -148,28 +160,52 @@ impl WorkspaceView { ctx.focus(&pane); WorkspaceView { - workspace, modal: None, center: PaneGroup::new(pane.id()), panes: vec![pane.clone()], active_pane: pane.clone(), loading_entries: HashSet::new(), settings, + replica_id, + worktrees: Default::default(), + buffers: Default::default(), } } + pub fn worktrees(&self) -> &HashSet> { + &self.worktrees + } + pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool { - self.workspace.read(app).contains_paths(paths, app) + paths.iter().all(|path| self.contains_path(&path, app)) + } + + pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool { + self.worktrees + .iter() + .any(|worktree| worktree.read(app).contains_abs_path(path)) + } + + pub fn worktree_scans_complete(&self, ctx: &AppContext) -> impl Future + 'static { + let futures = self + .worktrees + .iter() + .map(|worktree| worktree.read(ctx).scan_complete()) + .collect::>(); + async move { + for future in futures { + future.await; + } + } } pub fn open_paths( - &self, + &mut self, paths: &[PathBuf], ctx: &mut ViewContext, ) -> impl Future { - let entries = self - .workspace - .update(ctx, |workspace, ctx| workspace.open_paths(paths, ctx)); + let entries = self.open_paths2(paths, ctx); + let bg = ctx.background_executor().clone(); let tasks = paths .iter() @@ -197,6 +233,33 @@ impl WorkspaceView { } } + pub fn open_paths2( + &mut self, + paths: &[PathBuf], + ctx: &mut ViewContext, + ) -> Vec<(usize, Arc)> { + paths + .iter() + .cloned() + .map(move |path| self.open_path(path, ctx)) + .collect() + } + + pub fn open_path(&mut self, path: PathBuf, ctx: &mut ViewContext) -> (usize, Arc) { + for tree in self.worktrees.iter() { + if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) { + return (tree.id(), relative_path.into()); + } + } + + let worktree = ctx.add_model(|ctx| Worktree::new(path.clone(), ctx)); + let worktree_id = worktree.id(); + ctx.observe_model(&worktree, |_, _, ctx| ctx.notify()); + self.worktrees.insert(worktree); + ctx.notify(); + (worktree_id, Path::new("").into()) + } + pub fn toggle_modal(&mut self, ctx: &mut ViewContext, add_view: F) where V: 'static + View, @@ -244,9 +307,7 @@ impl WorkspaceView { self.loading_entries.insert(entry.clone()); let window_id = ctx.window_id(); - let future = self.workspace.update(ctx, |workspace, ctx| { - workspace.open_entry(entry.clone(), window_id, self.settings.clone(), ctx) - }); + let future = self.open_entry2(entry.clone(), window_id, self.settings.clone(), ctx); Some(ctx.spawn(future, move |me, item_view, ctx| { me.loading_entries.remove(&entry); @@ -257,6 +318,78 @@ impl WorkspaceView { })) } + pub fn open_entry2( + &mut self, + (worktree_id, path): (usize, Arc), + window_id: usize, + settings: watch::Receiver, + ctx: &mut ViewContext, + ) -> LocalBoxFuture<'static, Result, Arc>> { + let worktree = match self.worktrees.get(&worktree_id).cloned() { + Some(worktree) => worktree, + None => { + return future::ready(Err(Arc::new(anyhow!( + "worktree {} does not exist", + worktree_id + )))) + .boxed_local(); + } + }; + + let inode = match worktree.read(ctx).inode_for_path(&path) { + Some(inode) => inode, + None => { + return future::ready(Err(Arc::new(anyhow!("path {:?} does not exist", path)))) + .boxed_local(); + } + }; + + let file = match worktree.file(path.clone(), ctx.as_ref()) { + Some(file) => file, + None => { + return future::ready(Err(Arc::new(anyhow!("path {:?} does not exist", path)))) + .boxed_local() + } + }; + + if let Entry::Vacant(entry) = self.buffers.entry((worktree_id, inode)) { + let (mut tx, rx) = postage::watch::channel(); + entry.insert(rx); + let history = file.load_history(ctx.as_ref()); + let replica_id = self.replica_id; + let buffer = ctx + .background_executor() + .spawn(async move { Ok(Buffer::from_history(replica_id, history.await?)) }); + ctx.spawn(buffer, move |_, from_history_result, ctx| { + *tx.borrow_mut() = Some(match from_history_result { + Ok(buffer) => Ok(ctx.add_model(|_| buffer)), + Err(error) => Err(Arc::new(error)), + }) + }) + .detach() + } + + let mut watch = self.buffers.get(&(worktree_id, inode)).unwrap().clone(); + ctx.spawn( + async move { + loop { + if let Some(load_result) = watch.borrow().as_ref() { + return load_result.clone(); + } + watch.next().await; + } + }, + move |_, load_result, ctx| { + load_result.map(|buffer_handle| { + Box::new(ctx.as_mut().add_view(window_id, |ctx| { + BufferView::for_buffer(buffer_handle, Some(file), settings, ctx) + })) as Box + }) + }, + ) + .boxed_local() + } + pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext) { self.active_pane.update(ctx, |pane, ctx| { if let Some(item) = pane.active_item() { @@ -288,10 +421,6 @@ impl WorkspaceView { }; } - fn workspace_updated(&mut self, _: ModelHandle, ctx: &mut ViewContext) { - ctx.notify(); - } - fn add_pane(&mut self, ctx: &mut ViewContext) -> ViewHandle { let pane = ctx.add_view(|_| Pane::new(self.settings.clone())); let pane_id = pane.id(); @@ -403,10 +532,31 @@ impl View for WorkspaceView { } } +#[cfg(test)] +pub trait WorkspaceViewHandle { + fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)>; +} + +#[cfg(test)] +impl WorkspaceViewHandle for ViewHandle { + fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)> { + self.read(app) + .worktrees() + .iter() + .flat_map(|tree| { + let tree_id = tree.id(); + tree.read(app) + .files(0) + .map(move |f| (tree_id, f.path().clone())) + }) + .collect::>() + } +} + #[cfg(test)] mod tests { - use super::{pane, Workspace, WorkspaceView}; - use crate::{editor::BufferView, settings, test::temp_tree, workspace::WorkspaceHandle as _}; + use super::{pane, WorkspaceView, WorkspaceViewHandle as _}; + use crate::{editor::BufferView, settings, test::temp_tree}; use gpui::App; use serde_json::json; use std::{collections::HashSet, os::unix}; @@ -423,7 +573,13 @@ mod tests { })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); + + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings, ctx); + smol::block_on(workspace.open_paths(&[dir.path().into()], ctx)); + workspace + }); + app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let entries = app.read(|ctx| workspace.file_entries(ctx)); @@ -431,12 +587,10 @@ mod tests { let file2 = entries[1].clone(); let file3 = entries[2].clone(); - let (_, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); - let pane = app.read(|ctx| workspace_view.read(ctx).active_pane().clone()); + let pane = app.read(|ctx| workspace.read(ctx).active_pane().clone()); // Open the first entry - workspace_view + workspace .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx)) .unwrap() .await; @@ -450,7 +604,7 @@ mod tests { }); // Open the second entry - workspace_view + workspace .update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx)) .unwrap() .await; @@ -464,7 +618,7 @@ mod tests { }); // Open the first entry again. The existing pane item is activated. - workspace_view.update(&mut app, |w, ctx| { + workspace.update(&mut app, |w, ctx| { assert!(w.open_entry(file1.clone(), ctx).is_none()) }); app.read(|ctx| { @@ -477,7 +631,7 @@ mod tests { }); // Open the third entry twice concurrently. Only one pane item is added. - workspace_view + workspace .update(&mut app, |w, ctx| { let task = w.open_entry(file3.clone(), ctx).unwrap(); assert!(w.open_entry(file3.clone(), ctx).is_none()); @@ -505,22 +659,24 @@ mod tests { "b.txt": "", })); - let workspace = app.add_model(|ctx| Workspace::new(vec![dir1.path().into()], ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings, ctx); + workspace.open_path(dir1.path().into(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; // Open a file within an existing worktree. app.update(|ctx| { - workspace_view.update(ctx, |view, ctx| { + workspace.update(ctx, |view, ctx| { view.open_paths(&[dir1.path().join("a.txt")], ctx) }) }) .await; app.read(|ctx| { - workspace_view + workspace .read(ctx) .active_pane() .read(ctx) @@ -532,7 +688,7 @@ mod tests { // Open a file outside of any existing worktree. app.update(|ctx| { - workspace_view.update(ctx, |view, ctx| { + workspace.update(ctx, |view, ctx| { view.open_paths(&[dir2.path().join("b.txt")], ctx) }) }) @@ -552,7 +708,7 @@ mod tests { ); }); app.read(|ctx| { - workspace_view + workspace .read(ctx) .active_pane() .read(ctx) @@ -577,14 +733,18 @@ mod tests { let dir = temp_dir.path(); unix::fs::symlink(dir.join("hello.txt"), dir.join("hola.txt")).unwrap(); - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.into()], ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings, ctx); + workspace.open_path(dir.into(), ctx); + workspace + }); + app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) + .await; // Simultaneously open both the original file and the symlink to the same file. app.update(|ctx| { - workspace_view.update(ctx, |view, ctx| { + workspace.update(ctx, |view, ctx| { view.open_paths(&[dir.join("hello.txt"), dir.join("hola.txt")], ctx) }) }) @@ -592,7 +752,7 @@ mod tests { // The same content shows up with two different editors. let buffer_views = app.read(|ctx| { - workspace_view + workspace .read(ctx) .active_pane() .read(ctx) @@ -635,17 +795,19 @@ mod tests { })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); + let (window_id, workspace) = app.add_window(|ctx| { + let mut workspace = WorkspaceView::new(0, settings, ctx); + workspace.open_path(dir.path().into(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let entries = app.read(|ctx| workspace.file_entries(ctx)); let file1 = entries[0].clone(); - let (window_id, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); - let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone()); + let pane_1 = app.read(|ctx| workspace.read(ctx).active_pane().clone()); - workspace_view + workspace .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx)) .unwrap() .await; @@ -658,14 +820,14 @@ mod tests { app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ()); app.update(|ctx| { - let pane_2 = workspace_view.read(ctx).active_pane().clone(); + let pane_2 = workspace.read(ctx).active_pane().clone(); assert_ne!(pane_1, pane_2); let pane2_item = pane_2.read(ctx).active_item().unwrap(); assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone())); ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ()); - let workspace_view = workspace_view.read(ctx); + let workspace_view = workspace.read(ctx); assert_eq!(workspace_view.panes.len(), 1); assert_eq!(workspace_view.active_pane(), &pane_1); }); diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 58b209cb8c2ca92b4fdd72833b9b4fbd1f93d85e..ea023fb813d49f2260abbea835aa97b07a33470e 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -426,7 +426,7 @@ impl FileHandle { ) { let mut prev_state = self.state.lock().clone(); let cur_state = Arc::downgrade(&self.state); - ctx.observe(&self.worktree, move |observer, worktree, ctx| { + ctx.observe_model(&self.worktree, move |observer, worktree, ctx| { if let Some(cur_state) = cur_state.upgrade() { let cur_state_unlocked = cur_state.lock(); if *cur_state_unlocked != prev_state {