From 8cffa8bdb2cf06589c43f37a6862eebab8ff2eea Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 29 Apr 2021 15:54:17 -0700 Subject: [PATCH 1/8] Move file handle from buffer to buffer view --- zed/src/editor/buffer/mod.rs | 67 ++++++++-------------------- zed/src/editor/buffer_view.rs | 49 ++++++++++++++------- zed/src/workspace/workspace.rs | 25 +++++++---- zed/src/workspace/workspace_view.rs | 68 ++++++++++++++++++++++++++--- zed/src/worktree.rs | 8 ++-- 5 files changed, 136 insertions(+), 81 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 55ffaec02809f4234beca977fcd17f8069ae1c3f..bcc6880916796bff07f2989e6a2e5987af5ef27a 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -18,16 +18,14 @@ use crate::{ worktree::FileHandle, }; use anyhow::{anyhow, Result}; -use gpui::{AppContext, Entity, ModelContext}; +use gpui::{Entity, ModelContext}; use lazy_static::lazy_static; use rand::prelude::*; use std::{ cmp, - ffi::OsString, hash::BuildHasher, iter::{self, Iterator}, ops::{AddAssign, Range}, - path::Path, str, sync::Arc, time::{Duration, Instant}, @@ -59,7 +57,6 @@ type HashMap = std::collections::HashMap; type HashSet = std::collections::HashSet; pub struct Buffer { - file: Option, fragments: SumTree, insertion_splits: HashMap>, pub version: time::Global, @@ -359,24 +356,18 @@ impl Buffer { base_text: T, ctx: &mut ModelContext, ) -> Self { - Self::build(replica_id, None, History::new(base_text.into()), ctx) + Self::build(replica_id, History::new(base_text.into()), ctx) } pub fn from_history( replica_id: ReplicaId, - file: FileHandle, history: History, ctx: &mut ModelContext, ) -> Self { - Self::build(replica_id, Some(file), history, ctx) + Self::build(replica_id, history, ctx) } - fn build( - replica_id: ReplicaId, - file: Option, - history: History, - ctx: &mut ModelContext, - ) -> Self { + fn build(replica_id: ReplicaId, history: History, _: &mut ModelContext) -> Self { let mut insertion_splits = HashMap::default(); let mut fragments = SumTree::new(); @@ -425,12 +416,7 @@ impl Buffer { }); } - if let Some(file) = file.as_ref() { - file.observe_from_model(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged)); - } - Self { - file, fragments, insertion_splits, version: time::Global::new(), @@ -448,41 +434,27 @@ impl Buffer { } } - pub fn file_name(&self, ctx: &AppContext) -> Option { - self.file.as_ref().and_then(|file| file.file_name(ctx)) - } - - pub fn path(&self) -> Option> { - self.file.as_ref().map(|file| file.path()) - } - - pub fn entry_id(&self) -> Option<(usize, Arc)> { - self.file.as_ref().map(|file| file.entry_id()) - } - pub fn snapshot(&self) -> Snapshot { Snapshot { fragments: self.fragments.clone(), } } - pub fn save(&mut self, ctx: &mut ModelContext) -> LocalBoxFuture<'static, Result<()>> { - if let Some(file) = &self.file { - dbg!(file.path()); - - let snapshot = self.snapshot(); - let version = self.version.clone(); - let save_task = file.save(snapshot, ctx.as_ref()); - let task = ctx.spawn(save_task, |me, save_result, ctx| { - if save_result.is_ok() { - me.did_save(version, ctx); - } - save_result - }); - Box::pin(task) - } else { - Box::pin(async { Ok(()) }) - } + pub fn save( + &mut self, + file: &FileHandle, + ctx: &mut ModelContext, + ) -> LocalBoxFuture<'static, Result<()>> { + let snapshot = self.snapshot(); + let version = self.version.clone(); + let save_task = file.save(snapshot, ctx.as_ref()); + let task = ctx.spawn(save_task, |me, save_result, ctx| { + if save_result.is_ok() { + me.did_save(version, ctx); + } + save_result + }); + Box::pin(task) } fn did_save(&mut self, version: time::Global, ctx: &mut ModelContext) { @@ -1763,7 +1735,6 @@ impl Buffer { impl Clone for Buffer { fn clone(&self) -> Self { Self { - file: self.file.clone(), fragments: self.fragments.clone(), insertion_splits: self.insertion_splits.clone(), version: self.version.clone(), diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index bd740831932fd2d8fcede0726aecc6aff64187d9..3b9bee8a2e4365432c88ab41d9923b7305ebd871 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2,7 +2,7 @@ use super::{ buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, Selection, SelectionSetId, ToOffset, ToPoint, }; -use crate::{settings::Settings, watch, workspace}; +use crate::{settings::Settings, watch, workspace, worktree::FileHandle}; use anyhow::Result; use futures_core::future::LocalBoxFuture; use gpui::{ @@ -98,6 +98,7 @@ pub enum SelectAction { pub struct BufferView { handle: WeakViewHandle, buffer: ModelHandle, + file: Option, display_map: ModelHandle, selection_set_id: SelectionSetId, pending_selection: Option, @@ -120,18 +121,23 @@ struct ClipboardSelection { impl BufferView { pub fn single_line(settings: watch::Receiver, ctx: &mut ViewContext) -> Self { let buffer = ctx.add_model(|ctx| Buffer::new(0, String::new(), ctx)); - let mut view = Self::for_buffer(buffer, settings, ctx); + let mut view = Self::for_buffer(buffer, None, settings, ctx); view.single_line = true; view } pub fn for_buffer( buffer: ModelHandle, + file: Option, settings: watch::Receiver, ctx: &mut ViewContext, ) -> Self { settings.notify_view_on_change(ctx); + if let Some(file) = file.as_ref() { + file.observe_from_view(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged)); + } + ctx.observe(&buffer, Self::on_buffer_changed); ctx.subscribe_to_model(&buffer, Self::on_buffer_event); let display_map = ctx.add_model(|ctx| { @@ -157,6 +163,7 @@ impl BufferView { Self { handle: ctx.handle().downgrade(), buffer, + file, display_map, selection_set_id, pending_selection: None, @@ -405,7 +412,7 @@ impl BufferView { Ok(()) } - fn insert(&mut self, text: &String, ctx: &mut ViewContext) { + pub fn insert(&mut self, text: &String, ctx: &mut ViewContext) { let mut offset_ranges = SmallVec::<[Range; 32]>::new(); { let buffer = self.buffer.read(ctx); @@ -1343,9 +1350,10 @@ impl workspace::Item for Buffer { fn build_view( buffer: ModelHandle, settings: watch::Receiver, + file: Option, ctx: &mut ViewContext, ) -> Self::View { - BufferView::for_buffer(buffer, settings, ctx) + BufferView::for_buffer(buffer, file, settings, ctx) } } @@ -1362,28 +1370,39 @@ impl workspace::ItemView for BufferView { } fn title(&self, app: &AppContext) -> std::string::String { - if let Some(name) = self.buffer.read(app).file_name(app) { + let filename = self.file.as_ref().and_then(|file| file.file_name(app)); + if let Some(name) = filename { name.to_string_lossy().into() } else { "untitled".into() } } - fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc)> { - self.buffer.read(app).entry_id() + fn entry_id(&self, _: &AppContext) -> Option<(usize, Arc)> { + self.file.as_ref().map(|file| file.entry_id()) } fn clone_on_split(&self, ctx: &mut ViewContext) -> Option where Self: Sized, { - let clone = BufferView::for_buffer(self.buffer.clone(), self.settings.clone(), ctx); + let clone = BufferView::for_buffer( + self.buffer.clone(), + self.file.clone(), + self.settings.clone(), + ctx, + ); *clone.scroll_position.lock() = *self.scroll_position.lock(); Some(clone) } fn save(&self, ctx: &mut ViewContext) -> LocalBoxFuture<'static, Result<()>> { - self.buffer.update(ctx, |buffer, ctx| buffer.save(ctx)) + if let Some(file) = self.file.as_ref() { + self.buffer + .update(ctx, |buffer, ctx| buffer.save(file, ctx)) + } else { + Box::pin(async { Ok(()) }) + } } fn is_dirty(&self, ctx: &AppContext) -> bool { @@ -1406,7 +1425,7 @@ mod tests { app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, buffer_view) = - app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); buffer_view.update(app, |view, ctx| { view.begin_selection(DisplayPoint::new(2, 2), false, ctx); @@ -1521,7 +1540,7 @@ mod tests { let settings = settings::channel(&font_cache).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); let layouts = view .read(app) @@ -1560,7 +1579,7 @@ mod tests { }); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( @@ -1632,7 +1651,7 @@ mod tests { let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); buffer.update(app, |buffer, ctx| { buffer.edit( @@ -1675,7 +1694,7 @@ mod tests { }); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( @@ -1706,7 +1725,7 @@ mod tests { let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; let view = app - .add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)) + .add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)) .1; // Cut with three selections. Clipboard text is divided into three slices. diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index 0f0272957260f4c4dbb952ab44eb3ab3475ea0a4..75bec37eb827f979a6aec0bf050938466fe982ba 100644 --- a/zed/src/workspace/workspace.rs +++ b/zed/src/workspace/workspace.rs @@ -4,7 +4,7 @@ use crate::{ settings::Settings, time::ReplicaId, watch, - worktree::{Worktree, WorktreeHandle as _}, + worktree::{FileHandle, Worktree, WorktreeHandle as _}, }; use anyhow::anyhow; use gpui::{AppContext, Entity, Handle, ModelContext, ModelHandle, MutableAppContext, ViewContext}; @@ -25,6 +25,7 @@ where fn build_view( handle: ModelHandle, settings: watch::Receiver, + file: Option, ctx: &mut ViewContext, ) -> Self::View; } @@ -34,6 +35,7 @@ pub trait ItemHandle: Debug + Send + Sync { &self, window_id: usize, settings: watch::Receiver, + file: Option, app: &mut MutableAppContext, ) -> Box; fn id(&self) -> usize; @@ -45,9 +47,12 @@ impl ItemHandle for ModelHandle { &self, window_id: usize, settings: watch::Receiver, + file: Option, app: &mut MutableAppContext, ) -> Box { - Box::new(app.add_view(window_id, |ctx| T::build_view(self.clone(), settings, ctx))) + Box::new(app.add_view(window_id, |ctx| { + T::build_view(self.clone(), settings, file, ctx) + })) } fn id(&self) -> usize { @@ -148,7 +153,7 @@ impl Workspace { &mut self, (worktree_id, path): (usize, Arc), ctx: &mut ModelContext<'_, Self>, - ) -> anyhow::Result + Send>>> { + ) -> anyhow::Result + Send>>> { let worktree = self .worktrees .get(&worktree_id) @@ -160,18 +165,20 @@ impl Workspace { .inode_for_path(&path) .ok_or_else(|| anyhow!("path {:?} does not exist", path))?; + let file = worktree.file(path.clone(), ctx.as_ref())?; + let item_key = (worktree_id, inode); if let Some(item) = self.items.get(&item_key).cloned() { return Ok(async move { match item { OpenedItem::Loaded(handle) => { - return Ok(handle); + return (Ok(handle), file); } OpenedItem::Loading(rx) => loop { rx.updated().await; if let Some(result) = smol::block_on(rx.read()).clone() { - return result; + return (result, file); } }, } @@ -180,7 +187,6 @@ impl Workspace { } let replica_id = self.replica_id; - let file = worktree.file(path.clone(), ctx.as_ref())?; let history = file.load_history(ctx.as_ref()); let (mut tx, rx) = watch::channel(None); @@ -190,7 +196,7 @@ impl Workspace { move |me, history: anyhow::Result, ctx| match history { Ok(history) => { let handle = Box::new( - ctx.add_model(|ctx| Buffer::from_history(replica_id, file, history, ctx)), + ctx.add_model(|ctx| Buffer::from_history(replica_id, history, ctx)), ) as Box; me.items .insert(item_key, OpenedItem::Loaded(handle.clone())); @@ -282,14 +288,15 @@ mod tests { ) }); - let handle_1 = future_1.await.unwrap(); - let handle_2 = future_2.await.unwrap(); + let handle_1 = future_1.await.0.unwrap(); + let handle_2 = future_2.await.0.unwrap(); assert_eq!(handle_1.id(), handle_2.id()); // Open the same entry again now that it has loaded let handle_3 = workspace .update(&mut app, |w, app| w.open_entry(entry, app).unwrap()) .await + .0 .unwrap(); assert_eq!(handle_3.id(), handle_1.id()); diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 6815a1e2c95778da781e5a671d802489dbc4e593..3e54a5d998b9a18eb22a567ddc7f78cd14eae078 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -250,13 +250,14 @@ impl WorkspaceView { error!("{}", error); None } - Ok(item) => { + Ok(future) => { let settings = self.settings.clone(); - Some(ctx.spawn(item, move |me, item, ctx| { + Some(ctx.spawn(future, move |me, (item, file), ctx| { me.loading_entries.remove(&entry); match item { Ok(item) => { - let item_view = item.add_view(ctx.window_id(), settings, ctx.as_mut()); + let item_view = + item.add_view(ctx.window_id(), settings, Some(file), ctx.as_mut()); me.add_item(item_view, ctx); } Err(error) => { @@ -417,10 +418,10 @@ impl View for WorkspaceView { #[cfg(test)] mod tests { use super::{pane, Workspace, WorkspaceView}; - use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _}; + use crate::{editor::BufferView, settings, test::temp_tree, workspace::WorkspaceHandle as _}; use gpui::App; use serde_json::json; - use std::collections::HashSet; + use std::{collections::HashSet, os::unix}; #[test] fn test_open_entry() { @@ -575,6 +576,63 @@ mod tests { }); } + #[test] + fn test_open_two_paths_to_the_same_file() { + use crate::workspace::ItemViewHandle; + + App::test_async((), |mut app| async move { + // Create a worktree with a symlink: + // dir + // ├── hello.txt + // └── hola.txt -> hello.txt + let temp_dir = temp_tree(json!({ "hello.txt": "hi" })); + 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)); + + // Simultaneously open both the original file and the symlink to the same file. + app.update(|ctx| { + workspace_view.update(ctx, |view, ctx| { + view.open_paths(&[dir.join("hello.txt"), dir.join("hola.txt")], ctx) + }) + }) + .await; + + // The same content shows up with two different editors. + let buffer_views = app.read(|ctx| { + workspace_view + .read(ctx) + .active_pane() + .read(ctx) + .items() + .iter() + .map(|i| i.to_any().downcast::().unwrap()) + .collect::>() + }); + app.read(|ctx| { + assert_eq!(buffer_views[0].title(ctx), "hello.txt"); + assert_eq!(buffer_views[1].title(ctx), "hola.txt"); + assert_eq!(buffer_views[0].read(ctx).text(ctx), "hi"); + assert_eq!(buffer_views[1].read(ctx).text(ctx), "hi"); + }); + + // When modifying one buffer, the changes appear in both editors. + app.update(|ctx| { + buffer_views[0].update(ctx, |buf, ctx| { + buf.insert(&"oh, ".to_string(), ctx); + }); + }); + app.read(|ctx| { + assert_eq!(buffer_views[0].read(ctx).text(ctx), "oh, hi"); + assert_eq!(buffer_views[1].read(ctx).text(ctx), "oh, hi"); + }); + }); + } + #[test] fn test_pane_actions() { App::test_async((), |mut app| async move { diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 7152316938a9a7b917d11c6ef61d18cbeb79d53c..e945fbb876079a2849eefa667ede243307a73852 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -9,7 +9,7 @@ use crate::{ use ::ignore::gitignore::Gitignore; use anyhow::{anyhow, Context, Result}; pub use fuzzy::{match_paths, PathMatch}; -use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task}; +use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task, View, ViewContext}; use lazy_static::lazy_static; use parking_lot::Mutex; use postage::{ @@ -419,10 +419,10 @@ impl FileHandle { (self.worktree.id(), self.path()) } - pub fn observe_from_model( + pub fn observe_from_view( &self, - ctx: &mut ModelContext, - mut callback: impl FnMut(&mut T, FileHandle, &mut ModelContext) + 'static, + ctx: &mut ViewContext, + mut callback: impl FnMut(&mut T, FileHandle, &mut ViewContext) + 'static, ) { let mut prev_state = self.state.lock().clone(); let cur_state = Arc::downgrade(&self.state); From afb623b6b5527d59c32b265b592b4a3a080f3b51 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 16:25:18 -0600 Subject: [PATCH 2/8] Make Workspace::open_entry2, which returns a dyn ItemViewHandle Co-Authored-By: Max Brunsfeld --- CargoPants.toml | 1 + zed/src/editor/buffer/mod.rs | 50 ++++++-------- zed/src/editor/buffer_view.rs | 22 +++--- zed/src/editor/display_map/fold_map.rs | 12 ++-- zed/src/editor/display_map/mod.rs | 4 +- zed/src/workspace/workspace.rs | 95 ++++++++++++++++++++++++-- zed/src/workspace/workspace_view.rs | 34 +++------ zed/src/worktree.rs | 16 ++--- 8 files changed, 144 insertions(+), 90 deletions(-) create mode 120000 CargoPants.toml diff --git a/CargoPants.toml b/CargoPants.toml new file mode 120000 index 0000000000000000000000000000000000000000..f036ca3dce0a01168f0a72fbcf3fd675a1ded33d --- /dev/null +++ b/CargoPants.toml @@ -0,0 +1 @@ +Cargo.toml \ No newline at end of file diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index bcc6880916796bff07f2989e6a2e5987af5ef27a..c462472a393ff2c09b2d38658c31b6704d7cc16c 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -351,23 +351,15 @@ pub struct UndoOperation { } impl Buffer { - pub fn new>>( - replica_id: ReplicaId, - base_text: T, - ctx: &mut ModelContext, - ) -> Self { - Self::build(replica_id, History::new(base_text.into()), ctx) + pub fn new>>(replica_id: ReplicaId, base_text: T) -> Self { + Self::build(replica_id, History::new(base_text.into())) } - pub fn from_history( - replica_id: ReplicaId, - history: History, - ctx: &mut ModelContext, - ) -> Self { - Self::build(replica_id, history, ctx) + pub fn from_history(replica_id: ReplicaId, history: History) -> Self { + Self::build(replica_id, history) } - fn build(replica_id: ReplicaId, history: History, _: &mut ModelContext) -> Self { + fn build(replica_id: ReplicaId, history: History) -> Self { let mut insertion_splits = HashMap::default(); let mut fragments = SumTree::new(); @@ -2304,8 +2296,8 @@ mod tests { #[test] fn test_edit() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "abc", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, "abc"); assert_eq!(buffer.text(), "abc"); buffer.edit(vec![3..3], "def", None).unwrap(); assert_eq!(buffer.text(), "abcdef"); @@ -2329,8 +2321,8 @@ mod tests { let buffer_1_events = Rc::new(RefCell::new(Vec::new())); let buffer_2_events = Rc::new(RefCell::new(Vec::new())); - let buffer1 = app.add_model(|ctx| Buffer::new(0, "abcdef", ctx)); - let buffer2 = app.add_model(|ctx| Buffer::new(1, "abcdef", ctx)); + let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef")); + let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef")); let mut buffer_ops = Vec::new(); buffer1.update(app, |buffer, ctx| { let buffer_1_events = buffer_1_events.clone(); @@ -2417,7 +2409,7 @@ mod tests { .take(reference_string_len) .collect::(); ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, reference_string.as_str(), ctx); + let mut buffer = Buffer::new(0, reference_string.as_str()); let mut buffer_versions = Vec::new(); for _i in 0..10 { let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None); @@ -2503,7 +2495,7 @@ mod tests { fn test_line_len() { App::test((), |ctx| { ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); buffer.edit(vec![18..18], "\npqrs\n", None).unwrap(); @@ -2525,7 +2517,7 @@ mod tests { fn test_rightmost_point() { App::test((), |ctx| { ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); assert_eq!(buffer.rightmost_point().row, 0); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); assert_eq!(buffer.rightmost_point().row, 0); @@ -2546,7 +2538,7 @@ mod tests { fn test_text_summary_for_range() { App::test((), |ctx| { ctx.add_model(|ctx| { - let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx); + let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz"); let text = Text::from(buffer.text()); assert_eq!( buffer.text_summary_for_range(1..3), @@ -2577,7 +2569,7 @@ mod tests { fn test_chars_at() { App::test((), |ctx| { ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap(); buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); buffer.edit(vec![18..18], "\npqrs", None).unwrap(); @@ -2599,7 +2591,7 @@ mod tests { assert_eq!(chars.collect::(), "PQrs"); // Regression test: - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None).unwrap(); buffer.edit(vec![60..60], "\n", None).unwrap(); @@ -2729,7 +2721,7 @@ mod tests { fn test_anchors() { App::test((), |ctx| { ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abc", None).unwrap(); let left_anchor = buffer.anchor_before(2).unwrap(); let right_anchor = buffer.anchor_after(2).unwrap(); @@ -2894,7 +2886,7 @@ mod tests { fn test_anchors_at_start_and_end() { App::test((), |ctx| { ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); let before_start_anchor = buffer.anchor_before(0).unwrap(); let after_end_anchor = buffer.anchor_after(0).unwrap(); @@ -2921,7 +2913,7 @@ mod tests { #[test] fn test_is_modified() { App::test((), |app| { - let model = app.add_model(|ctx| Buffer::new(0, "abc", ctx)); + let model = app.add_model(|ctx| Buffer::new(0, "abc")); let events = Rc::new(RefCell::new(Vec::new())); // initially, the buffer isn't dirty. @@ -3009,7 +3001,7 @@ mod tests { fn test_undo_redo() { App::test((), |app| { app.add_model(|ctx| { - let mut buffer = Buffer::new(0, "1234", ctx); + let mut buffer = Buffer::new(0, "1234"); let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap(); let edit2 = buffer.edit(vec![3..4], "yzef", None).unwrap(); @@ -3047,7 +3039,7 @@ mod tests { App::test((), |app| { app.add_model(|ctx| { let mut now = Instant::now(); - let mut buffer = Buffer::new(0, "123456", ctx); + let mut buffer = Buffer::new(0, "123456"); let (set_id, _) = buffer .add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None); @@ -3132,7 +3124,7 @@ mod tests { let mut network = Network::new(); for i in 0..PEERS { let buffer = - ctx.add_model(|ctx| Buffer::new(i as ReplicaId, base_text.as_str(), ctx)); + ctx.add_model(|ctx| Buffer::new(i as ReplicaId, base_text.as_str())); buffers.push(buffer); replica_ids.push(i as u16); network.add_peer(i as u16); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 3b9bee8a2e4365432c88ab41d9923b7305ebd871..dd5c73ba0b409d6810241a65f35e2f39f08658fb 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -120,7 +120,7 @@ struct ClipboardSelection { impl BufferView { pub fn single_line(settings: watch::Receiver, ctx: &mut ViewContext) -> Self { - let buffer = ctx.add_model(|ctx| Buffer::new(0, String::new(), ctx)); + let buffer = ctx.add_model(|_| Buffer::new(0, String::new())); let mut view = Self::for_buffer(buffer, None, settings, ctx); view.single_line = true; view @@ -1421,8 +1421,7 @@ mod tests { #[test] fn test_selection_with_mouse() { App::test((), |app| { - let buffer = - app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx)); + let buffer = app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, buffer_view) = app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); @@ -1536,7 +1535,7 @@ mod tests { let layout_cache = TextLayoutCache::new(app.platform().fonts()); let font_cache = app.font_cache().clone(); - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6))); let settings = settings::channel(&font_cache).unwrap().1; let (_, view) = @@ -1553,7 +1552,7 @@ mod tests { #[test] fn test_fold() { App::test((), |app| { - let buffer = app.add_model(|ctx| { + let buffer = app.add_model(|_| { Buffer::new( 0, " @@ -1574,7 +1573,6 @@ mod tests { } " .unindent(), - ctx, ) }); let settings = settings::channel(&app.font_cache()).unwrap().1; @@ -1648,7 +1646,7 @@ mod tests { #[test] fn test_move_cursor() -> Result<()> { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6))); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); @@ -1685,12 +1683,8 @@ mod tests { #[test] fn test_backspace() { App::test((), |app| { - let buffer = app.add_model(|ctx| { - Buffer::new( - 0, - "one two three\nfour five six\nseven eight nine\nten\n", - ctx, - ) + let buffer = app.add_model(|_| { + Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n") }); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = @@ -1722,7 +1716,7 @@ mod tests { #[test] fn test_clipboard() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "one two three four five six ")); let settings = settings::channel(&app.font_cache()).unwrap().1; let view = app .add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 536971a87d13cffc33fc9e63a0d64ae3e2f02099..3c52adcbaa1fdd57972d467173a46d6e187ecb97 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -471,7 +471,7 @@ mod tests { #[test] fn test_basic_folds() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( @@ -522,7 +522,7 @@ mod tests { #[test] fn test_overlapping_folds() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( vec![ @@ -541,7 +541,7 @@ mod tests { #[test] fn test_merging_folds_via_edit() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( @@ -589,10 +589,10 @@ mod tests { let mut rng = StdRng::seed_from_u64(seed); App::test((), |app| { - let buffer = app.add_model(|ctx| { + let buffer = app.add_model(|_| { let len = rng.gen_range(0..10); let text = RandomCharIter::new(&mut rng).take(len).collect::(); - Buffer::new(0, text, ctx) + Buffer::new(0, text) }); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); @@ -664,7 +664,7 @@ mod tests { fn test_buffer_rows() { App::test((), |app| { let text = sample_text(6, 6) + "\n"; - let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, text)); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index c44f3ca302396f95159b1540d8bf507b1cfcceb5..8142a8925cc9d4b92862276ff92d6048941f75b1 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -298,7 +298,7 @@ mod tests { fn test_chars_at() { App::test((), |app| { let text = sample_text(6, 6); - let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, text)); let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx)); buffer .update(app, |buffer, ctx| { @@ -365,7 +365,7 @@ mod tests { #[test] fn test_max_point() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb")); let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx)); assert_eq!( map.read(app).max_point(app.as_ref()), diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index 75bec37eb827f979a6aec0bf050938466fe982ba..212682ffce7eea58759d6aca7eb1876f346ef329 100644 --- a/zed/src/workspace/workspace.rs +++ b/zed/src/workspace/workspace.rs @@ -1,14 +1,16 @@ use super::{ItemView, ItemViewHandle}; use crate::{ - editor::{Buffer, History}, + editor::{Buffer, BufferView, History}, settings::Settings, time::ReplicaId, watch, worktree::{FileHandle, Worktree, WorktreeHandle as _}, }; use anyhow::anyhow; +use futures_core::future::LocalBoxFuture; use gpui::{AppContext, Entity, Handle, ModelContext, ModelHandle, MutableAppContext, ViewContext}; use smol::prelude::*; +use std::{collections::hash_map::Entry, future}; use std::{ collections::{HashMap, HashSet}, fmt::Debug, @@ -82,14 +84,19 @@ pub struct Workspace { replica_id: ReplicaId, worktrees: HashSet>, items: HashMap<(usize, u64), OpenedItem>, + 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: HashSet::new(), - items: HashMap::new(), + worktrees: Default::default(), + items: Default::default(), + buffers: Default::default(), }; workspace.open_paths(&paths, ctx); workspace @@ -149,6 +156,78 @@ impl Workspace { (worktree_id, Path::new("").into()) } + pub fn open_entry2( + &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() + } + pub fn open_entry( &mut self, (worktree_id, path): (usize, Arc), @@ -165,7 +244,9 @@ impl Workspace { .inode_for_path(&path) .ok_or_else(|| anyhow!("path {:?} does not exist", path))?; - let file = worktree.file(path.clone(), ctx.as_ref())?; + let file = worktree + .file(path.clone(), ctx.as_ref()) + .ok_or_else(|| anyhow!("path {:?} does not exist", path))?; let item_key = (worktree_id, inode); if let Some(item) = self.items.get(&item_key).cloned() { @@ -195,9 +276,9 @@ impl Workspace { history, move |me, history: anyhow::Result, ctx| match history { Ok(history) => { - let handle = Box::new( - ctx.add_model(|ctx| Buffer::from_history(replica_id, history, ctx)), - ) as Box; + let handle = + Box::new(ctx.add_model(|_| Buffer::from_history(replica_id, history))) + as Box; me.items .insert(item_key, OpenedItem::Loaded(handle.clone())); ctx.spawn( diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 3e54a5d998b9a18eb22a567ddc7f78cd14eae078..8ac38ade12212169209cdae68741798158463b9c 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -243,30 +243,18 @@ impl WorkspaceView { self.loading_entries.insert(entry.clone()); - match self.workspace.update(ctx, |workspace, ctx| { - workspace.open_entry(entry.clone(), ctx) - }) { - Err(error) => { - error!("{}", error); - None - } - Ok(future) => { - let settings = self.settings.clone(); - Some(ctx.spawn(future, move |me, (item, file), ctx| { - me.loading_entries.remove(&entry); - match item { - Ok(item) => { - let item_view = - item.add_view(ctx.window_id(), settings, Some(file), ctx.as_mut()); - me.add_item(item_view, ctx); - } - Err(error) => { - error!("{}", error); - } - } - })) + let window_id = ctx.window_id(); + let future = self.workspace.update(ctx, |workspace, ctx| { + workspace.open_entry2(entry.clone(), window_id, self.settings.clone(), ctx) + }); + + Some(ctx.spawn(future, move |me, item_view, ctx| { + me.loading_entries.remove(&entry); + match item_view { + Ok(item_view) => me.add_item(item_view, ctx), + Err(error) => log::error!("error opening item: {}", error), } - } + })) } pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext) { diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index e945fbb876079a2849eefa667ede243307a73852..58b209cb8c2ca92b4fdd72833b9b4fbd1f93d85e 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -7,7 +7,7 @@ use crate::{ sum_tree::{self, Cursor, Edit, SeekBias, SumTree}, }; use ::ignore::gitignore::Gitignore; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; pub use fuzzy::{match_paths, PathMatch}; use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task, View, ViewContext}; use lazy_static::lazy_static; @@ -1126,15 +1126,14 @@ struct UpdateIgnoreStatusJob { } pub trait WorktreeHandle { - fn file(&self, path: impl AsRef, app: &AppContext) -> Result; + fn file(&self, path: impl AsRef, app: &AppContext) -> Option; } impl WorktreeHandle for ModelHandle { - fn file(&self, path: impl AsRef, app: &AppContext) -> Result { + fn file(&self, path: impl AsRef, app: &AppContext) -> Option { let tree = self.read(app); - let entry = tree - .entry_for_path(&path) - .ok_or_else(|| anyhow!("path does not exist in tree"))?; + let entry = tree.entry_for_path(&path)?; + let path = entry.path().clone(); let mut handles = tree.handles.lock(); let state = if let Some(state) = handles.get(&path).and_then(Weak::upgrade) { @@ -1148,7 +1147,7 @@ impl WorktreeHandle for ModelHandle { state }; - Ok(FileHandle { + Some(FileHandle { worktree: self.clone(), state, }) @@ -1347,8 +1346,7 @@ mod tests { app.read(|ctx| tree.read(ctx).scan_complete()).await; app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1)); - let buffer = - app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx)); + let buffer = app.add_model(|_| Buffer::new(1, "a line of text.\n".repeat(10 * 1024))); let path = tree.update(&mut app, |tree, ctx| { let path = tree.files(0).next().unwrap().path().clone(); From b801628230709db6d76882950607e054a7e35498 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 16:31:01 -0600 Subject: [PATCH 3/8] Clean up Co-Authored-By: Max Brunsfeld --- CargoPants.toml | 1 - zed/src/editor/buffer/mod.rs | 22 +-- zed/src/editor/buffer_view.rs | 15 +- zed/src/editor/display_map/fold_map.rs | 2 +- zed/src/workspace/workspace.rs | 202 +------------------------ zed/src/workspace/workspace_view.rs | 2 +- 6 files changed, 19 insertions(+), 225 deletions(-) delete mode 120000 CargoPants.toml diff --git a/CargoPants.toml b/CargoPants.toml deleted file mode 120000 index f036ca3dce0a01168f0a72fbcf3fd675a1ded33d..0000000000000000000000000000000000000000 --- a/CargoPants.toml +++ /dev/null @@ -1 +0,0 @@ -Cargo.toml \ No newline at end of file diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index c462472a393ff2c09b2d38658c31b6704d7cc16c..59b0bf9b36cee6bab3d03b00e3212d81b85fcd9e 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -2408,7 +2408,7 @@ mod tests { let mut reference_string = RandomCharIter::new(&mut rng) .take(reference_string_len) .collect::(); - ctx.add_model(|ctx| { + ctx.add_model(|_| { let mut buffer = Buffer::new(0, reference_string.as_str()); let mut buffer_versions = Vec::new(); for _i in 0..10 { @@ -2494,7 +2494,7 @@ mod tests { #[test] fn test_line_len() { App::test((), |ctx| { - ctx.add_model(|ctx| { + ctx.add_model(|_| { let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); @@ -2516,7 +2516,7 @@ mod tests { #[test] fn test_rightmost_point() { App::test((), |ctx| { - ctx.add_model(|ctx| { + ctx.add_model(|_| { let mut buffer = Buffer::new(0, ""); assert_eq!(buffer.rightmost_point().row, 0); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); @@ -2537,7 +2537,7 @@ mod tests { #[test] fn test_text_summary_for_range() { App::test((), |ctx| { - ctx.add_model(|ctx| { + ctx.add_model(|_| { let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz"); let text = Text::from(buffer.text()); assert_eq!( @@ -2568,7 +2568,7 @@ mod tests { #[test] fn test_chars_at() { App::test((), |ctx| { - ctx.add_model(|ctx| { + ctx.add_model(|_| { let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap(); buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); @@ -2720,7 +2720,7 @@ mod tests { #[test] fn test_anchors() { App::test((), |ctx| { - ctx.add_model(|ctx| { + ctx.add_model(|_| { let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abc", None).unwrap(); let left_anchor = buffer.anchor_before(2).unwrap(); @@ -2885,7 +2885,7 @@ mod tests { #[test] fn test_anchors_at_start_and_end() { App::test((), |ctx| { - ctx.add_model(|ctx| { + ctx.add_model(|_| { let mut buffer = Buffer::new(0, ""); let before_start_anchor = buffer.anchor_before(0).unwrap(); let after_end_anchor = buffer.anchor_after(0).unwrap(); @@ -2913,7 +2913,7 @@ mod tests { #[test] fn test_is_modified() { App::test((), |app| { - let model = app.add_model(|ctx| Buffer::new(0, "abc")); + let model = app.add_model(|_| Buffer::new(0, "abc")); let events = Rc::new(RefCell::new(Vec::new())); // initially, the buffer isn't dirty. @@ -3000,7 +3000,7 @@ mod tests { #[test] fn test_undo_redo() { App::test((), |app| { - app.add_model(|ctx| { + app.add_model(|_| { let mut buffer = Buffer::new(0, "1234"); let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap(); @@ -3037,7 +3037,7 @@ mod tests { #[test] fn test_history() { App::test((), |app| { - app.add_model(|ctx| { + app.add_model(|_| { let mut now = Instant::now(); let mut buffer = Buffer::new(0, "123456"); @@ -3124,7 +3124,7 @@ mod tests { let mut network = Network::new(); for i in 0..PEERS { let buffer = - ctx.add_model(|ctx| Buffer::new(i as ReplicaId, base_text.as_str())); + ctx.add_model(|_| Buffer::new(i as ReplicaId, base_text.as_str())); buffers.push(buffer); replica_ids.push(i as u16); network.add_peer(i as u16); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index dd5c73ba0b409d6810241a65f35e2f39f08658fb..b5eaf7c4ec905c4452a388b5fc2f7a215b212458 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1344,19 +1344,6 @@ impl View for BufferView { } } -impl workspace::Item for Buffer { - type View = BufferView; - - fn build_view( - buffer: ModelHandle, - settings: watch::Receiver, - file: Option, - ctx: &mut ViewContext, - ) -> Self::View { - BufferView::for_buffer(buffer, file, settings, ctx) - } -} - impl workspace::ItemView for BufferView { fn should_activate_item_on_event(event: &Self::Event) -> bool { matches!(event, Event::Activate) @@ -1421,7 +1408,7 @@ mod tests { #[test] fn test_selection_with_mouse() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); + let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, buffer_view) = app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 3c52adcbaa1fdd57972d467173a46d6e187ecb97..58f51cee94096d5c256ddce7804fef9e5be8d781 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -471,7 +471,7 @@ mod tests { #[test] fn test_basic_folds() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6))); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index 212682ffce7eea58759d6aca7eb1876f346ef329..58f9786eb5b03cfb57f99900abbf287611f8d7b0 100644 --- a/zed/src/workspace/workspace.rs +++ b/zed/src/workspace/workspace.rs @@ -1,89 +1,25 @@ -use super::{ItemView, ItemViewHandle}; +use super::ItemViewHandle; use crate::{ - editor::{Buffer, BufferView, History}, + editor::{Buffer, BufferView}, settings::Settings, time::ReplicaId, watch, - worktree::{FileHandle, Worktree, WorktreeHandle as _}, + worktree::{Worktree, WorktreeHandle as _}, }; use anyhow::anyhow; use futures_core::future::LocalBoxFuture; -use gpui::{AppContext, Entity, Handle, ModelContext, ModelHandle, MutableAppContext, ViewContext}; +use gpui::{AppContext, Entity, ModelContext, ModelHandle}; use smol::prelude::*; use std::{collections::hash_map::Entry, future}; use std::{ collections::{HashMap, HashSet}, - fmt::Debug, path::{Path, PathBuf}, - pin::Pin, sync::Arc, }; -pub trait Item -where - Self: Sized, -{ - type View: ItemView; - fn build_view( - handle: ModelHandle, - settings: watch::Receiver, - file: Option, - ctx: &mut ViewContext, - ) -> Self::View; -} - -pub trait ItemHandle: Debug + Send + Sync { - fn add_view( - &self, - window_id: usize, - settings: watch::Receiver, - file: Option, - app: &mut MutableAppContext, - ) -> Box; - fn id(&self) -> usize; - fn boxed_clone(&self) -> Box; -} - -impl ItemHandle for ModelHandle { - fn add_view( - &self, - window_id: usize, - settings: watch::Receiver, - file: Option, - app: &mut MutableAppContext, - ) -> Box { - Box::new(app.add_view(window_id, |ctx| { - T::build_view(self.clone(), settings, file, ctx) - })) - } - - fn id(&self) -> usize { - Handle::id(self) - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.boxed_clone() - } -} - -pub type OpenResult = Result, Arc>; - -#[derive(Clone)] -enum OpenedItem { - Loading(watch::Receiver>), - Loaded(Box), -} - pub struct Workspace { replica_id: ReplicaId, worktrees: HashSet>, - items: HashMap<(usize, u64), OpenedItem>, buffers: HashMap< (usize, u64), postage::watch::Receiver, Arc>>>, @@ -95,7 +31,6 @@ impl Workspace { let mut workspace = Self { replica_id: 0, worktrees: Default::default(), - items: Default::default(), buffers: Default::default(), }; workspace.open_paths(&paths, ctx); @@ -156,7 +91,7 @@ impl Workspace { (worktree_id, Path::new("").into()) } - pub fn open_entry2( + pub fn open_entry( &mut self, (worktree_id, path): (usize, Arc), window_id: usize, @@ -228,83 +163,6 @@ impl Workspace { .boxed_local() } - pub fn open_entry( - &mut self, - (worktree_id, path): (usize, Arc), - ctx: &mut ModelContext<'_, Self>, - ) -> anyhow::Result + Send>>> { - let worktree = self - .worktrees - .get(&worktree_id) - .cloned() - .ok_or_else(|| anyhow!("worktree {} does not exist", worktree_id,))?; - - let inode = worktree - .read(ctx) - .inode_for_path(&path) - .ok_or_else(|| anyhow!("path {:?} does not exist", path))?; - - let file = worktree - .file(path.clone(), ctx.as_ref()) - .ok_or_else(|| anyhow!("path {:?} does not exist", path))?; - - let item_key = (worktree_id, inode); - if let Some(item) = self.items.get(&item_key).cloned() { - return Ok(async move { - match item { - OpenedItem::Loaded(handle) => { - return (Ok(handle), file); - } - OpenedItem::Loading(rx) => loop { - rx.updated().await; - - if let Some(result) = smol::block_on(rx.read()).clone() { - return (result, file); - } - }, - } - } - .boxed()); - } - - let replica_id = self.replica_id; - let history = file.load_history(ctx.as_ref()); - - let (mut tx, rx) = watch::channel(None); - self.items.insert(item_key, OpenedItem::Loading(rx)); - ctx.spawn( - history, - move |me, history: anyhow::Result, ctx| match history { - Ok(history) => { - let handle = - Box::new(ctx.add_model(|_| Buffer::from_history(replica_id, history))) - as Box; - me.items - .insert(item_key, OpenedItem::Loaded(handle.clone())); - ctx.spawn( - async move { - tx.update(|value| *value = Some(Ok(handle))).await; - }, - |_, _, _| {}, - ) - .detach(); - } - Err(error) => { - ctx.spawn( - async move { - tx.update(|value| *value = Some(Err(Arc::new(error)))).await; - }, - |_, _, _| {}, - ) - .detach(); - } - }, - ) - .detach(); - - self.open_entry((worktree_id, path), ctx) - } - fn on_worktree_updated(&mut self, _: ModelHandle, ctx: &mut ModelContext) { ctx.notify(); } @@ -334,53 +192,3 @@ impl WorkspaceHandle for ModelHandle { .collect::>() } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::temp_tree; - use gpui::App; - use serde_json::json; - - #[test] - fn test_open_entry() { - App::test_async((), |mut app| async move { - let dir = temp_tree(json!({ - "a": { - "aa": "aa contents", - "ab": "ab contents", - }, - })); - - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); - app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) - .await; - - // Get the first file entry. - let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone()); - let path = app.read(|ctx| tree.read(ctx).files(0).next().unwrap().path().clone()); - let entry = (tree.id(), path); - - // Open the same entry twice before it finishes loading. - let (future_1, future_2) = workspace.update(&mut app, |w, app| { - ( - w.open_entry(entry.clone(), app).unwrap(), - w.open_entry(entry.clone(), app).unwrap(), - ) - }); - - let handle_1 = future_1.await.0.unwrap(); - let handle_2 = future_2.await.0.unwrap(); - assert_eq!(handle_1.id(), handle_2.id()); - - // Open the same entry again now that it has loaded - let handle_3 = workspace - .update(&mut app, |w, app| w.open_entry(entry, app).unwrap()) - .await - .0 - .unwrap(); - - assert_eq!(handle_3.id(), handle_1.id()); - }) - } -} diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 8ac38ade12212169209cdae68741798158463b9c..8df9be4d57fb4f67f0b70986b618e8bc26d9efe7 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -245,7 +245,7 @@ impl WorkspaceView { let window_id = ctx.window_id(); let future = self.workspace.update(ctx, |workspace, ctx| { - workspace.open_entry2(entry.clone(), window_id, self.settings.clone(), ctx) + workspace.open_entry(entry.clone(), window_id, self.settings.clone(), ctx) }); Some(ctx.spawn(future, move |me, item_view, ctx| { From ed28bd3f95658a54048eb59ea6f57174a1eb2689 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 16:57:13 -0600 Subject: [PATCH 4/8] 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 { From 366f8fcdb077aaaae604952c86e86e9311271fe0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 20:36:12 -0600 Subject: [PATCH 5/8] Inline methods previously moved from Workspace --- zed/src/workspace/workspace_view.rs | 87 +++++++++++------------------ 1 file changed, 34 insertions(+), 53 deletions(-) diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 91e1e3940395b833656f47e7f05c083113972928..2b89ba4d60c7b085e3786b7b54243aa7326e0b87 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -6,7 +6,6 @@ use crate::{ 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, @@ -17,7 +16,6 @@ use log::error; use smol::prelude::*; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, - future, path::{Path, PathBuf}, sync::Arc, }; @@ -204,7 +202,11 @@ impl WorkspaceView { paths: &[PathBuf], ctx: &mut ViewContext, ) -> impl Future { - let entries = self.open_paths2(paths, ctx); + let entries = paths + .iter() + .cloned() + .map(|path| self.open_path(path, ctx)) + .collect::>(); let bg = ctx.background_executor().clone(); let tasks = paths @@ -233,18 +235,6 @@ 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()) { @@ -304,54 +294,34 @@ impl WorkspaceView { return None; } - self.loading_entries.insert(entry.clone()); - - let window_id = ctx.window_id(); - let future = self.open_entry2(entry.clone(), window_id, self.settings.clone(), ctx); + let (worktree_id, path) = entry.clone(); - Some(ctx.spawn(future, move |me, item_view, ctx| { - me.loading_entries.remove(&entry); - match item_view { - Ok(item_view) => me.add_item(item_view, ctx), - Err(error) => log::error!("error opening item: {}", error), - } - })) - } - - 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(); + log::error!("worktree {} does not exist", worktree_id); + return None; } }; 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(); + log::error!("path {:?} does not exist", path); + return None; } }; 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() + log::error!("path {:?} does not exist", path); + return None; } }; + self.loading_entries.insert(entry.clone()); + if let Entry::Vacant(entry) = self.buffers.entry((worktree_id, inode)) { let (mut tx, rx) = postage::watch::channel(); entry.insert(rx); @@ -370,7 +340,7 @@ impl WorkspaceView { } let mut watch = self.buffers.get(&(worktree_id, inode)).unwrap().clone(); - ctx.spawn( + Some(ctx.spawn( async move { loop { if let Some(load_result) = watch.borrow().as_ref() { @@ -379,15 +349,26 @@ impl WorkspaceView { 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 - }) + move |me, load_result, ctx| { + me.loading_entries.remove(&entry); + match load_result { + Ok(buffer_handle) => { + let buffer_view = Box::new(ctx.add_view(|ctx| { + BufferView::for_buffer( + buffer_handle, + Some(file), + me.settings.clone(), + ctx, + ) + })); + me.add_item(buffer_view, ctx); + } + Err(error) => { + log::error!("error opening item: {}", error); + } + } }, - ) - .boxed_local() + )) } pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext) { From 8820df7912b09c79702852c2b8395444972ff1d3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 3 May 2021 20:42:32 -0600 Subject: [PATCH 6/8] Rename WorkspaceView to Workspace and reorganize module --- zed/src/file_finder.rs | 24 +-- .../workspace_view.rs => workspace.rs} | 178 +++++++++++++++--- zed/src/workspace/mod.rs | 148 --------------- 3 files changed, 165 insertions(+), 185 deletions(-) rename zed/src/{workspace/workspace_view.rs => workspace.rs} (84%) delete mode 100644 zed/src/workspace/mod.rs diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 844d5968c2bf47174cf67846d92ca5183777caa5..57118bcf22793ed78abfdb64aac4b962a294e9e7 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::WorkspaceView, + workspace::Workspace, worktree::{match_paths, PathMatch, Worktree}, }; use gpui::{ @@ -26,7 +26,7 @@ use std::{ pub struct FileFinder { handle: WeakViewHandle, settings: watch::Receiver, - workspace: WeakViewHandle, + workspace: WeakViewHandle, query_buffer: ViewHandle, search_count: usize, latest_search_id: usize, @@ -253,7 +253,7 @@ impl FileFinder { }) } - fn toggle(workspace_view: &mut WorkspaceView, _: &(), ctx: &mut ViewContext) { + fn toggle(workspace_view: &mut Workspace, _: &(), ctx: &mut ViewContext) { workspace_view.toggle_modal(ctx, |ctx, workspace_view| { let workspace = ctx.handle(); let finder = @@ -264,10 +264,10 @@ impl FileFinder { } fn on_event( - workspace_view: &mut WorkspaceView, + workspace_view: &mut Workspace, _: ViewHandle, event: &Event, - ctx: &mut ViewContext, + ctx: &mut ViewContext, ) { match event { Event::Selected(tree_id, path) => { @@ -284,7 +284,7 @@ impl FileFinder { pub fn new( settings: watch::Receiver, - workspace: ViewHandle, + workspace: ViewHandle, ctx: &mut ViewContext, ) -> Self { ctx.observe_view(&workspace, Self::workspace_updated); @@ -310,7 +310,7 @@ impl FileFinder { } } - fn workspace_updated(&mut self, _: ViewHandle, 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); } @@ -453,7 +453,7 @@ impl FileFinder { #[cfg(test)] mod tests { use super::*; - use crate::{editor, settings, test::temp_tree, workspace::WorkspaceView}; + use crate::{editor, settings, test::temp_tree, workspace::Workspace}; use gpui::App; use serde_json::json; use std::fs; @@ -473,7 +473,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (window_id, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings, ctx); + let mut workspace = Workspace::new(0, settings, ctx); workspace.open_path(tmp_dir.path().into(), ctx); workspace }); @@ -542,7 +542,7 @@ mod tests { })); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings.clone(), ctx); + let mut workspace = Workspace::new(0, settings.clone(), ctx); workspace.open_path(tmp_dir.path().into(), ctx); workspace }); @@ -599,7 +599,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings.clone(), ctx); + let mut workspace = Workspace::new(0, settings.clone(), ctx); workspace.open_path(file_path, ctx); workspace }); @@ -641,7 +641,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings.clone(), ctx); + let mut workspace = Workspace::new(0, settings.clone(), ctx); smol::block_on(workspace.open_paths( &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], ctx, diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace.rs similarity index 84% rename from zed/src/workspace/workspace_view.rs rename to zed/src/workspace.rs index 2b89ba4d60c7b085e3786b7b54243aa7326e0b87..00cdeef7a5ea09467a53fac30809a89cde12d119 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace.rs @@ -1,32 +1,99 @@ -use super::{pane, Pane, PaneGroup, SplitDirection}; +pub mod pane; +pub mod pane_group; +pub use pane::*; +pub use pane_group::*; + use crate::{ - editor::{Buffer, BufferView}, settings::Settings, + watch::{self, Receiver}, +}; +use gpui::{MutableAppContext, PathPromptOptions}; +use std::path::PathBuf; +pub fn init(app: &mut MutableAppContext) { + app.add_global_action("workspace:open", open); + app.add_global_action("workspace:open_paths", open_paths); + app.add_global_action("app:quit", quit); + app.add_action("workspace:save", Workspace::save_active_item); + app.add_action("workspace:debug_elements", Workspace::debug_elements); + app.add_bindings(vec![ + Binding::new("cmd-s", "workspace:save", None), + Binding::new("cmd-alt-i", "workspace:debug_elements", None), + ]); + pane::init(app); +} +use crate::{ + editor::{Buffer, BufferView}, time::ReplicaId, - watch, worktree::{Worktree, WorktreeHandle}, }; use futures_core::{future::LocalBoxFuture, Future}; use gpui::{ color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, - ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, View, ViewContext, - ViewHandle, + ClipboardItem, Entity, EntityTask, ModelHandle, View, ViewContext, ViewHandle, }; use log::error; use smol::prelude::*; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, - path::{Path, PathBuf}, + path::Path, sync::Arc, }; -pub fn init(app: &mut MutableAppContext) { - app.add_action("workspace:save", WorkspaceView::save_active_item); - app.add_action("workspace:debug_elements", WorkspaceView::debug_elements); - app.add_bindings(vec![ - Binding::new("cmd-s", "workspace:save", None), - Binding::new("cmd-alt-i", "workspace:debug_elements", None), - ]); +pub struct OpenParams { + pub paths: Vec, + pub settings: watch::Receiver, +} + +fn open(settings: &Receiver, ctx: &mut MutableAppContext) { + let settings = settings.clone(); + ctx.prompt_for_paths( + PathPromptOptions { + files: true, + directories: true, + multiple: true, + }, + move |paths, ctx| { + if let Some(paths) = paths { + ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings }); + } + }, + ); +} + +fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { + log::info!("open paths {:?}", params.paths); + + // Open paths in existing workspace if possible + for window_id in app.window_ids().collect::>() { + if let Some(handle) = app.root_view::(window_id) { + if handle.update(app, |view, ctx| { + if view.contains_paths(¶ms.paths, ctx.as_ref()) { + let open_paths = view.open_paths(¶ms.paths, ctx); + ctx.foreground().spawn(open_paths).detach(); + log::info!("open paths on existing workspace"); + true + } else { + false + } + }) { + return; + } + } + } + + log::info!("open new workspace"); + + // Add a new workspace if necessary + app.add_window(|ctx| { + let mut view = Workspace::new(0, params.settings.clone(), ctx); + let open_paths = view.open_paths(¶ms.paths, ctx); + ctx.foreground().spawn(open_paths).detach(); + view + }); +} + +fn quit(_: &(), app: &mut MutableAppContext) { + app.platform().quit(); } pub trait ItemView: View { @@ -129,7 +196,7 @@ pub struct State { pub center: PaneGroup, } -pub struct WorkspaceView { +pub struct Workspace { pub settings: watch::Receiver, modal: Option, center: PaneGroup, @@ -144,7 +211,7 @@ pub struct WorkspaceView { >, } -impl WorkspaceView { +impl Workspace { pub fn new( replica_id: ReplicaId, settings: watch::Receiver, @@ -157,7 +224,7 @@ impl WorkspaceView { }); ctx.focus(&pane); - WorkspaceView { + Workspace { modal: None, center: PaneGroup::new(pane.id()), panes: vec![pane.clone()], @@ -487,11 +554,11 @@ impl WorkspaceView { } } -impl Entity for WorkspaceView { +impl Entity for Workspace { type Event = (); } -impl View for WorkspaceView { +impl View for Workspace { fn ui_name() -> &'static str { "Workspace" } @@ -514,12 +581,12 @@ impl View for WorkspaceView { } #[cfg(test)] -pub trait WorkspaceViewHandle { +pub trait WorkspaceHandle { fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)>; } #[cfg(test)] -impl WorkspaceViewHandle for ViewHandle { +impl WorkspaceHandle for ViewHandle { fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)> { self.read(app) .worktrees() @@ -536,12 +603,73 @@ impl WorkspaceViewHandle for ViewHandle { #[cfg(test)] mod tests { - use super::{pane, WorkspaceView, WorkspaceViewHandle as _}; + use super::*; use crate::{editor::BufferView, settings, test::temp_tree}; use gpui::App; use serde_json::json; use std::{collections::HashSet, os::unix}; + #[test] + fn test_open_paths_action() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + + init(app); + + let dir = temp_tree(json!({ + "a": { + "aa": null, + "ab": null, + }, + "b": { + "ba": null, + "bb": null, + }, + "c": { + "ca": null, + "cb": null, + }, + })); + + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths: vec![ + dir.path().join("a").to_path_buf(), + dir.path().join("b").to_path_buf(), + ], + settings: settings.clone(), + }, + ); + assert_eq!(app.window_ids().count(), 1); + + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths: vec![dir.path().join("a").to_path_buf()], + settings: settings.clone(), + }, + ); + assert_eq!(app.window_ids().count(), 1); + let workspace_view_1 = app + .root_view::(app.window_ids().next().unwrap()) + .unwrap(); + assert_eq!(workspace_view_1.read(app).worktrees().len(), 2); + + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths: vec![ + dir.path().join("b").to_path_buf(), + dir.path().join("c").to_path_buf(), + ], + settings: settings.clone(), + }, + ); + assert_eq!(app.window_ids().count(), 2); + }); + } + #[test] fn test_open_entry() { App::test_async((), |mut app| async move { @@ -556,7 +684,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings, ctx); + let mut workspace = Workspace::new(0, settings, ctx); smol::block_on(workspace.open_paths(&[dir.path().into()], ctx)); workspace }); @@ -642,7 +770,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings, ctx); + let mut workspace = Workspace::new(0, settings, ctx); workspace.open_path(dir1.path().into(), ctx); workspace }); @@ -716,7 +844,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings, ctx); + let mut workspace = Workspace::new(0, settings, ctx); workspace.open_path(dir.into(), ctx); workspace }); @@ -777,7 +905,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (window_id, workspace) = app.add_window(|ctx| { - let mut workspace = WorkspaceView::new(0, settings, ctx); + let mut workspace = Workspace::new(0, settings, ctx); workspace.open_path(dir.path().into(), ctx); workspace }); diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs deleted file mode 100644 index f651c31f533251e1c48c3008cee292f43248724f..0000000000000000000000000000000000000000 --- a/zed/src/workspace/mod.rs +++ /dev/null @@ -1,148 +0,0 @@ -pub mod pane; -pub mod pane_group; -pub mod workspace_view; - -pub use pane::*; -pub use pane_group::*; -pub use workspace_view::*; - -use crate::{ - settings::Settings, - watch::{self, Receiver}, -}; -use gpui::{MutableAppContext, PathPromptOptions}; -use std::path::PathBuf; - -pub fn init(app: &mut MutableAppContext) { - app.add_global_action("workspace:open", open); - app.add_global_action("workspace:open_paths", open_paths); - app.add_global_action("app:quit", quit); - pane::init(app); - workspace_view::init(app); -} - -pub struct OpenParams { - pub paths: Vec, - pub settings: watch::Receiver, -} - -fn open(settings: &Receiver, ctx: &mut MutableAppContext) { - let settings = settings.clone(); - ctx.prompt_for_paths( - PathPromptOptions { - files: true, - directories: true, - multiple: true, - }, - move |paths, ctx| { - if let Some(paths) = paths { - ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings }); - } - }, - ); -} - -fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { - log::info!("open paths {:?}", params.paths); - - // Open paths in existing workspace if possible - for window_id in app.window_ids().collect::>() { - if let Some(handle) = app.root_view::(window_id) { - if handle.update(app, |view, ctx| { - if view.contains_paths(¶ms.paths, ctx.as_ref()) { - let open_paths = view.open_paths(¶ms.paths, ctx); - ctx.foreground().spawn(open_paths).detach(); - log::info!("open paths on existing workspace"); - true - } else { - false - } - }) { - return; - } - } - } - - log::info!("open new workspace"); - - // Add a new workspace if necessary - app.add_window(|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 - }); -} - -fn quit(_: &(), app: &mut MutableAppContext) { - app.platform().quit(); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{settings, test::*}; - use gpui::App; - use serde_json::json; - - #[test] - fn test_open_paths_action() { - App::test((), |app| { - let settings = settings::channel(&app.font_cache()).unwrap().1; - - init(app); - - let dir = temp_tree(json!({ - "a": { - "aa": null, - "ab": null, - }, - "b": { - "ba": null, - "bb": null, - }, - "c": { - "ca": null, - "cb": null, - }, - })); - - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths: vec![ - dir.path().join("a").to_path_buf(), - dir.path().join("b").to_path_buf(), - ], - settings: settings.clone(), - }, - ); - assert_eq!(app.window_ids().count(), 1); - - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths: vec![dir.path().join("a").to_path_buf()], - settings: settings.clone(), - }, - ); - assert_eq!(app.window_ids().count(), 1); - let workspace_view_1 = app - .root_view::(app.window_ids().next().unwrap()) - .unwrap(); - assert_eq!(workspace_view_1.read(app).worktrees().len(), 2); - - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths: vec![ - dir.path().join("b").to_path_buf(), - dir.path().join("c").to_path_buf(), - ], - settings: settings.clone(), - }, - ); - assert_eq!(app.window_ids().count(), 2); - }); - } -} From 2b9a97a46ee67b8fd8834536bd72e7c67c56a320 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 4 May 2021 12:41:00 -0700 Subject: [PATCH 7/8] Fix hangs in workspace tests --- zed/src/file_finder.rs | 19 +++++++++++-------- zed/src/workspace.rs | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 57118bcf22793ed78abfdb64aac4b962a294e9e7..05ff52d0202613859b2c346f348fe1d3ff50f88d 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -640,16 +640,19 @@ mod tests { })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, workspace) = app.add_window(|ctx| { - let mut workspace = Workspace::new(0, settings.clone(), ctx); - smol::block_on(workspace.open_paths( - &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], - ctx, - )); - workspace - }); + let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx)); + + workspace + .update(&mut app, |workspace, ctx| { + workspace.open_paths( + &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], + ctx, + ) + }) + .await; app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; + let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 00cdeef7a5ea09467a53fac30809a89cde12d119..0e0faa6b9167583ce915c847e9fa4eedef60d504 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -685,7 +685,7 @@ mod tests { let (_, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings, ctx); - smol::block_on(workspace.open_paths(&[dir.path().into()], ctx)); + workspace.open_path(dir.path().into(), ctx); workspace }); From 361bb83f4382685b4d742d6d8881fadfed464a77 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 4 May 2021 14:18:12 -0700 Subject: [PATCH 8/8] Replace Workspace::open_path with simpler ::add_worktree method Co-Authored-By: Nathan Sobo --- zed/src/file_finder.rs | 6 +++--- zed/src/workspace.rs | 30 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 05ff52d0202613859b2c346f348fe1d3ff50f88d..0965cdcda5bc77ca4d35a7840855e53a2004a688 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -474,7 +474,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (window_id, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings, ctx); - workspace.open_path(tmp_dir.path().into(), ctx); + workspace.add_worktree(tmp_dir.path(), ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) @@ -543,7 +543,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings.clone(), ctx); - workspace.open_path(tmp_dir.path().into(), ctx); + workspace.add_worktree(tmp_dir.path(), ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) @@ -600,7 +600,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings.clone(), ctx); - workspace.open_path(file_path, ctx); + workspace.add_worktree(&file_path, ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 0e0faa6b9167583ce915c847e9fa4eedef60d504..629d23beecce0bdf1e1c59a76cd6fbe465f92f9b 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -272,7 +272,15 @@ impl Workspace { let entries = paths .iter() .cloned() - .map(|path| self.open_path(path, ctx)) + .map(|path| { + 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_id = self.add_worktree(&path, ctx); + (worktree_id, Path::new("").into()) + }) .collect::>(); let bg = ctx.background_executor().clone(); @@ -302,19 +310,13 @@ impl Workspace { } } - 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)); + pub fn add_worktree(&mut self, path: &Path, ctx: &mut ViewContext) -> usize { + let worktree = ctx.add_model(|ctx| Worktree::new(path, ctx)); let worktree_id = worktree.id(); ctx.observe_model(&worktree, |_, _, ctx| ctx.notify()); self.worktrees.insert(worktree); ctx.notify(); - (worktree_id, Path::new("").into()) + worktree_id } pub fn toggle_modal(&mut self, ctx: &mut ViewContext, add_view: F) @@ -685,7 +687,7 @@ mod tests { let (_, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings, ctx); - workspace.open_path(dir.path().into(), ctx); + workspace.add_worktree(dir.path(), ctx); workspace }); @@ -771,7 +773,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings, ctx); - workspace.open_path(dir1.path().into(), ctx); + workspace.add_worktree(dir1.path(), ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) @@ -845,7 +847,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings, ctx); - workspace.open_path(dir.into(), ctx); + workspace.add_worktree(dir, ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) @@ -906,7 +908,7 @@ mod tests { let settings = settings::channel(&app.font_cache()).unwrap().1; let (window_id, workspace) = app.add_window(|ctx| { let mut workspace = Workspace::new(0, settings, ctx); - workspace.open_path(dir.path().into(), ctx); + workspace.add_worktree(dir.path(), ctx); workspace }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))