From 18f1040c8524e9bad765a06c09a1987f36459058 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Jan 2022 15:27:34 +0100 Subject: [PATCH 01/31] Rename `Navigation` to `NavHistory` --- crates/diagnostics/src/diagnostics.rs | 4 ++-- crates/editor/src/editor.rs | 30 +++++++++++++-------------- crates/editor/src/items.rs | 12 +++++------ crates/workspace/src/pane.rs | 30 +++++++++++++-------------- crates/workspace/src/workspace.rs | 12 +++++------ 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 054074fb39434cefab18d6429622abfcbfea3dba..0cc4362a46b5b706a0f5f4cb4dd09f8c74731172 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -17,7 +17,7 @@ use postage::watch; use project::{Project, ProjectPath, WorktreeId}; use std::{cmp::Ordering, mem, ops::Range, rc::Rc, sync::Arc}; use util::TryFutureExt; -use workspace::{Navigation, Workspace}; +use workspace::{NavHistory, Workspace}; action!(Deploy); action!(OpenExcerpts); @@ -522,7 +522,7 @@ impl workspace::Item for ProjectDiagnostics { fn build_view( handle: ModelHandle, workspace: &Workspace, - _: Rc, + _: Rc, cx: &mut ViewContext, ) -> Self::View { ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), workspace.settings(), cx) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 96e9973df502941b6bc272d983cdbf27f5af722a..978f0caa578e012c2d90553edd85d2fbc50b721a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -49,7 +49,7 @@ use sum_tree::Bias; use text::rope::TextDimension; use theme::{DiagnosticStyle, EditorStyle}; use util::post_inc; -use workspace::{Navigation, PathOpener, Workspace}; +use workspace::{NavHistory, PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; @@ -379,7 +379,7 @@ pub struct Editor { mode: EditorMode, placeholder_text: Option>, highlighted_rows: Option>, - navigation: Option>, + nav_history: Option>, } pub struct EditorSnapshot { @@ -465,7 +465,7 @@ impl Editor { let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx); clone.scroll_position = self.scroll_position; clone.scroll_top_anchor = self.scroll_top_anchor.clone(); - clone.navigation = self.navigation.clone(); + clone.nav_history = self.nav_history.clone(); clone } @@ -515,7 +515,7 @@ impl Editor { mode: EditorMode::Full, placeholder_text: None, highlighted_rows: None, - navigation: None, + nav_history: None, }; let selection = Selection { id: post_inc(&mut this.next_selection_id), @@ -860,7 +860,7 @@ impl Editor { } } - self.push_to_navigation_history(newest_selection.head(), Some(end.to_point(&buffer)), cx); + self.push_to_nav_history(newest_selection.head(), Some(end.to_point(&buffer)), cx); let selection = Selection { id: post_inc(&mut self.next_selection_id), @@ -2455,13 +2455,13 @@ impl Editor { self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } - fn push_to_navigation_history( + fn push_to_nav_history( &self, position: Anchor, new_position: Option, cx: &mut ViewContext, ) { - if let Some(navigation) = &self.navigation { + if let Some(nav_history) = &self.nav_history { let buffer = self.buffer.read(cx).read(cx); let offset = position.to_offset(&buffer); let point = position.to_point(&buffer); @@ -2474,7 +2474,7 @@ impl Editor { } } - navigation.push( + nav_history.push( Some(NavigationData { anchor: position, offset, @@ -3330,7 +3330,7 @@ impl Editor { .max_by_key(|s| s.id) .map(|s| s.head().to_point(&buffer)); if new_cursor_position.is_some() { - self.push_to_navigation_history(old_cursor_position, new_cursor_position, cx); + self.push_to_nav_history(old_cursor_position, new_cursor_position, cx); } } @@ -4174,22 +4174,22 @@ mod tests { fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.add_window(Default::default(), |cx| { use workspace::ItemView; - let navigation = Rc::new(workspace::Navigation::default()); + let nav_history = Rc::new(workspace::NavHistory::default()); let settings = EditorSettings::test(&cx); let buffer = MultiBuffer::build_simple(&sample_text(30, 5, 'a'), cx); let mut editor = build_editor(buffer.clone(), settings, cx); - editor.navigation = Some(navigation.clone()); + editor.nav_history = Some(nav_history.clone()); // Move the cursor a small distance. // Nothing is added to the navigation history. editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx); editor.select_display_ranges(&[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)], cx); - assert!(navigation.pop_backward().is_none()); + assert!(nav_history.pop_backward().is_none()); // Move the cursor a large distance. // The history can jump back to the previous position. editor.select_display_ranges(&[DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)], cx); - let nav_entry = navigation.pop_backward().unwrap(); + let nav_entry = nav_history.pop_backward().unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item_view.id(), cx.view_id()); assert_eq!( @@ -4205,7 +4205,7 @@ mod tests { editor.selected_display_ranges(cx), &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] ); - assert!(navigation.pop_backward().is_none()); + assert!(nav_history.pop_backward().is_none()); // Move the cursor a large distance via the mouse. // The history can jump back to the previous position. @@ -4215,7 +4215,7 @@ mod tests { editor.selected_display_ranges(cx), &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)] ); - let nav_entry = navigation.pop_backward().unwrap(); + let nav_entry = nav_history.pop_backward().unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item_view.id(), cx.view_id()); assert_eq!( diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 147622c8d48650ab6a303da8acff02725f42e9a2..d3efb806c820a184b27a243d4a5255292b07ba71 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -13,7 +13,7 @@ use std::rc::Rc; use text::{Point, Selection}; use util::TryFutureExt; use workspace::{ - ItemHandle, ItemView, ItemViewHandle, Navigation, PathOpener, Settings, StatusItemView, + ItemHandle, ItemView, ItemViewHandle, NavHistory, PathOpener, Settings, StatusItemView, WeakItemHandle, Workspace, }; @@ -46,7 +46,7 @@ impl ItemHandle for BufferItemHandle { &self, window_id: usize, workspace: &Workspace, - navigation: Rc, + nav_history: Rc, cx: &mut MutableAppContext, ) -> Box { let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); @@ -57,7 +57,7 @@ impl ItemHandle for BufferItemHandle { crate::settings_builder(weak_buffer, workspace.settings()), cx, ); - editor.navigation = Some(navigation); + editor.nav_history = Some(nav_history); editor })) } @@ -115,9 +115,9 @@ impl ItemView for Editor { }; drop(buffer); - let navigation = self.navigation.take(); + let nav_history = self.nav_history.take(); self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx); - self.navigation = navigation; + self.nav_history = nav_history; } } @@ -150,7 +150,7 @@ impl ItemView for Editor { fn deactivated(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.newest_selection_internal() { - self.push_to_navigation_history(selection.head(), None, cx); + self.push_to_nav_history(selection.head(), None, cx); } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index be0e3f05fbefc80ebad7064b6ae8175415f93be7..07d5ffee47a370ffb1c91f62adbe703dce4d26aa 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -76,14 +76,14 @@ pub struct Pane { item_views: Vec<(usize, Box)>, active_item_index: usize, settings: watch::Receiver, - navigation: Rc, + nav_history: Rc, } #[derive(Default)] -pub struct Navigation(RefCell); +pub struct NavHistory(RefCell); #[derive(Default)] -struct NavigationHistory { +struct NavHistoryState { mode: NavigationMode, backward_stack: VecDeque, forward_stack: VecDeque, @@ -114,7 +114,7 @@ impl Pane { item_views: Vec::new(), active_item_index: 0, settings, - navigation: Default::default(), + nav_history: Default::default(), } } @@ -148,7 +148,7 @@ impl Pane { ) -> Task<()> { let to_load = pane.update(cx, |pane, cx| { // Retrieve the weak item handle from the history. - let entry = pane.navigation.pop(mode)?; + let entry = pane.nav_history.pop(mode)?; // If the item is still present in this pane, then activate it. if let Some(index) = entry @@ -157,9 +157,9 @@ impl Pane { .and_then(|v| pane.index_for_item_view(v.as_ref())) { if let Some(item_view) = pane.active_item() { - pane.navigation.set_mode(mode); + pane.nav_history.set_mode(mode); item_view.deactivated(cx); - pane.navigation.set_mode(NavigationMode::Normal); + pane.nav_history.set_mode(NavigationMode::Normal); } pane.active_item_index = index; @@ -173,7 +173,7 @@ impl Pane { // If the item is no longer present in this pane, then retrieve its // project path in order to reopen it. else { - pane.navigation + pane.nav_history .0 .borrow_mut() .paths_by_item @@ -192,9 +192,9 @@ impl Pane { if let Some(pane) = cx.read(|cx| pane.upgrade(cx)) { if let Some(item) = item.log_err() { workspace.update(&mut cx, |workspace, cx| { - pane.update(cx, |p, _| p.navigation.set_mode(mode)); + pane.update(cx, |p, _| p.nav_history.set_mode(mode)); let item_view = workspace.open_item_in_pane(item, &pane, cx); - pane.update(cx, |p, _| p.navigation.set_mode(NavigationMode::Normal)); + pane.update(cx, |p, _| p.nav_history.set_mode(NavigationMode::Normal)); if let Some(data) = entry.data { item_view.navigate(data, cx); @@ -232,7 +232,7 @@ impl Pane { } let item_view = - item_handle.add_view(cx.window_id(), workspace, self.navigation.clone(), cx); + item_handle.add_view(cx.window_id(), workspace, self.nav_history.clone(), cx); self.add_item_view(item_view.boxed_clone(), cx); item_view } @@ -322,11 +322,11 @@ impl Pane { item_view.deactivated(cx); } - let mut navigation = self.navigation.0.borrow_mut(); + let mut nav_history = self.nav_history.0.borrow_mut(); if let Some(path) = item_view.project_path(cx) { - navigation.paths_by_item.insert(item_view.id(), path); + nav_history.paths_by_item.insert(item_view.id(), path); } else { - navigation.paths_by_item.remove(&item_view.id()); + nav_history.paths_by_item.remove(&item_view.id()); } item_ix += 1; @@ -536,7 +536,7 @@ impl View for Pane { } } -impl Navigation { +impl NavHistory { pub fn pop_backward(&self) -> Option { self.0.borrow_mut().backward_stack.pop_back() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 531d976fd57b02872cdd56bc35fcb937b889bb9f..b5822d75e6ba0eeda218f19eb18317b75ededfa5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -137,7 +137,7 @@ pub trait Item: Entity + Sized { fn build_view( handle: ModelHandle, workspace: &Workspace, - navigation: Rc, + nav_history: Rc, cx: &mut ViewContext, ) -> Self::View; @@ -190,7 +190,7 @@ pub trait ItemHandle: Send + Sync { &self, window_id: usize, workspace: &Workspace, - navigation: Rc, + nav_history: Rc, cx: &mut MutableAppContext, ) -> Box; fn boxed_clone(&self) -> Box; @@ -242,11 +242,11 @@ impl ItemHandle for ModelHandle { &self, window_id: usize, workspace: &Workspace, - navigation: Rc, + nav_history: Rc, cx: &mut MutableAppContext, ) -> Box { Box::new(cx.add_view(window_id, |cx| { - T::build_view(self.clone(), workspace, navigation, cx) + T::build_view(self.clone(), workspace, nav_history, cx) })) } @@ -276,10 +276,10 @@ impl ItemHandle for Box { &self, window_id: usize, workspace: &Workspace, - navigation: Rc, + nav_history: Rc, cx: &mut MutableAppContext, ) -> Box { - ItemHandle::add_view(self.as_ref(), window_id, workspace, navigation, cx) + ItemHandle::add_view(self.as_ref(), window_id, workspace, nav_history, cx) } fn boxed_clone(&self) -> Box { From 9c9a09cccb41c4ea2a3d06b32170876e77946ba1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Jan 2022 15:56:04 +0100 Subject: [PATCH 02/31] Replace `project_path` with `project_entry` in `workspace::{Item, ItemView}` --- crates/diagnostics/src/diagnostics.rs | 4 +- crates/editor/src/items.rs | 18 +++---- crates/project/src/project.rs | 8 ++++ crates/project/src/worktree.rs | 9 +++- crates/workspace/src/pane.rs | 30 ++++++------ crates/workspace/src/workspace.rs | 50 +++++++++++++------- crates/zed/src/zed.rs | 67 ++++++++++++++++++++------- 7 files changed, 122 insertions(+), 64 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 0cc4362a46b5b706a0f5f4cb4dd09f8c74731172..85b40acd8d6fce445e084a403f949efdf0bd440a 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -528,7 +528,7 @@ impl workspace::Item for ProjectDiagnostics { ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), workspace.settings(), cx) } - fn project_path(&self) -> Option { + fn project_entry(&self) -> Option { None } } @@ -544,7 +544,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { "Project Diagnostics".to_string() } - fn project_path(&self, _: &AppContext) -> Option { + fn project_entry(&self, _: &AppContext) -> Option { None } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d3efb806c820a184b27a243d4a5255292b07ba71..8158c5f6c4b7df63d48a2407aae340b876216adb 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -6,7 +6,7 @@ use gpui::{ }; use language::{Bias, Buffer, Diagnostic, File as _}; use postage::watch; -use project::{File, ProjectPath, Worktree}; +use project::{File, ProjectEntry, ProjectPath, Worktree}; use std::fmt::Write; use std::path::Path; use std::rc::Rc; @@ -74,11 +74,8 @@ impl ItemHandle for BufferItemHandle { Box::new(WeakBufferItemHandle(self.0.downgrade())) } - fn project_path(&self, cx: &AppContext) -> Option { - File::from_dyn(self.0.read(cx).file()).map(|f| ProjectPath { - worktree_id: f.worktree_id(cx), - path: f.path().clone(), - }) + fn project_entry(&self, cx: &AppContext) -> Option { + File::from_dyn(self.0.read(cx).file()).and_then(|f| f.project_entry(cx)) } fn id(&self) -> usize { @@ -134,11 +131,8 @@ impl ItemView for Editor { } } - fn project_path(&self, cx: &AppContext) -> Option { - File::from_dyn(self.buffer().read(cx).file(cx)).map(|file| ProjectPath { - worktree_id: file.worktree_id(cx), - path: file.path().clone(), - }) + fn project_entry(&self, cx: &AppContext) -> Option { + File::from_dyn(self.buffer().read(cx).file(cx)).and_then(|file| file.project_entry(cx)) } fn clone_on_split(&self, cx: &mut ViewContext) -> Option @@ -163,7 +157,7 @@ impl ItemView for Editor { } fn can_save(&self, cx: &AppContext) -> bool { - self.project_path(cx).is_some() + self.project_entry(cx).is_some() } fn save(&mut self, cx: &mut ViewContext) -> Result>> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a8b38a2bb20cdb6a988cc809573ab4142046a6a8..310274239f886b5bed06e7f3a3327ee78b6aae15 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -567,6 +567,14 @@ impl Project { } } + pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option { + let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx); + Some(ProjectPath { + worktree_id: entry.worktree_id, + path: worktree.entry_for_id(entry.entry_id)?.path.clone(), + }) + } + pub fn is_running_disk_based_diagnostics(&self) -> bool { self.pending_disk_based_diagnostics > 0 } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index c01e660eab6cbe9687cc2a5fad22a5b22a9eb292..ee36d0bcad1affce2cb8b7a7c42cc21a6e3adca7 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1,7 +1,7 @@ use super::{ fs::{self, Fs}, ignore::IgnoreStack, - DiagnosticSummary, + DiagnosticSummary, ProjectEntry, }; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context, Result}; @@ -2372,6 +2372,13 @@ impl File { pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId { self.worktree.read(cx).id() } + + pub fn project_entry(&self, cx: &AppContext) -> Option { + self.entry_id.map(|entry_id| ProjectEntry { + worktree_id: self.worktree_id(cx), + entry_id, + }) + } } #[derive(Clone, Debug)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 07d5ffee47a370ffb1c91f62adbe703dce4d26aa..3944a5d49d657c82d8a84bdd2d5dc18a222fc346 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -10,7 +10,7 @@ use gpui::{ Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext, ViewHandle, }; use postage::watch; -use project::ProjectPath; +use project::ProjectEntry; use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; use util::ResultExt; @@ -87,7 +87,7 @@ struct NavHistoryState { mode: NavigationMode, backward_stack: VecDeque, forward_stack: VecDeque, - paths_by_item: HashMap, + project_entries_by_item: HashMap, } #[derive(Copy, Clone)] @@ -148,10 +148,10 @@ impl Pane { ) -> Task<()> { let to_load = pane.update(cx, |pane, cx| { // Retrieve the weak item handle from the history. - let entry = pane.nav_history.pop(mode)?; + let nav_entry = pane.nav_history.pop(mode)?; // If the item is still present in this pane, then activate it. - if let Some(index) = entry + if let Some(index) = nav_entry .item_view .upgrade(cx) .and_then(|v| pane.index_for_item_view(v.as_ref())) @@ -164,7 +164,7 @@ impl Pane { pane.active_item_index = index; pane.focus_active_item(cx); - if let Some(data) = entry.data { + if let Some(data) = nav_entry.data { pane.active_item()?.navigate(data, cx); } cx.notify(); @@ -176,17 +176,17 @@ impl Pane { pane.nav_history .0 .borrow_mut() - .paths_by_item - .get(&entry.item_view.id()) + .project_entries_by_item + .get(&nav_entry.item_view.id()) .cloned() - .map(|project_path| (project_path, entry)) + .map(|project_entry| (project_entry, nav_entry)) } }); - if let Some((project_path, entry)) = to_load { + if let Some((project_entry, nav_entry)) = to_load { // If the item was no longer present, then load it again from its previous path. let pane = pane.downgrade(); - let task = workspace.load_path(project_path, cx); + let task = workspace.load_entry(project_entry, cx); cx.spawn(|workspace, mut cx| async move { let item = task.await; if let Some(pane) = cx.read(|cx| pane.upgrade(cx)) { @@ -196,7 +196,7 @@ impl Pane { let item_view = workspace.open_item_in_pane(item, &pane, cx); pane.update(cx, |p, _| p.nav_history.set_mode(NavigationMode::Normal)); - if let Some(data) = entry.data { + if let Some(data) = nav_entry.data { item_view.navigate(data, cx); } }); @@ -323,10 +323,12 @@ impl Pane { } let mut nav_history = self.nav_history.0.borrow_mut(); - if let Some(path) = item_view.project_path(cx) { - nav_history.paths_by_item.insert(item_view.id(), path); + if let Some(entry) = item_view.project_entry(cx) { + nav_history + .project_entries_by_item + .insert(item_view.id(), entry); } else { - nav_history.paths_by_item.remove(&item_view.id()); + nav_history.project_entries_by_item.remove(&item_view.id()); } item_ix += 1; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b5822d75e6ba0eeda218f19eb18317b75ededfa5..949c0193eaa5ff6c748d811ee861a2076704ca93 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -27,7 +27,7 @@ pub use pane::*; pub use pane_group::*; use parking_lot::Mutex; use postage::{prelude::Stream, watch}; -use project::{fs, Fs, Project, ProjectPath, Worktree}; +use project::{fs, Fs, Project, ProjectEntry, ProjectPath, Worktree}; pub use settings::Settings; use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}; use status_bar::StatusBar; @@ -140,8 +140,7 @@ pub trait Item: Entity + Sized { nav_history: Rc, cx: &mut ViewContext, ) -> Self::View; - - fn project_path(&self) -> Option; + fn project_entry(&self) -> Option; } pub trait ItemView: View { @@ -151,7 +150,7 @@ pub trait ItemView: View { fn navigate(&mut self, _: Box, _: &mut ViewContext) {} fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn title(&self, cx: &AppContext) -> String; - fn project_path(&self, cx: &AppContext) -> Option; + fn project_entry(&self, cx: &AppContext) -> Option; fn clone_on_split(&self, _: &mut ViewContext) -> Option where Self: Sized, @@ -196,7 +195,7 @@ pub trait ItemHandle: Send + Sync { fn boxed_clone(&self) -> Box; fn downgrade(&self) -> Box; fn to_any(&self) -> AnyModelHandle; - fn project_path(&self, cx: &AppContext) -> Option; + fn project_entry(&self, cx: &AppContext) -> Option; } pub trait WeakItemHandle { @@ -207,7 +206,7 @@ pub trait WeakItemHandle { pub trait ItemViewHandle { fn item_handle(&self, cx: &AppContext) -> Box; fn title(&self, cx: &AppContext) -> String; - fn project_path(&self, cx: &AppContext) -> Option; + fn project_entry(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; fn added_to_pane(&mut self, cx: &mut ViewContext); @@ -262,8 +261,8 @@ impl ItemHandle for ModelHandle { self.clone().into() } - fn project_path(&self, cx: &AppContext) -> Option { - self.read(cx).project_path() + fn project_entry(&self, cx: &AppContext) -> Option { + self.read(cx).project_entry() } } @@ -294,8 +293,8 @@ impl ItemHandle for Box { self.as_ref().to_any() } - fn project_path(&self, cx: &AppContext) -> Option { - self.as_ref().project_path(cx) + fn project_entry(&self, cx: &AppContext) -> Option { + self.as_ref().project_entry(cx) } } @@ -332,8 +331,8 @@ impl ItemViewHandle for ViewHandle { self.read(cx).title(cx) } - fn project_path(&self, cx: &AppContext) -> Option { - self.read(cx).project_path(cx) + fn project_entry(&self, cx: &AppContext) -> Option { + self.read(cx).project_entry(cx) } fn boxed_clone(&self) -> Box { @@ -776,6 +775,18 @@ impl Workspace { }) } + pub fn load_entry( + &mut self, + path: ProjectEntry, + cx: &mut ViewContext, + ) -> Task>> { + if let Some(path) = self.project.read(cx).path_for_entry(path, cx) { + self.load_path(path, cx) + } else { + Task::ready(Err(anyhow!("entry does not exist"))) + } + } + pub fn load_path( &mut self, path: ProjectPath, @@ -805,10 +816,13 @@ impl Workspace { } fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option> { - self.items - .iter() - .filter_map(|i| i.upgrade(cx)) - .find(|i| i.project_path(cx).as_ref() == Some(path)) + let project = self.project.read(cx); + self.items.iter().filter_map(|i| i.upgrade(cx)).find(|i| { + let item_path = i + .project_entry(cx) + .and_then(|entry| project.path_for_entry(entry, cx)); + item_path.as_ref() == Some(path) + }) } pub fn item_of_type(&self, cx: &AppContext) -> Option> { @@ -822,7 +836,9 @@ impl Workspace { } fn active_project_path(&self, cx: &ViewContext) -> Option { - self.active_item(cx).and_then(|item| item.project_path(cx)) + self.active_item(cx) + .and_then(|item| item.project_entry(cx)) + .and_then(|entry| self.project.read(cx).path_for_entry(entry, cx)) } pub fn save_active_item(&mut self, _: &Save, cx: &mut ViewContext) { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f5aeec861ace4634aeefb03e7316042a43c5e168..ac24bd9aea6c85bfaa30ec6caa4343a1e7740ecb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -268,9 +268,11 @@ mod tests { .await .unwrap(); cx.read(|cx| { - let pane = workspace.read(cx).active_pane().read(cx); + let workspace = workspace.read(cx); + let pane = workspace.active_pane().read(cx); + let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); assert_eq!( - pane.active_item().unwrap().project_path(cx), + workspace.project().read(cx).path_for_entry(entry, cx), Some(file1.clone()) ); assert_eq!(pane.item_views().count(), 1); @@ -282,9 +284,11 @@ mod tests { .await .unwrap(); cx.read(|cx| { - let pane = workspace.read(cx).active_pane().read(cx); + let workspace = workspace.read(cx); + let pane = workspace.active_pane().read(cx); + let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); assert_eq!( - pane.active_item().unwrap().project_path(cx), + workspace.project().read(cx).path_for_entry(entry, cx), Some(file2.clone()) ); assert_eq!(pane.item_views().count(), 2); @@ -298,9 +302,11 @@ mod tests { assert_eq!(entry_1.id(), entry_1b.id()); cx.read(|cx| { - let pane = workspace.read(cx).active_pane().read(cx); + let workspace = workspace.read(cx); + let pane = workspace.active_pane().read(cx); + let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); assert_eq!( - pane.active_item().unwrap().project_path(cx), + workspace.project().read(cx).path_for_entry(entry, cx), Some(file1.clone()) ); assert_eq!(pane.item_views().count(), 2); @@ -315,13 +321,11 @@ mod tests { .await .unwrap(); - workspace.read_with(&cx, |w, cx| { + workspace.read_with(&cx, |workspace, cx| { + let pane = workspace.active_pane().read(cx); + let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); assert_eq!( - w.active_pane() - .read(cx) - .active_item() - .unwrap() - .project_path(cx.as_ref()), + workspace.project().read(cx).path_for_entry(entry, cx), Some(file2.clone()) ); }); @@ -336,14 +340,22 @@ mod tests { t1.await.unwrap(); t2.await.unwrap(); cx.read(|cx| { - let pane = workspace.read(cx).active_pane().read(cx); + let workspace = workspace.read(cx); + let pane = workspace.active_pane().read(cx); + let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); assert_eq!( - pane.active_item().unwrap().project_path(cx), + workspace.project().read(cx).path_for_entry(entry, cx), Some(file3.clone()) ); let pane_entries = pane .item_views() - .map(|i| i.project_path(cx).unwrap()) + .map(|i| { + workspace + .project() + .read(cx) + .path_for_entry(i.project_entry(cx).unwrap(), cx) + .unwrap() + }) .collect::>(); assert_eq!(pane_entries, &[file1, file2, file3]); }); @@ -666,8 +678,15 @@ mod tests { .await .unwrap(); cx.read(|cx| { + let workspace = workspace.read(cx); + let pane1_entry = pane_1 + .read(cx) + .active_item() + .unwrap() + .project_entry(cx) + .unwrap(); assert_eq!( - pane_1.read(cx).active_item().unwrap().project_path(cx), + workspace.project().read(cx).path_for_entry(pane1_entry, cx), Some(file1.clone()) ); }); @@ -682,7 +701,15 @@ mod tests { assert_ne!(pane_1, pane_2); let pane2_item = pane_2.read(cx).active_item().unwrap(); - assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone())); + let pane2_entry = pane2_item.project_entry(cx).unwrap(); + assert_eq!( + workspace + .read(cx) + .project() + .read(cx) + .path_for_entry(pane2_entry, cx), + Some(file1.clone()) + ); cx.dispatch_action(window_id, vec![pane_2.id()], &workspace::CloseActiveItem); let workspace = workspace.read(cx); @@ -862,7 +889,11 @@ mod tests { let item = workspace.active_item(cx).unwrap(); let editor = item.to_any().downcast::().unwrap(); let selections = editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)); - (item.project_path(cx).unwrap(), selections[0].start) + let path = workspace + .project() + .read(cx) + .path_for_entry(item.project_entry(cx).unwrap(), cx); + (path.unwrap(), selections[0].start) }) } } From e7235a82ec4b5adbf4359341f7ce174c836db4cb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Jan 2022 17:27:00 +0100 Subject: [PATCH 03/31] Remove unused `languages` field on `LocalWorktree` --- crates/project/src/worktree.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ee36d0bcad1affce2cb8b7a7c42cc21a6e3adca7..cacdd4b1b2d5343864c02f86f4a9ba15705b0671 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1002,7 +1002,6 @@ pub struct LocalWorktree { client: Arc, user_store: ModelHandle, fs: Arc, - languages: Vec>, language_servers: HashMap>, } @@ -1110,7 +1109,6 @@ impl LocalWorktree { client, user_store, fs, - languages: Default::default(), language_servers: Default::default(), }; @@ -1155,19 +1153,11 @@ impl LocalWorktree { &self.language_registry } - pub fn languages(&self) -> &[Arc] { - &self.languages - } - pub fn register_language( &mut self, language: &Arc, cx: &mut ModelContext, ) -> Option> { - if !self.languages.iter().any(|l| Arc::ptr_eq(l, language)) { - self.languages.push(language.clone()); - } - if let Some(server) = self.language_servers.get(language.name()) { return Some(server.clone()); } From ae284c2d8abc19d41367922ae842c19288306c50 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Jan 2022 18:44:48 +0100 Subject: [PATCH 04/31] Route `save_as` via the `Project` Co-Authored-By: Max Brunsfeld --- crates/diagnostics/src/diagnostics.rs | 6 +-- crates/editor/src/items.rs | 46 +++-------------- crates/project/src/project.rs | 47 ++++++++++++++++- crates/project/src/worktree.rs | 48 ++++++++++++++--- crates/workspace/src/workspace.rs | 74 +++++---------------------- 5 files changed, 108 insertions(+), 113 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 85b40acd8d6fce445e084a403f949efdf0bd440a..ed49a3226b3da86668dc931dcc2e82c480ec3c9d 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -15,7 +15,7 @@ use gpui::{ use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath, WorktreeId}; -use std::{cmp::Ordering, mem, ops::Range, rc::Rc, sync::Arc}; +use std::{cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; use util::TryFutureExt; use workspace::{NavHistory, Workspace}; @@ -570,8 +570,8 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { fn save_as( &mut self, - _: ModelHandle, - _: &std::path::Path, + _: ModelHandle, + _: PathBuf, _: &mut ViewContext, ) -> Task> { unreachable!() diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8158c5f6c4b7df63d48a2407aae340b876216adb..3ba61b8dca3bda6c4edd395589b5cf7277e0767a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -4,12 +4,12 @@ use gpui::{ elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; -use language::{Bias, Buffer, Diagnostic, File as _}; +use language::{Bias, Buffer, Diagnostic}; use postage::watch; -use project::{File, ProjectEntry, ProjectPath, Worktree}; -use std::fmt::Write; -use std::path::Path; +use project::worktree::File; +use project::{Project, ProjectEntry, ProjectPath, Worktree}; use std::rc::Rc; +use std::{fmt::Write, path::PathBuf}; use text::{Point, Selection}; use util::TryFutureExt; use workspace::{ @@ -182,8 +182,8 @@ impl ItemView for Editor { fn save_as( &mut self, - worktree: ModelHandle, - path: &Path, + project: ModelHandle, + abs_path: PathBuf, cx: &mut ViewContext, ) -> Task> { let buffer = self @@ -193,38 +193,8 @@ impl ItemView for Editor { .expect("cannot call save_as on an excerpt list") .clone(); - buffer.update(cx, |buffer, cx| { - let handle = cx.handle(); - let text = buffer.as_rope().clone(); - let version = buffer.version(); - - let save_as = worktree.update(cx, |worktree, cx| { - worktree - .as_local_mut() - .unwrap() - .save_buffer_as(handle, path, text, cx) - }); - - cx.spawn(|buffer, mut cx| async move { - save_as.await.map(|new_file| { - let (language, language_server) = worktree.update(&mut cx, |worktree, cx| { - let worktree = worktree.as_local_mut().unwrap(); - let language = worktree - .language_registry() - .select_language(new_file.full_path()) - .cloned(); - let language_server = language - .as_ref() - .and_then(|language| worktree.register_language(language, cx)); - (language, language_server.clone()) - }); - - buffer.update(&mut cx, |buffer, cx| { - buffer.did_save(version, new_file.mtime, Some(Box::new(new_file)), cx); - buffer.set_language(language, language_server, cx); - }); - }) - }) + project.update(cx, |project, cx| { + project.save_buffer_as(buffer, &abs_path, cx) }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 310274239f886b5bed06e7f3a3327ee78b6aae15..ba79a2fdfc72a6931931f38c550fc1086ef18d34 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -15,7 +15,7 @@ use language::{Buffer, DiagnosticEntry, LanguageRegistry}; use lsp::DiagnosticSeverity; use postage::{prelude::Stream, watch}; use std::{ - path::Path, + path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, }; use util::TryFutureExt as _; @@ -468,6 +468,49 @@ impl Project { } } + pub fn save_buffer_as( + &self, + buffer: ModelHandle, + abs_path: &Path, + cx: &mut ModelContext, + ) -> Task> { + let result = self.worktree_for_abs_path(abs_path, cx); + cx.spawn(|_, mut cx| async move { + let (worktree, path) = result.await?; + worktree + .update(&mut cx, |worktree, cx| { + worktree + .as_local() + .unwrap() + .save_buffer_as(buffer.clone(), path, cx) + }) + .await?; + Ok(()) + }) + } + + pub fn worktree_for_abs_path( + &self, + abs_path: &Path, + cx: &mut ModelContext, + ) -> Task, PathBuf)>> { + for tree in &self.worktrees { + if let Some(relative_path) = tree + .read(cx) + .as_local() + .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok()) + { + return Task::ready(Ok((tree.clone(), relative_path.into()))); + } + } + + let worktree = self.add_local_worktree(abs_path, cx); + cx.background().spawn(async move { + let worktree = worktree.await?; + Ok((worktree, PathBuf::new())) + }) + } + pub fn is_shared(&self) -> bool { match &self.client_state { ProjectClientState::Local { is_shared, .. } => *is_shared, @@ -476,7 +519,7 @@ impl Project { } pub fn add_local_worktree( - &mut self, + &self, abs_path: impl AsRef, cx: &mut ModelContext, ) -> Task>> { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cacdd4b1b2d5343864c02f86f4a9ba15705b0671..1603e0fede5d7c6378ad42597ba5d49d11d27559 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1002,6 +1002,7 @@ pub struct LocalWorktree { client: Arc, user_store: ModelHandle, fs: Arc, + languages: Vec>, language_servers: HashMap>, } @@ -1109,6 +1110,7 @@ impl LocalWorktree { client, user_store, fs, + languages: Default::default(), language_servers: Default::default(), }; @@ -1153,11 +1155,19 @@ impl LocalWorktree { &self.language_registry } + pub fn languages(&self) -> &[Arc] { + &self.languages + } + pub fn register_language( &mut self, language: &Arc, cx: &mut ModelContext, ) -> Option> { + if !self.languages.iter().any(|l| Arc::ptr_eq(l, language)) { + self.languages.push(language.clone()); + } + if let Some(server) = self.language_servers.get(language.name()) { return Some(server.clone()); } @@ -1498,26 +1508,48 @@ impl LocalWorktree { pub fn save_buffer_as( &self, - buffer: ModelHandle, + buffer_handle: ModelHandle, path: impl Into>, - text: Rope, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { + let buffer = buffer_handle.read(cx); + let text = buffer.as_rope().clone(); + let version = buffer.version(); let save = self.save(path, text, cx); cx.spawn(|this, mut cx| async move { let entry = save.await?; - this.update(&mut cx, |this, cx| { + let file = this.update(&mut cx, |this, cx| { let this = this.as_local_mut().unwrap(); - this.open_buffers.insert(buffer.id(), buffer.downgrade()); - Ok(File { + this.open_buffers + .insert(buffer_handle.id(), buffer_handle.downgrade()); + File { entry_id: Some(entry.id), worktree: cx.handle(), worktree_path: this.abs_path.clone(), path: entry.path, mtime: entry.mtime, is_local: true, - }) - }) + } + }); + + let (language, language_server) = this.update(&mut cx, |worktree, cx| { + let worktree = worktree.as_local_mut().unwrap(); + let language = worktree + .language_registry() + .select_language(file.full_path()) + .cloned(); + let language_server = language + .as_ref() + .and_then(|language| worktree.register_language(language, cx)); + (language, language_server.clone()) + }); + + buffer_handle.update(&mut cx, |buffer, cx| { + buffer.did_save(version, file.mtime, Some(Box::new(file)), cx); + buffer.set_language(language, language_server, cx); + }); + + Ok(()) }) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 949c0193eaa5ff6c748d811ee861a2076704ca93..da7c40d5f6402c1aec897dbceea19c0a403cec0f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -168,8 +168,8 @@ pub trait ItemView: View { fn can_save_as(&self, cx: &AppContext) -> bool; fn save_as( &mut self, - worktree: ModelHandle, - path: &Path, + project: ModelHandle, + abs_path: PathBuf, cx: &mut ViewContext, ) -> Task>; fn should_activate_item_on_event(_: &Self::Event) -> bool { @@ -221,8 +221,8 @@ pub trait ItemViewHandle { fn save(&self, cx: &mut MutableAppContext) -> Result>>; fn save_as( &self, - worktree: ModelHandle, - path: &Path, + project: ModelHandle, + abs_path: PathBuf, cx: &mut MutableAppContext, ) -> Task>; } @@ -379,11 +379,11 @@ impl ItemViewHandle for ViewHandle { fn save_as( &self, - worktree: ModelHandle, - path: &Path, + project: ModelHandle, + abs_path: PathBuf, cx: &mut MutableAppContext, ) -> Task> { - self.update(cx, |item, cx| item.save_as(worktree, path, cx)) + self.update(cx, |item, cx| item.save_as(project, abs_path, cx)) } fn is_dirty(&self, cx: &AppContext) -> bool { @@ -674,44 +674,14 @@ impl Workspace { }) } - fn worktree_for_abs_path( - &self, - abs_path: &Path, - cx: &mut ViewContext, - ) -> Task, PathBuf)>> { - let abs_path: Arc = Arc::from(abs_path); - cx.spawn(|this, mut cx| async move { - let mut entry_id = None; - this.read_with(&cx, |this, cx| { - for tree in this.worktrees(cx) { - if let Some(relative_path) = tree - .read(cx) - .as_local() - .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok()) - { - entry_id = Some((tree.clone(), relative_path.into())); - break; - } - } - }); - - if let Some(entry_id) = entry_id { - Ok(entry_id) - } else { - let worktree = this - .update(&mut cx, |this, cx| this.add_worktree(&abs_path, cx)) - .await?; - Ok((worktree, PathBuf::new())) - } - }) - } - fn project_path_for_path( &self, abs_path: &Path, cx: &mut ViewContext, ) -> Task> { - let entry = self.worktree_for_abs_path(abs_path, cx); + let entry = self.project().update(cx, |project, cx| { + project.worktree_for_abs_path(abs_path, cx) + }); cx.spawn(|_, cx| async move { let (worktree, path) = entry.await?; Ok(ProjectPath { @@ -880,28 +850,8 @@ impl Workspace { .to_path_buf(); cx.prompt_for_new_path(&start_abs_path, move |abs_path, cx| { if let Some(abs_path) = abs_path { - cx.spawn(|mut cx| async move { - let result = match handle - .update(&mut cx, |this, cx| { - this.worktree_for_abs_path(&abs_path, cx) - }) - .await - { - Ok((worktree, path)) => { - handle - .update(&mut cx, |_, cx| { - item.save_as(worktree, &path, cx.as_mut()) - }) - .await - } - Err(error) => Err(error), - }; - - if let Err(error) = result { - error!("failed to save item: {:?}, ", error); - } - }) - .detach() + let project = handle.read(cx).project().clone(); + cx.update(|cx| item.save_as(project, abs_path, cx).detach_and_log_err(cx)); } }); } From 8052f905e558bff2fe04639d18760f21ac8238ee Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Jan 2022 18:46:17 +0100 Subject: [PATCH 05/31] Remove unused `languages` field from `LocalWorktree` --- crates/project/src/worktree.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 1603e0fede5d7c6378ad42597ba5d49d11d27559..192c3b2fcabe2334e8a4c9adf7b8a5e31d97c925 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1002,7 +1002,6 @@ pub struct LocalWorktree { client: Arc, user_store: ModelHandle, fs: Arc, - languages: Vec>, language_servers: HashMap>, } @@ -1110,7 +1109,6 @@ impl LocalWorktree { client, user_store, fs, - languages: Default::default(), language_servers: Default::default(), }; @@ -1155,19 +1153,11 @@ impl LocalWorktree { &self.language_registry } - pub fn languages(&self) -> &[Arc] { - &self.languages - } - pub fn register_language( &mut self, language: &Arc, cx: &mut ModelContext, ) -> Option> { - if !self.languages.iter().any(|l| Arc::ptr_eq(l, language)) { - self.languages.push(language.clone()); - } - if let Some(server) = self.language_servers.get(language.name()) { return Some(server.clone()); } From 10c64f527c055369e4bcc0e9ce77c725ac24c41d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Jan 2022 19:28:41 +0100 Subject: [PATCH 06/31] WIP --- crates/editor/src/items.rs | 2 +- crates/project/src/project.rs | 226 +++++++++++++++++++++++++++++++-- crates/project/src/worktree.rs | 165 ++---------------------- 3 files changed, 223 insertions(+), 170 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 3ba61b8dca3bda6c4edd395589b5cf7277e0767a..290b8e6d50c4922b0c631da6d3b762298c7c2510 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -194,7 +194,7 @@ impl ItemView for Editor { .clone(); project.update(cx, |project, cx| { - project.save_buffer_as(buffer, &abs_path, cx) + project.save_buffer_as(buffer, abs_path, cx) }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ba79a2fdfc72a6931931f38c550fc1086ef18d34..4a3046850439370798105912c1be368be791359d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -11,14 +11,14 @@ use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::{Buffer, DiagnosticEntry, LanguageRegistry}; -use lsp::DiagnosticSeverity; +use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry}; +use lsp::{DiagnosticSeverity, LanguageServer}; use postage::{prelude::Stream, watch}; use std::{ path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, }; -use util::TryFutureExt as _; +use util::{ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; @@ -27,6 +27,7 @@ pub struct Project { worktrees: Vec>, active_entry: Option, languages: Arc, + language_servers: HashMap<(Arc, String), Arc>, client: Arc, user_store: ModelHandle, fs: Arc, @@ -62,7 +63,6 @@ pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(WorktreeId), DiskBasedDiagnosticsStarted, - DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId }, DiskBasedDiagnosticsFinished, DiagnosticsUpdated(ProjectPath), } @@ -191,6 +191,7 @@ impl Project { user_store, fs, pending_disk_based_diagnostics: 0, + language_servers: Default::default(), } }) } @@ -283,6 +284,7 @@ impl Project { replica_id, }, pending_disk_based_diagnostics: 0, + language_servers: Default::default(), }; for worktree in worktrees { this.add_worktree(worktree, cx); @@ -457,12 +459,40 @@ impl Project { } pub fn open_buffer( - &self, + &mut self, path: ProjectPath, cx: &mut ModelContext, ) -> Task>> { if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) { - worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx)) + let language_server = worktree + .read(cx) + .as_local() + .map(|w| w.abs_path().clone()) + .and_then(|worktree_abs_path| { + let abs_path = worktree.abs_path().join(&path.path); + let language = self.languages.select_language(&abs_path).cloned(); + if let Some(language) = language { + if let Some(language_server) = + self.register_language_server(worktree.abs_path(), &language, cx) + { + worktree.register_language(&language, &language_server); + } + } + }); + worktree.update(cx, |worktree, cx| { + if let Some(worktree) = worktree.as_local_mut() { + let abs_path = worktree.abs_path().join(&path.path); + let language = self.languages.select_language(&abs_path).cloned(); + if let Some(language) = language { + if let Some(language_server) = + self.register_language_server(worktree.abs_path(), &language, cx) + { + worktree.register_language(&language, &language_server); + } + } + } + worktree.open_buffer(path.path, cx) + }) } else { cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) }) } @@ -471,24 +501,194 @@ impl Project { pub fn save_buffer_as( &self, buffer: ModelHandle, - abs_path: &Path, + abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let result = self.worktree_for_abs_path(abs_path, cx); - cx.spawn(|_, mut cx| async move { + let result = self.worktree_for_abs_path(&abs_path, cx); + let languages = self.languages.clone(); + cx.spawn(|this, mut cx| async move { let (worktree, path) = result.await?; worktree .update(&mut cx, |worktree, cx| { - worktree - .as_local() - .unwrap() - .save_buffer_as(buffer.clone(), path, cx) + let worktree = worktree.as_local_mut().unwrap(); + let language = languages.select_language(&abs_path); + if let Some(language) = language { + if let Some(language_server) = this.update(cx, |this, cx| { + this.register_language_server(worktree.abs_path(), language, cx) + }) { + worktree.register_language(&language, &language_server); + } + } + + worktree.save_buffer_as(buffer.clone(), path, cx) }) .await?; Ok(()) }) } + fn register_language_server( + &mut self, + worktree_abs_path: Arc, + language: &Arc, + cx: &mut ModelContext, + ) -> Option> { + if let Some(server) = self + .language_servers + .get(&(worktree_abs_path.clone(), language.name().to_string())) + { + return Some(server.clone()); + } + + if let Some(language_server) = language + .start_server(&worktree_abs_path, cx) + .log_err() + .flatten() + { + enum DiagnosticProgress { + Updating, + Updated, + } + + let disk_based_sources = language + .disk_based_diagnostic_sources() + .cloned() + .unwrap_or_default(); + let disk_based_diagnostics_progress_token = + language.disk_based_diagnostics_progress_token().cloned(); + let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = + smol::channel::unbounded(); + language_server + .on_notification::(move |params| { + smol::block_on(diagnostics_tx.send(params)).ok(); + }) + .detach(); + cx.spawn_weak(|this, mut cx| { + let has_disk_based_diagnostic_progress_token = + disk_based_diagnostics_progress_token.is_some(); + let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); + async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + if !has_disk_based_diagnostic_progress_token { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updating), + ) + .ok(); + } + this.update_diagnostics(diagnostics, &disk_based_sources, cx) + .log_err(); + if !has_disk_based_diagnostic_progress_token { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updated), + ) + .ok(); + } + }) + } else { + break; + } + } + } + }) + .detach(); + + let mut pending_disk_based_diagnostics: i32 = 0; + language_server + .on_notification::(move |params| { + let token = match params.token { + lsp::NumberOrString::Number(_) => None, + lsp::NumberOrString::String(token) => Some(token), + }; + + if token == disk_based_diagnostics_progress_token { + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::Begin(_) => { + if pending_disk_based_diagnostics == 0 { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updating), + ) + .ok(); + } + pending_disk_based_diagnostics += 1; + } + lsp::WorkDoneProgress::End(_) => { + pending_disk_based_diagnostics -= 1; + if pending_disk_based_diagnostics == 0 { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updated), + ) + .ok(); + } + } + _ => {} + }, + } + } + }) + .detach(); + let rpc = self.client.clone(); + cx.spawn_weak(|this, mut cx| async move { + while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + match progress { + DiagnosticProgress::Updating => { + let message = handle.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdating); + let this = this.as_local().unwrap(); + this.share.as_ref().map(|share| { + proto::DiskBasedDiagnosticsUpdating { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + } + }) + }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } + } + DiagnosticProgress::Updated => { + let message = handle.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + let this = this.as_local().unwrap(); + this.share.as_ref().map(|share| { + proto::DiskBasedDiagnosticsUpdated { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + } + }) + }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } + } + } + } else { + break; + } + } + }) + .detach(); + + self.language_servers.insert( + (worktree_abs_path.clone(), language.name().to_string()), + language_server.clone(), + ); + Some(language_server) + } else { + None + } + } + pub fn worktree_for_abs_path( &self, abs_path: &Path, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 192c3b2fcabe2334e8a4c9adf7b8a5e31d97c925..06b61b7175dbf5c372d5ef8509d743d7dd5757f3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1156,157 +1156,10 @@ impl LocalWorktree { pub fn register_language( &mut self, language: &Arc, - cx: &mut ModelContext, - ) -> Option> { - if let Some(server) = self.language_servers.get(language.name()) { - return Some(server.clone()); - } - - if let Some(language_server) = language - .start_server(self.abs_path(), cx) - .log_err() - .flatten() - { - enum DiagnosticProgress { - Updating, - Updated, - } - - let disk_based_sources = language - .disk_based_diagnostic_sources() - .cloned() - .unwrap_or_default(); - let disk_based_diagnostics_progress_token = - language.disk_based_diagnostics_progress_token().cloned(); - let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = - smol::channel::unbounded(); - language_server - .on_notification::(move |params| { - smol::block_on(diagnostics_tx.send(params)).ok(); - }) - .detach(); - cx.spawn_weak(|this, mut cx| { - let has_disk_based_diagnostic_progress_token = - disk_based_diagnostics_progress_token.is_some(); - let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); - async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - if !has_disk_based_diagnostic_progress_token { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updating), - ) - .ok(); - } - this.update_diagnostics(diagnostics, &disk_based_sources, cx) - .log_err(); - if !has_disk_based_diagnostic_progress_token { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updated), - ) - .ok(); - } - }) - } else { - break; - } - } - } - }) - .detach(); - - let mut pending_disk_based_diagnostics: i32 = 0; - language_server - .on_notification::(move |params| { - let token = match params.token { - lsp::NumberOrString::Number(_) => None, - lsp::NumberOrString::String(token) => Some(token), - }; - - if token == disk_based_diagnostics_progress_token { - match params.value { - lsp::ProgressParamsValue::WorkDone(progress) => match progress { - lsp::WorkDoneProgress::Begin(_) => { - if pending_disk_based_diagnostics == 0 { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updating), - ) - .ok(); - } - pending_disk_based_diagnostics += 1; - } - lsp::WorkDoneProgress::End(_) => { - pending_disk_based_diagnostics -= 1; - if pending_disk_based_diagnostics == 0 { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updated), - ) - .ok(); - } - } - _ => {} - }, - } - } - }) - .detach(); - let rpc = self.client.clone(); - cx.spawn_weak(|this, mut cx| async move { - while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - match progress { - DiagnosticProgress::Updating => { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdating); - let this = this.as_local().unwrap(); - this.share.as_ref().map(|share| { - proto::DiskBasedDiagnosticsUpdating { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - } - }) - }); - - if let Some(message) = message { - rpc.send(message).await.log_err(); - } - } - DiagnosticProgress::Updated => { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdated); - let this = this.as_local().unwrap(); - this.share.as_ref().map(|share| { - proto::DiskBasedDiagnosticsUpdated { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - } - }) - }); - - if let Some(message) = message { - rpc.send(message).await.log_err(); - } - } - } - } else { - break; - } - } - }) - .detach(); - - self.language_servers - .insert(language.name().to_string(), language_server.clone()); - Some(language_server.clone()) - } else { - None - } + language_server: &Arc, + ) { + self.language_servers + .insert(language.name().to_string(), language_server.clone()); } fn get_open_buffer( @@ -1342,7 +1195,7 @@ impl LocalWorktree { .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx)) .await?; - let (diagnostics, language, language_server) = this.update(&mut cx, |this, cx| { + let (diagnostics, language, language_server) = this.update(&mut cx, |this, _| { let this = this.as_local_mut().unwrap(); let diagnostics = this.diagnostics.get(&path).cloned(); let language = this @@ -1351,7 +1204,7 @@ impl LocalWorktree { .cloned(); let server = language .as_ref() - .and_then(|language| this.register_language(language, cx)); + .and_then(|language| this.language_servers.get(language.name()).cloned()); (diagnostics, language, server) }); @@ -1522,7 +1375,7 @@ impl LocalWorktree { } }); - let (language, language_server) = this.update(&mut cx, |worktree, cx| { + let (language, language_server) = this.update(&mut cx, |worktree, _| { let worktree = worktree.as_local_mut().unwrap(); let language = worktree .language_registry() @@ -1530,7 +1383,7 @@ impl LocalWorktree { .cloned(); let language_server = language .as_ref() - .and_then(|language| worktree.register_language(language, cx)); + .and_then(|language| worktree.language_servers.get(language.name()).cloned()); (language, language_server.clone()) }); @@ -3277,7 +3130,7 @@ mod tests { use client::test::{FakeHttpClient, FakeServer}; use fs::RealFs; use gpui::test::subscribe; - use language::{tree_sitter_rust, DiagnosticEntry, LanguageServerConfig}; + use language::{tree_sitter_rust, DiagnosticEntry, Language, LanguageServerConfig}; use language::{Diagnostic, LanguageConfig}; use lsp::Url; use rand::prelude::*; From f43dcd67635acd51dc1109ed6c1aaf556d7e4452 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 19 Jan 2022 14:05:06 -0800 Subject: [PATCH 07/31] Move logic for starting language servers to the project --- crates/diagnostics/src/diagnostics.rs | 6 + crates/editor/src/display_map.rs | 14 +- crates/editor/src/editor.rs | 35 +- crates/editor/src/items.rs | 2 +- crates/language/src/buffer.rs | 22 +- crates/language/src/tests.rs | 28 +- crates/project/src/project.rs | 517 +++++++++++++-------- crates/project/src/worktree.rs | 642 ++++++++++---------------- crates/server/src/rpc.rs | 29 +- 9 files changed, 639 insertions(+), 656 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ed49a3226b3da86668dc931dcc2e82c480ec3c9d..4334429a52e227f63c3536a38349496c93181e9a 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -731,6 +731,8 @@ mod tests { // Create some diagnostics worktree.update(&mut cx, |worktree, cx| { worktree + .as_local_mut() + .unwrap() .update_diagnostic_entries( Arc::from("/test/main.rs".as_ref()), None, @@ -882,6 +884,8 @@ mod tests { // Diagnostics are added for another earlier path. worktree.update(&mut cx, |worktree, cx| { worktree + .as_local_mut() + .unwrap() .update_diagnostic_entries( Arc::from("/test/consts.rs".as_ref()), None, @@ -980,6 +984,8 @@ mod tests { // Diagnostics are added to the first path worktree.update(&mut cx, |worktree, cx| { worktree + .as_local_mut() + .unwrap() .update_diagnostic_entries( Arc::from("/test/consts.rs".as_ref()), None, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index faf770cb1bde70fea6ab314f542395223a3c84c6..8757e7593b58137d57c9cb341149e1e069576726 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -840,7 +840,7 @@ mod tests { ("mod.body".to_string(), Color::red().into()), ("fn.name".to_string(), Color::blue().into()), ]); - let lang = Arc::new( + let language = Arc::new( Language::new( LanguageConfig { name: "Test".to_string(), @@ -857,10 +857,9 @@ mod tests { ) .unwrap(), ); - lang.set_theme(&theme); + language.set_theme(&theme); - let buffer = - cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); buffer.condition(&cx, |buf, _| !buf.is_parsing()).await; let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); @@ -928,7 +927,7 @@ mod tests { ("mod.body".to_string(), Color::red().into()), ("fn.name".to_string(), Color::blue().into()), ]); - let lang = Arc::new( + let language = Arc::new( Language::new( LanguageConfig { name: "Test".to_string(), @@ -945,10 +944,9 @@ mod tests { ) .unwrap(), ); - lang.set_theme(&theme); + language.set_theme(&theme); - let buffer = - cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); buffer.condition(&cx, |buf, _| !buf.is_parsing()).await; let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 978f0caa578e012c2d90553edd85d2fbc50b721a..8a56170daa54d55bbae10f508414b2e1a66e5488 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -533,9 +533,8 @@ impl Editor { _: &workspace::OpenNew, cx: &mut ViewContext, ) { - let buffer = cx.add_model(|cx| { - Buffer::new(0, "", cx).with_language(Some(language::PLAIN_TEXT.clone()), None, cx) - }); + let buffer = cx + .add_model(|cx| Buffer::new(0, "", cx).with_language(language::PLAIN_TEXT.clone(), cx)); workspace.open_item(BufferItemHandle(buffer), cx); } @@ -5746,10 +5745,10 @@ mod tests { #[gpui::test] async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - let language = Some(Arc::new(Language::new( + let language = Arc::new(Language::new( LanguageConfig::default(), Some(tree_sitter_rust::language()), - ))); + )); let text = r#" use mod1::mod2::{mod3, mod4}; @@ -5760,7 +5759,7 @@ mod tests { "# .unindent(); - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) @@ -5887,7 +5886,7 @@ mod tests { #[gpui::test] async fn test_autoindent_selections(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - let language = Some(Arc::new( + let language = Arc::new( Language::new( LanguageConfig { brackets: vec![ @@ -5915,11 +5914,11 @@ mod tests { "#, ) .unwrap(), - )); + ); let text = "fn a() {}"; - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx)); editor @@ -5944,7 +5943,7 @@ mod tests { #[gpui::test] async fn test_autoclose_pairs(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - let language = Some(Arc::new(Language::new( + let language = Arc::new(Language::new( LanguageConfig { brackets: vec![ BracketPair { @@ -5963,7 +5962,7 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )); let text = r#" a @@ -5973,7 +5972,7 @@ mod tests { "# .unindent(); - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) @@ -6055,13 +6054,13 @@ mod tests { #[gpui::test] async fn test_toggle_comment(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - let language = Some(Arc::new(Language::new( + let language = Arc::new(Language::new( LanguageConfig { line_comment: Some("// ".to_string()), ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )); let text = " fn a() { @@ -6072,7 +6071,7 @@ mod tests { " .unindent(); - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); @@ -6323,7 +6322,7 @@ mod tests { #[gpui::test] async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - let language = Some(Arc::new(Language::new( + let language = Arc::new(Language::new( LanguageConfig { brackets: vec![ BracketPair { @@ -6342,7 +6341,7 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )); let text = concat!( "{ }\n", // Suppress rustfmt @@ -6352,7 +6351,7 @@ mod tests { "{{} }\n", // ); - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 290b8e6d50c4922b0c631da6d3b762298c7c2510..6b170e12e96bac341c2a1e729d56e13c17361ea0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -34,7 +34,7 @@ impl PathOpener for BufferOpener { ) -> Option>>> { let buffer = worktree.open_buffer(project_path.path, cx); let task = cx.spawn(|_, _| async move { - let buffer = buffer.await?; + let buffer = buffer.await?.0; Ok(Box::new(BufferItemHandle(buffer)) as Box) }); Some(task) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 4cf0c8f0813eb26cd7309892274f41609fa9b9a8..d0410b47013b872c166a9d30b1541d38f20ea6c7 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -385,13 +385,17 @@ impl Buffer { } } - pub fn with_language( + pub fn with_language(mut self, language: Arc, cx: &mut ModelContext) -> Self { + self.set_language(Some(language), cx); + self + } + + pub fn with_language_server( mut self, - language: Option>, - language_server: Option>, + server: Arc, cx: &mut ModelContext, ) -> Self { - self.set_language(language, language_server, cx); + self.set_language_server(Some(server), cx); self } @@ -523,13 +527,16 @@ impl Buffer { })) } - pub fn set_language( + pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { + self.language = language; + self.reparse(cx); + } + + pub fn set_language_server( &mut self, - language: Option>, language_server: Option>, cx: &mut ModelContext, ) { - self.language = language; self.language_server = if let Some(server) = language_server { let (latest_snapshot_tx, mut latest_snapshot_rx) = watch::channel(); Some(LanguageServerState { @@ -611,7 +618,6 @@ impl Buffer { None }; - self.reparse(cx); self.update_language_server(); } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index e2ee035c86ac4d36a4cbeeebebe265ab6023a2a6..065ca28cec4d5aa3474e975e6290e3baac377bcf 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -145,9 +145,8 @@ async fn test_apply_diff(mut cx: gpui::TestAppContext) { #[gpui::test] async fn test_reparse(mut cx: gpui::TestAppContext) { let text = "fn a() {}"; - let buffer = cx.add_model(|cx| { - Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx) - }); + let buffer = + cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)); // Wait for the initial text to parse buffer @@ -280,7 +279,7 @@ async fn test_reparse(mut cx: gpui::TestAppContext) { #[gpui::test] async fn test_outline(mut cx: gpui::TestAppContext) { - let language = Some(Arc::new( + let language = Arc::new( rust_lang() .with_outline_query( r#" @@ -308,7 +307,7 @@ async fn test_outline(mut cx: gpui::TestAppContext) { "#, ) .unwrap(), - )); + ); let text = r#" struct Person { @@ -337,7 +336,7 @@ async fn test_outline(mut cx: gpui::TestAppContext) { "# .unindent(); - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx)); + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); let outline = buffer .read_with(&cx, |buffer, _| buffer.snapshot().outline(None)) .unwrap(); @@ -422,7 +421,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { } " .unindent(); - Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx) + Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx) }); let buffer = buffer.read(cx); assert_eq!( @@ -452,8 +451,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { fn test_edit_with_autoindent(cx: &mut MutableAppContext) { cx.add_model(|cx| { let text = "fn a() {}"; - let mut buffer = - Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx); + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); buffer.edit_with_autoindent([8..8], "\n\n", cx); assert_eq!(buffer.text(), "fn a() {\n \n}"); @@ -479,8 +477,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta " .unindent(); - let mut buffer = - Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx); + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, // their indentation is not adjusted. @@ -529,8 +526,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte " .unindent(); - let mut buffer = - Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx); + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); buffer.edit_with_autoindent([5..5], "\nb", cx); assert_eq!( @@ -575,7 +571,9 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { .unindent(); let buffer = cx.add_model(|cx| { - Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang)), Some(language_server), cx) + Buffer::new(0, text, cx) + .with_language(Arc::new(rust_lang), cx) + .with_language_server(language_server, cx) }); let open_notification = fake @@ -849,7 +847,7 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) { ); let mut buffer = Buffer::new(0, text, cx); - buffer.set_language(Some(Arc::new(rust_lang())), None, cx); + buffer.set_language(Some(Arc::new(rust_lang())), cx); buffer .update_diagnostics( None, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4a3046850439370798105912c1be368be791359d..93c91cdb22359d1cf6630f09a1018d2cd019270c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5,7 +5,7 @@ pub mod worktree; use anyhow::{anyhow, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; -use collections::HashMap; +use collections::{hash_map, HashMap, HashSet}; use futures::Future; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ @@ -27,7 +27,7 @@ pub struct Project { worktrees: Vec>, active_entry: Option, languages: Arc, - language_servers: HashMap<(Arc, String), Arc>, + language_servers: HashMap<(WorktreeId, String), Arc>, client: Arc, user_store: ModelHandle, fs: Arc, @@ -63,6 +63,7 @@ pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(WorktreeId), DiskBasedDiagnosticsStarted, + DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId }, DiskBasedDiagnosticsFinished, DiagnosticsUpdated(ProjectPath), } @@ -223,7 +224,6 @@ impl Project { worktree, client.clone(), user_store.clone(), - languages.clone(), cx, ) .await?, @@ -463,39 +463,21 @@ impl Project { path: ProjectPath, cx: &mut ModelContext, ) -> Task>> { - if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) { - let language_server = worktree - .read(cx) - .as_local() - .map(|w| w.abs_path().clone()) - .and_then(|worktree_abs_path| { - let abs_path = worktree.abs_path().join(&path.path); - let language = self.languages.select_language(&abs_path).cloned(); - if let Some(language) = language { - if let Some(language_server) = - self.register_language_server(worktree.abs_path(), &language, cx) - { - worktree.register_language(&language, &language_server); - } - } - }); - worktree.update(cx, |worktree, cx| { - if let Some(worktree) = worktree.as_local_mut() { - let abs_path = worktree.abs_path().join(&path.path); - let language = self.languages.select_language(&abs_path).cloned(); - if let Some(language) = language { - if let Some(language_server) = - self.register_language_server(worktree.abs_path(), &language, cx) - { - worktree.register_language(&language, &language_server); - } - } - } - worktree.open_buffer(path.path, cx) - }) + let worktree = if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) { + worktree } else { - cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) }) - } + return cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) }); + }; + let buffer_task = worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx)); + cx.spawn(|this, mut cx| async move { + let (buffer, buffer_is_new) = buffer_task.await?; + if buffer_is_new { + this.update(&mut cx, |this, cx| { + this.buffer_added(worktree, buffer.clone(), cx) + }); + } + Ok(buffer) + }) } pub fn save_buffer_as( @@ -504,189 +486,213 @@ impl Project { abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let result = self.worktree_for_abs_path(&abs_path, cx); - let languages = self.languages.clone(); + let worktree_task = self.worktree_for_abs_path(&abs_path, cx); cx.spawn(|this, mut cx| async move { - let (worktree, path) = result.await?; + let (worktree, path) = worktree_task.await?; worktree .update(&mut cx, |worktree, cx| { - let worktree = worktree.as_local_mut().unwrap(); - let language = languages.select_language(&abs_path); - if let Some(language) = language { - if let Some(language_server) = this.update(cx, |this, cx| { - this.register_language_server(worktree.abs_path(), language, cx) - }) { - worktree.register_language(&language, &language_server); - } - } - - worktree.save_buffer_as(buffer.clone(), path, cx) + worktree + .as_local_mut() + .unwrap() + .save_buffer_as(buffer.clone(), path, cx) }) .await?; + this.update(&mut cx, |this, cx| this.buffer_added(worktree, buffer, cx)); Ok(()) }) } - fn register_language_server( + fn buffer_added( &mut self, - worktree_abs_path: Arc, - language: &Arc, + worktree: ModelHandle, + buffer: ModelHandle, cx: &mut ModelContext, - ) -> Option> { - if let Some(server) = self + ) -> Option<()> { + // Set the buffer's language + let full_path = buffer.read(cx).file()?.full_path(); + let language = self.languages.select_language(&full_path)?.clone(); + buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(language.clone()), cx); + }); + + // For local worktrees, start a language server if needed. + let worktree = worktree.read(cx); + let worktree_id = worktree.id(); + let worktree_abs_path = worktree.as_local()?.abs_path().clone(); + let language_server = match self .language_servers - .get(&(worktree_abs_path.clone(), language.name().to_string())) + .entry((worktree_id, language.name().to_string())) { - return Some(server.clone()); - } + hash_map::Entry::Occupied(e) => Some(e.get().clone()), + hash_map::Entry::Vacant(e) => Self::start_language_server( + self.client.clone(), + language, + worktree_id, + &worktree_abs_path, + cx, + ) + .map(|server| e.insert(server).clone()), + }; + + buffer.update(cx, |buffer, cx| { + buffer.set_language_server(language_server, cx) + }); + + None + } - if let Some(language_server) = language - .start_server(&worktree_abs_path, cx) + fn start_language_server( + rpc: Arc, + language: Arc, + worktree_id: WorktreeId, + worktree_path: &Path, + cx: &mut ModelContext, + ) -> Option> { + let language_server = language + .start_server(worktree_path, cx) .log_err() - .flatten() - { - enum DiagnosticProgress { - Updating, - Updated, - } + .flatten()?; - let disk_based_sources = language - .disk_based_diagnostic_sources() - .cloned() - .unwrap_or_default(); - let disk_based_diagnostics_progress_token = - language.disk_based_diagnostics_progress_token().cloned(); - let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = - smol::channel::unbounded(); - language_server - .on_notification::(move |params| { - smol::block_on(diagnostics_tx.send(params)).ok(); - }) - .detach(); - cx.spawn_weak(|this, mut cx| { - let has_disk_based_diagnostic_progress_token = - disk_based_diagnostics_progress_token.is_some(); - let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); - async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - if !has_disk_based_diagnostic_progress_token { + enum DiagnosticProgress { + Updating, + Publish(lsp::PublishDiagnosticsParams), + Updated, + } + + let disk_based_sources = language + .disk_based_diagnostic_sources() + .cloned() + .unwrap_or_default(); + let disk_based_diagnostics_progress_token = + language.disk_based_diagnostics_progress_token().cloned(); + let has_disk_based_diagnostic_progress_token = + disk_based_diagnostics_progress_token.is_some(); + let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + + language_server + .on_notification::({ + let diagnostics_tx = diagnostics_tx.clone(); + move |params| { + if !has_disk_based_diagnostic_progress_token { + smol::block_on(diagnostics_tx.send(DiagnosticProgress::Updating)).ok(); + } + smol::block_on(diagnostics_tx.send(DiagnosticProgress::Publish(params))).ok(); + if !has_disk_based_diagnostic_progress_token { + smol::block_on(diagnostics_tx.send(DiagnosticProgress::Updated)).ok(); + } + } + }) + .detach(); + + let mut pending_disk_based_diagnostics: i32 = 0; + language_server + .on_notification::(move |params| { + let token = match params.token { + lsp::NumberOrString::Number(_) => None, + lsp::NumberOrString::String(token) => Some(token), + }; + + if token == disk_based_diagnostics_progress_token { + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::Begin(_) => { + if pending_disk_based_diagnostics == 0 { smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updating), + diagnostics_tx.send(DiagnosticProgress::Updating), ) .ok(); } - this.update_diagnostics(diagnostics, &disk_based_sources, cx) - .log_err(); - if !has_disk_based_diagnostic_progress_token { + pending_disk_based_diagnostics += 1; + } + lsp::WorkDoneProgress::End(_) => { + pending_disk_based_diagnostics -= 1; + if pending_disk_based_diagnostics == 0 { smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updated), + diagnostics_tx.send(DiagnosticProgress::Updated), ) .ok(); } - }) - } else { - break; - } + } + _ => {} + }, } } }) .detach(); - let mut pending_disk_based_diagnostics: i32 = 0; - language_server - .on_notification::(move |params| { - let token = match params.token { - lsp::NumberOrString::Number(_) => None, - lsp::NumberOrString::String(token) => Some(token), - }; - - if token == disk_based_diagnostics_progress_token { - match params.value { - lsp::ProgressParamsValue::WorkDone(progress) => match progress { - lsp::WorkDoneProgress::Begin(_) => { - if pending_disk_based_diagnostics == 0 { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updating), - ) - .ok(); - } - pending_disk_based_diagnostics += 1; - } - lsp::WorkDoneProgress::End(_) => { - pending_disk_based_diagnostics -= 1; - if pending_disk_based_diagnostics == 0 { - smol::block_on( - disk_based_diagnostics_done_tx - .send(DiagnosticProgress::Updated), - ) - .ok(); - } - } - _ => {} - }, + cx.spawn_weak(|this, mut cx| async move { + while let Ok(message) = diagnostics_rx.recv().await { + let this = cx.read(|cx| this.upgrade(cx))?; + match message { + DiagnosticProgress::Updating => { + let project_id = this.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsStarted); + this.remote_id() + }); + if let Some(project_id) = project_id { + rpc.send(proto::DiskBasedDiagnosticsUpdating { + project_id, + worktree_id: worktree_id.to_proto(), + }) + .await + .log_err(); } } - }) - .detach(); - let rpc = self.client.clone(); - cx.spawn_weak(|this, mut cx| async move { - while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - match progress { - DiagnosticProgress::Updating => { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdating); - let this = this.as_local().unwrap(); - this.share.as_ref().map(|share| { - proto::DiskBasedDiagnosticsUpdating { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - } - }) - }); - - if let Some(message) = message { - rpc.send(message).await.log_err(); - } - } - DiagnosticProgress::Updated => { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdated); - let this = this.as_local().unwrap(); - this.share.as_ref().map(|share| { - proto::DiskBasedDiagnosticsUpdated { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - } - }) - }); - - if let Some(message) = message { - rpc.send(message).await.log_err(); - } - } + DiagnosticProgress::Publish(params) => { + this.update(&mut cx, |this, cx| { + this.update_diagnostics(params, &disk_based_sources, cx) + .log_err(); + }); + } + DiagnosticProgress::Updated => { + let project_id = this.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsFinished); + this.remote_id() + }); + if let Some(project_id) = project_id { + rpc.send(proto::DiskBasedDiagnosticsUpdated { + project_id, + worktree_id: worktree_id.to_proto(), + }) + .await + .log_err(); } - } else { - break; } } - }) - .detach(); + } + Some(()) + }) + .detach(); - self.language_servers.insert( - (worktree_abs_path.clone(), language.name().to_string()), - language_server.clone(), - ); - Some(language_server) - } else { - None + Some(language_server) + } + + fn update_diagnostics( + &mut self, + diagnostics: lsp::PublishDiagnosticsParams, + disk_based_sources: &HashSet, + cx: &mut ModelContext, + ) -> Result<()> { + let path = diagnostics + .uri + .to_file_path() + .map_err(|_| anyhow!("URI is not a file"))?; + for tree in &self.worktrees { + let relative_path = tree.update(cx, |tree, _| { + path.strip_prefix(tree.as_local()?.abs_path()).ok() + }); + if let Some(relative_path) = relative_path { + return tree.update(cx, |tree, cx| { + tree.as_local_mut().unwrap().update_diagnostics( + relative_path.into(), + diagnostics, + disk_based_sources, + cx, + ) + }); + } } + todo!() } pub fn worktree_for_abs_path( @@ -726,12 +732,10 @@ impl Project { let fs = self.fs.clone(); let client = self.client.clone(); let user_store = self.user_store.clone(); - let languages = self.languages.clone(); let path = Arc::from(abs_path.as_ref()); cx.spawn(|project, mut cx| async move { let worktree = - Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx) - .await?; + Worktree::open_local(client.clone(), user_store, path, fs, &mut cx).await?; let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| { project.add_worktree(worktree.clone(), cx); @@ -936,13 +940,11 @@ impl Project { .worktree .ok_or_else(|| anyhow!("invalid worktree"))?; let user_store = self.user_store.clone(); - let languages = self.languages.clone(); cx.spawn(|this, mut cx| { async move { - let worktree = Worktree::remote( - remote_id, replica_id, worktree, client, user_store, languages, &mut cx, - ) - .await?; + let worktree = + Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx) + .await?; this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx)); Ok(()) } @@ -1248,6 +1250,25 @@ impl Entity for Project { } } } + + fn app_will_quit( + &mut self, + _: &mut MutableAppContext, + ) -> Option>>> { + use futures::FutureExt; + + let shutdown_futures = self + .language_servers + .drain() + .filter_map(|(_, server)| server.shutdown()) + .collect::>(); + Some( + async move { + futures::future::join_all(shutdown_futures).await; + } + .boxed(), + ) + } } impl Collaborator { @@ -1275,8 +1296,12 @@ mod tests { use super::*; use client::test::FakeHttpClient; use fs::RealFs; - use gpui::TestAppContext; - use language::LanguageRegistry; + use futures::StreamExt; + use gpui::{test::subscribe, TestAppContext}; + use language::{ + tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point, + }; + use lsp::Url; use serde_json::json; use std::{os::unix, path::PathBuf}; use util::test::temp_tree; @@ -1344,6 +1369,114 @@ mod tests { ); } + #[gpui::test] + async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { + let (language_server_config, mut fake_server) = + LanguageServerConfig::fake(cx.background()).await; + let progress_token = language_server_config + .disk_based_diagnostics_progress_token + .clone() + .unwrap(); + let mut languages = LanguageRegistry::new(); + languages.add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + language_server: Some(language_server_config), + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); + + let dir = temp_tree(json!({ + "a.rs": "fn a() { A }", + "b.rs": "const y: i32 = 1", + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + + let tree = Worktree::open_local( + client, + user_store, + dir.path(), + Arc::new(RealFs), + &mut cx.to_async(), + ) + .await + .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + // Cause worktree to start the fake language server + let _buffer = tree + .update(&mut cx, |tree, cx| tree.open_buffer("b.rs", cx)) + .await + .unwrap(); + + let mut events = subscribe(&tree, &mut cx); + + fake_server.start_progress(&progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsUpdating + ); + + fake_server.start_progress(&progress_token).await; + fake_server.end_progress(&progress_token).await; + fake_server.start_progress(&progress_token).await; + + fake_server + .notify::(lsp::PublishDiagnosticsParams { + uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "undefined variable 'A'".to_string(), + ..Default::default() + }], + }) + .await; + assert_eq!( + events.next().await.unwrap(), + Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs"))) + ); + + fake_server.end_progress(&progress_token).await; + fake_server.end_progress(&progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsUpdated + ); + + let (buffer, _) = tree + .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx)) + .await + .unwrap(); + + buffer.read_with(&cx, |buffer, _| { + let snapshot = buffer.snapshot(); + let diagnostics = snapshot + .diagnostics_in_range::<_, Point>(0..buffer.len()) + .collect::>(); + assert_eq!( + diagnostics, + &[DiagnosticEntry { + range: Point::new(0, 9)..Point::new(0, 10), + diagnostic: Diagnostic { + severity: lsp::DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + }] + ) + }); + } + #[gpui::test] async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) { let dir = temp_tree(json!({ diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 06b61b7175dbf5c372d5ef8509d743d7dd5757f3..ab26524473f8d1a5c70763b9ebbadf817bab1418 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4,7 +4,7 @@ use super::{ DiagnosticSummary, ProjectEntry, }; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use client::{proto, Client, PeerId, TypedEnvelope, UserStore}; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; @@ -15,11 +15,10 @@ use gpui::{ Task, UpgradeModelHandle, WeakModelHandle, }; use language::{ - range_from_lsp, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, File as _, Language, - LanguageRegistry, Operation, PointUtf16, Rope, + range_from_lsp, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, File as _, Operation, + PointUtf16, Rope, }; use lazy_static::lazy_static; -use lsp::LanguageServer; use parking_lot::Mutex; use postage::{ prelude::{Sink as _, Stream as _}, @@ -37,7 +36,7 @@ use std::{ ops::Deref, path::{Path, PathBuf}, sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, + atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}, Arc, }, time::{Duration, SystemTime}, @@ -74,29 +73,6 @@ pub enum Event { impl Entity for Worktree { type Event = Event; - - fn app_will_quit( - &mut self, - _: &mut MutableAppContext, - ) -> Option>>> { - use futures::FutureExt; - - if let Self::Local(worktree) = self { - let shutdown_futures = worktree - .language_servers - .drain() - .filter_map(|(_, server)| server.shutdown()) - .collect::>(); - Some( - async move { - futures::future::join_all(shutdown_futures).await; - } - .boxed(), - ) - } else { - None - } - } } impl Worktree { @@ -105,11 +81,10 @@ impl Worktree { user_store: ModelHandle, path: impl Into>, fs: Arc, - languages: Arc, cx: &mut AsyncAppContext, ) -> Result> { let (tree, scan_states_tx) = - LocalWorktree::new(client, user_store, path, fs.clone(), languages, cx).await?; + LocalWorktree::new(client, user_store, path, fs.clone(), cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.snapshot.abs_path.clone(); @@ -131,7 +106,6 @@ impl Worktree { worktree: proto::Worktree, client: Arc, user_store: ModelHandle, - languages: Arc, cx: &mut AsyncAppContext, ) -> Result> { let remote_id = worktree.id; @@ -238,7 +212,6 @@ impl Worktree { loading_buffers: Default::default(), open_buffers: Default::default(), queued_operations: Default::default(), - languages, user_store, diagnostic_summaries, }) @@ -306,13 +279,6 @@ impl Worktree { } } - pub fn languages(&self) -> &Arc { - match self { - Worktree::Local(worktree) => &worktree.language_registry, - Worktree::Remote(worktree) => &worktree.languages, - } - } - pub fn user_store(&self) -> &ModelHandle { match self { Worktree::Local(worktree) => &worktree.user_store, @@ -379,7 +345,7 @@ impl Worktree { &mut self, path: impl AsRef, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task, bool)>> { let path = path.as_ref(); // If there is already a buffer for the given path, then return it. @@ -388,9 +354,10 @@ impl Worktree { Worktree::Remote(worktree) => worktree.get_open_buffer(path, cx), }; if let Some(existing_buffer) = existing_buffer { - return cx.spawn(move |_, _| async move { Ok(existing_buffer) }); + return cx.spawn(move |_, _| async move { Ok((existing_buffer, false)) }); } + let is_new = Arc::new(AtomicBool::new(true)); let path: Arc = Arc::from(path); let mut loading_watch = match self.loading_buffers().entry(path.clone()) { // If the given path is already being loaded, then wait for that existing @@ -412,7 +379,10 @@ impl Worktree { // After the buffer loads, record the fact that it is no longer // loading. this.update(&mut cx, |this, _| this.loading_buffers().remove(&path)); - *tx.borrow_mut() = Some(result.map_err(|e| Arc::new(e))); + *tx.borrow_mut() = Some(match result { + Ok(buffer) => Ok((buffer, is_new)), + Err(error) => Err(Arc::new(error)), + }); }) .detach(); rx @@ -422,7 +392,10 @@ impl Worktree { cx.spawn(|_, _| async move { loop { if let Some(result) = loading_watch.borrow().as_ref() { - return result.clone().map_err(|e| anyhow!("{}", e)); + return match result { + Ok((buf, is_new)) => Ok((buf.clone(), is_new.fetch_and(false, SeqCst))), + Err(error) => Err(anyhow!("{}", error)), + }; } loading_watch.recv().await; } @@ -731,179 +704,6 @@ impl Worktree { } } - pub fn update_diagnostics( - &mut self, - params: lsp::PublishDiagnosticsParams, - disk_based_sources: &HashSet, - cx: &mut ModelContext, - ) -> Result<()> { - let this = self.as_local_mut().ok_or_else(|| anyhow!("not local"))?; - let abs_path = params - .uri - .to_file_path() - .map_err(|_| anyhow!("URI is not a file"))?; - let worktree_path = Arc::from( - abs_path - .strip_prefix(&this.abs_path) - .context("path is not within worktree")?, - ); - - let mut next_group_id = 0; - let mut diagnostics = Vec::default(); - let mut primary_diagnostic_group_ids = HashMap::default(); - let mut sources_by_group_id = HashMap::default(); - let mut supporting_diagnostic_severities = HashMap::default(); - for diagnostic in ¶ms.diagnostics { - let source = diagnostic.source.as_ref(); - let code = diagnostic.code.as_ref().map(|code| match code { - lsp::NumberOrString::Number(code) => code.to_string(), - lsp::NumberOrString::String(code) => code.clone(), - }); - let range = range_from_lsp(diagnostic.range); - let is_supporting = diagnostic - .related_information - .as_ref() - .map_or(false, |infos| { - infos.iter().any(|info| { - primary_diagnostic_group_ids.contains_key(&( - source, - code.clone(), - range_from_lsp(info.location.range), - )) - }) - }); - - if is_supporting { - if let Some(severity) = diagnostic.severity { - supporting_diagnostic_severities - .insert((source, code.clone(), range), severity); - } - } else { - let group_id = post_inc(&mut next_group_id); - let is_disk_based = - source.map_or(false, |source| disk_based_sources.contains(source)); - - sources_by_group_id.insert(group_id, source); - primary_diagnostic_group_ids - .insert((source, code.clone(), range.clone()), group_id); - - diagnostics.push(DiagnosticEntry { - range, - diagnostic: Diagnostic { - code: code.clone(), - severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR), - message: diagnostic.message.clone(), - group_id, - is_primary: true, - is_valid: true, - is_disk_based, - }, - }); - if let Some(infos) = &diagnostic.related_information { - for info in infos { - if info.location.uri == params.uri { - let range = range_from_lsp(info.location.range); - diagnostics.push(DiagnosticEntry { - range, - diagnostic: Diagnostic { - code: code.clone(), - severity: DiagnosticSeverity::INFORMATION, - message: info.message.clone(), - group_id, - is_primary: false, - is_valid: true, - is_disk_based, - }, - }); - } - } - } - } - } - - for entry in &mut diagnostics { - let diagnostic = &mut entry.diagnostic; - if !diagnostic.is_primary { - let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap(); - if let Some(&severity) = supporting_diagnostic_severities.get(&( - source, - diagnostic.code.clone(), - entry.range.clone(), - )) { - diagnostic.severity = severity; - } - } - } - - self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)?; - Ok(()) - } - - pub fn update_diagnostic_entries( - &mut self, - worktree_path: Arc, - version: Option, - diagnostics: Vec>, - cx: &mut ModelContext, - ) -> Result<()> { - let this = self.as_local_mut().unwrap(); - for buffer in this.open_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - if buffer - .read(cx) - .file() - .map_or(false, |file| *file.path() == worktree_path) - { - let (remote_id, operation) = buffer.update(cx, |buffer, cx| { - ( - buffer.remote_id(), - buffer.update_diagnostics(version, diagnostics.clone(), cx), - ) - }); - self.send_buffer_update(remote_id, operation?, cx); - break; - } - } - } - - let this = self.as_local_mut().unwrap(); - let summary = DiagnosticSummary::new(&diagnostics); - this.diagnostic_summaries - .insert(PathKey(worktree_path.clone()), summary.clone()); - this.diagnostics.insert(worktree_path.clone(), diagnostics); - - cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); - - if let Some(share) = this.share.as_ref() { - cx.foreground() - .spawn({ - let client = this.client.clone(); - let project_id = share.project_id; - let worktree_id = this.id().to_proto(); - let path = worktree_path.to_string_lossy().to_string(); - async move { - client - .send(proto::UpdateDiagnosticSummary { - project_id, - worktree_id, - summary: Some(proto::DiagnosticSummary { - path, - error_count: summary.error_count as u32, - warning_count: summary.warning_count as u32, - info_count: summary.info_count as u32, - hint_count: summary.hint_count as u32, - }), - }) - .await - .log_err() - } - }) - .detach(); - } - - Ok(()) - } - fn send_buffer_update( &mut self, buffer_id: u64, @@ -998,11 +798,9 @@ pub struct LocalWorktree { diagnostics: HashMap, Vec>>, diagnostic_summaries: TreeMap, queued_operations: Vec<(u64, Operation)>, - language_registry: Arc, client: Arc, user_store: ModelHandle, fs: Arc, - language_servers: HashMap>, } struct ShareState { @@ -1020,7 +818,6 @@ pub struct RemoteWorktree { replica_id: ReplicaId, loading_buffers: LoadingBuffers, open_buffers: HashMap, - languages: Arc, user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, diagnostic_summaries: TreeMap, @@ -1028,7 +825,9 @@ pub struct RemoteWorktree { type LoadingBuffers = HashMap< Arc, - postage::watch::Receiver, Arc>>>, + postage::watch::Receiver< + Option, Arc), Arc>>, + >, >; #[derive(Default, Deserialize)] @@ -1042,7 +841,6 @@ impl LocalWorktree { user_store: ModelHandle, path: impl Into>, fs: Arc, - languages: Arc, cx: &mut AsyncAppContext, ) -> Result<(ModelHandle, Sender)> { let abs_path = path.into(); @@ -1105,11 +903,9 @@ impl LocalWorktree { diagnostics: Default::default(), diagnostic_summaries: Default::default(), queued_operations: Default::default(), - language_registry: languages, client, user_store, fs, - language_servers: Default::default(), }; cx.spawn_weak(|this, mut cx| async move { @@ -1149,19 +945,6 @@ impl LocalWorktree { self.config.collaborators.clone() } - pub fn language_registry(&self) -> &LanguageRegistry { - &self.language_registry - } - - pub fn register_language( - &mut self, - language: &Arc, - language_server: &Arc, - ) { - self.language_servers - .insert(language.name().to_string(), language_server.clone()); - } - fn get_open_buffer( &mut self, path: &Path, @@ -1195,23 +978,13 @@ impl LocalWorktree { .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx)) .await?; - let (diagnostics, language, language_server) = this.update(&mut cx, |this, _| { - let this = this.as_local_mut().unwrap(); - let diagnostics = this.diagnostics.get(&path).cloned(); - let language = this - .language_registry - .select_language(file.full_path()) - .cloned(); - let server = language - .as_ref() - .and_then(|language| this.language_servers.get(language.name()).cloned()); - (diagnostics, language, server) + let diagnostics = this.update(&mut cx, |this, _| { + this.as_local_mut().unwrap().diagnostics.get(&path).cloned() }); let mut buffer_operations = Vec::new(); let buffer = cx.add_model(|cx| { let mut buffer = Buffer::from_file(0, contents, Box::new(file), cx); - buffer.set_language(language, language_server, cx); if let Some(diagnostics) = diagnostics { let op = buffer.update_diagnostics(None, diagnostics, cx).unwrap(); buffer_operations.push(op); @@ -1239,7 +1012,7 @@ impl LocalWorktree { cx.spawn(|this, mut cx| async move { let peer_id = envelope.original_sender_id(); let path = Path::new(&envelope.payload.path); - let buffer = this + let (buffer, _) = this .update(&mut cx, |this, cx| this.open_buffer(path, cx)) .await?; this.update(&mut cx, |this, cx| { @@ -1285,6 +1058,201 @@ impl LocalWorktree { cx.notify(); } + pub fn update_diagnostics( + &mut self, + worktree_path: Arc, + params: lsp::PublishDiagnosticsParams, + disk_based_sources: &HashSet, + cx: &mut ModelContext, + ) -> Result<()> { + let mut next_group_id = 0; + let mut diagnostics = Vec::default(); + let mut primary_diagnostic_group_ids = HashMap::default(); + let mut sources_by_group_id = HashMap::default(); + let mut supporting_diagnostic_severities = HashMap::default(); + for diagnostic in ¶ms.diagnostics { + let source = diagnostic.source.as_ref(); + let code = diagnostic.code.as_ref().map(|code| match code { + lsp::NumberOrString::Number(code) => code.to_string(), + lsp::NumberOrString::String(code) => code.clone(), + }); + let range = range_from_lsp(diagnostic.range); + let is_supporting = diagnostic + .related_information + .as_ref() + .map_or(false, |infos| { + infos.iter().any(|info| { + primary_diagnostic_group_ids.contains_key(&( + source, + code.clone(), + range_from_lsp(info.location.range), + )) + }) + }); + + if is_supporting { + if let Some(severity) = diagnostic.severity { + supporting_diagnostic_severities + .insert((source, code.clone(), range), severity); + } + } else { + let group_id = post_inc(&mut next_group_id); + let is_disk_based = + source.map_or(false, |source| disk_based_sources.contains(source)); + + sources_by_group_id.insert(group_id, source); + primary_diagnostic_group_ids + .insert((source, code.clone(), range.clone()), group_id); + + diagnostics.push(DiagnosticEntry { + range, + diagnostic: Diagnostic { + code: code.clone(), + severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR), + message: diagnostic.message.clone(), + group_id, + is_primary: true, + is_valid: true, + is_disk_based, + }, + }); + if let Some(infos) = &diagnostic.related_information { + for info in infos { + if info.location.uri == params.uri { + let range = range_from_lsp(info.location.range); + diagnostics.push(DiagnosticEntry { + range, + diagnostic: Diagnostic { + code: code.clone(), + severity: DiagnosticSeverity::INFORMATION, + message: info.message.clone(), + group_id, + is_primary: false, + is_valid: true, + is_disk_based, + }, + }); + } + } + } + } + } + + for entry in &mut diagnostics { + let diagnostic = &mut entry.diagnostic; + if !diagnostic.is_primary { + let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap(); + if let Some(&severity) = supporting_diagnostic_severities.get(&( + source, + diagnostic.code.clone(), + entry.range.clone(), + )) { + diagnostic.severity = severity; + } + } + } + + self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)?; + Ok(()) + } + + pub fn update_diagnostic_entries( + &mut self, + worktree_path: Arc, + version: Option, + diagnostics: Vec>, + cx: &mut ModelContext, + ) -> Result<()> { + for buffer in self.open_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + if buffer + .read(cx) + .file() + .map_or(false, |file| *file.path() == worktree_path) + { + let (remote_id, operation) = buffer.update(cx, |buffer, cx| { + ( + buffer.remote_id(), + buffer.update_diagnostics(version, diagnostics.clone(), cx), + ) + }); + self.send_buffer_update(remote_id, operation?, cx); + break; + } + } + } + + let summary = DiagnosticSummary::new(&diagnostics); + self.diagnostic_summaries + .insert(PathKey(worktree_path.clone()), summary.clone()); + self.diagnostics.insert(worktree_path.clone(), diagnostics); + + cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); + + if let Some(share) = self.share.as_ref() { + cx.foreground() + .spawn({ + let client = self.client.clone(); + let project_id = share.project_id; + let worktree_id = self.id().to_proto(); + let path = worktree_path.to_string_lossy().to_string(); + async move { + client + .send(proto::UpdateDiagnosticSummary { + project_id, + worktree_id, + summary: Some(proto::DiagnosticSummary { + path, + error_count: summary.error_count as u32, + warning_count: summary.warning_count as u32, + info_count: summary.info_count as u32, + hint_count: summary.hint_count as u32, + }), + }) + .await + .log_err() + } + }) + .detach(); + } + + Ok(()) + } + + fn send_buffer_update( + &mut self, + buffer_id: u64, + operation: Operation, + cx: &mut ModelContext, + ) -> Option<()> { + let share = self.share.as_ref()?; + let project_id = share.project_id; + let worktree_id = self.id(); + let rpc = self.client.clone(); + cx.spawn(|worktree, mut cx| async move { + if let Err(error) = rpc + .request(proto::UpdateBuffer { + project_id, + worktree_id: worktree_id.0 as u64, + buffer_id, + operations: vec![language::proto::serialize_operation(&operation)], + }) + .await + { + worktree.update(&mut cx, |worktree, _| { + log::error!("error sending buffer operation: {}", error); + worktree + .as_local_mut() + .unwrap() + .queued_operations + .push((buffer_id, operation)); + }); + } + }) + .detach(); + None + } + pub fn scan_complete(&self) -> impl Future { let mut scan_state_rx = self.last_scan_state_rx.clone(); async move { @@ -1375,21 +1343,8 @@ impl LocalWorktree { } }); - let (language, language_server) = this.update(&mut cx, |worktree, _| { - let worktree = worktree.as_local_mut().unwrap(); - let language = worktree - .language_registry() - .select_language(file.full_path()) - .cloned(); - let language_server = language - .as_ref() - .and_then(|language| worktree.language_servers.get(language.name()).cloned()); - (language, language_server.clone()) - }); - buffer_handle.update(&mut cx, |buffer, cx| { buffer.did_save(version, file.mtime, Some(Box::new(file)), cx); - buffer.set_language(language, language_server, cx); }); Ok(()) @@ -1578,16 +1533,10 @@ impl RemoteWorktree { mtime: entry.mtime, is_local: false, }; - let language = this.read_with(&cx, |this, _| { - use language::File; - this.languages().select_language(file.full_path()).cloned() - }); let remote_buffer = response.buffer.ok_or_else(|| anyhow!("empty buffer"))?; let buffer_id = remote_buffer.id as usize; let buffer = cx.add_model(|cx| { - Buffer::from_proto(replica_id, remote_buffer, Some(Box::new(file)), cx) - .unwrap() - .with_language(language, None, cx) + Buffer::from_proto(replica_id, remote_buffer, Some(Box::new(file)), cx).unwrap() }); this.update(&mut cx, move |this, cx| { let this = this.as_remote_mut().unwrap(); @@ -3129,9 +3078,7 @@ mod tests { use anyhow::Result; use client::test::{FakeHttpClient, FakeServer}; use fs::RealFs; - use gpui::test::subscribe; - use language::{tree_sitter_rust, DiagnosticEntry, Language, LanguageServerConfig}; - use language::{Diagnostic, LanguageConfig}; + use language::{Diagnostic, DiagnosticEntry}; use lsp::Url; use rand::prelude::*; use serde_json::json; @@ -3169,7 +3116,6 @@ mod tests { user_store, Arc::from(Path::new("/root")), Arc::new(fs), - Default::default(), &mut cx.to_async(), ) .await @@ -3207,12 +3153,11 @@ mod tests { user_store, dir.path(), Arc::new(RealFs), - Default::default(), &mut cx.to_async(), ) .await .unwrap(); - let buffer = tree + let (buffer, _) = tree .update(&mut cx, |tree, cx| tree.open_buffer("file1", cx)) .await .unwrap(); @@ -3242,7 +3187,6 @@ mod tests { user_store, file_path.clone(), Arc::new(RealFs), - Default::default(), &mut cx.to_async(), ) .await @@ -3251,7 +3195,7 @@ mod tests { .await; cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1)); - let buffer = tree + let (buffer, _) = tree .update(&mut cx, |tree, cx| tree.open_buffer("", cx)) .await .unwrap(); @@ -3291,7 +3235,6 @@ mod tests { user_store.clone(), dir.path(), Arc::new(RealFs), - Default::default(), &mut cx.to_async(), ) .await @@ -3299,7 +3242,7 @@ mod tests { let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { let buffer = tree.update(cx, |tree, cx| tree.open_buffer(path, cx)); - async move { buffer.await.unwrap() } + async move { buffer.await.unwrap().0 } }; let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| { tree.read_with(cx, |tree, _| { @@ -3330,7 +3273,6 @@ mod tests { initial_snapshot.to_proto(&Default::default()), Client::new(http_client.clone()), user_store, - Default::default(), &mut cx.to_async(), ) .await @@ -3443,7 +3385,6 @@ mod tests { user_store, dir.path(), Arc::new(RealFs), - Default::default(), &mut cx.to_async(), ) .await @@ -3496,7 +3437,6 @@ mod tests { user_store, "/the-dir".as_ref(), fs, - Default::default(), &mut cx.to_async(), ) .await @@ -3511,9 +3451,9 @@ mod tests { ) }); - let buffer_a_1 = buffer_a_1.await.unwrap(); - let buffer_a_2 = buffer_a_2.await.unwrap(); - let buffer_b = buffer_b.await.unwrap(); + let buffer_a_1 = buffer_a_1.await.unwrap().0; + let buffer_a_2 = buffer_a_2.await.unwrap().0; + let buffer_b = buffer_b.await.unwrap().0; assert_eq!(buffer_a_1.read_with(&cx, |b, _| b.text()), "a-contents"); assert_eq!(buffer_b.read_with(&cx, |b, _| b.text()), "b-contents"); @@ -3526,7 +3466,8 @@ mod tests { let buffer_a_3 = worktree .update(&mut cx, |worktree, cx| worktree.open_buffer("a.txt", cx)) .await - .unwrap(); + .unwrap() + .0; // There's still only one buffer per path. assert_eq!(buffer_a_3.id(), buffer_a_id); @@ -3550,7 +3491,6 @@ mod tests { user_store, dir.path(), Arc::new(RealFs), - Default::default(), &mut cx.to_async(), ) .await @@ -3559,7 +3499,7 @@ mod tests { cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; - let buffer1 = tree + let (buffer1, _) = tree .update(&mut cx, |tree, cx| tree.open_buffer("file1", cx)) .await .unwrap(); @@ -3626,7 +3566,7 @@ mod tests { // When a file is deleted, the buffer is considered dirty. let events = Rc::new(RefCell::new(Vec::new())); - let buffer2 = tree + let (buffer2, _) = tree .update(&mut cx, |tree, cx| tree.open_buffer("file2", cx)) .await .unwrap(); @@ -3647,7 +3587,7 @@ mod tests { // When a file is already dirty when deleted, we don't emit a Dirtied event. let events = Rc::new(RefCell::new(Vec::new())); - let buffer3 = tree + let (buffer3, _) = tree .update(&mut cx, |tree, cx| tree.open_buffer("file3", cx)) .await .unwrap(); @@ -3687,7 +3627,6 @@ mod tests { user_store, dir.path(), Arc::new(RealFs), - Default::default(), &mut cx.to_async(), ) .await @@ -3696,7 +3635,7 @@ mod tests { .await; let abs_path = dir.path().join("the-file"); - let buffer = tree + let (buffer, _) = tree .update(&mut cx, |tree, cx| { tree.open_buffer(Path::new("the-file"), cx) }) @@ -3775,115 +3714,6 @@ mod tests { .await; } - #[gpui::test] - async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { - let (language_server_config, mut fake_server) = - LanguageServerConfig::fake(cx.background()).await; - let progress_token = language_server_config - .disk_based_diagnostics_progress_token - .clone() - .unwrap(); - let mut languages = LanguageRegistry::new(); - languages.add(Arc::new(Language::new( - LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - language_server: Some(language_server_config), - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ))); - - let dir = temp_tree(json!({ - "a.rs": "fn a() { A }", - "b.rs": "const y: i32 = 1", - })); - - let http_client = FakeHttpClient::with_404_response(); - let client = Client::new(http_client.clone()); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - - let tree = Worktree::open_local( - client, - user_store, - dir.path(), - Arc::new(RealFs), - Arc::new(languages), - &mut cx.to_async(), - ) - .await - .unwrap(); - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; - - // Cause worktree to start the fake language server - let _buffer = tree - .update(&mut cx, |tree, cx| tree.open_buffer("b.rs", cx)) - .await - .unwrap(); - - let mut events = subscribe(&tree, &mut cx); - - fake_server.start_progress(&progress_token).await; - assert_eq!( - events.next().await.unwrap(), - Event::DiskBasedDiagnosticsUpdating - ); - - fake_server.start_progress(&progress_token).await; - fake_server.end_progress(&progress_token).await; - fake_server.start_progress(&progress_token).await; - - fake_server - .notify::(lsp::PublishDiagnosticsParams { - uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(), - version: None, - diagnostics: vec![lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), - severity: Some(lsp::DiagnosticSeverity::ERROR), - message: "undefined variable 'A'".to_string(), - ..Default::default() - }], - }) - .await; - assert_eq!( - events.next().await.unwrap(), - Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs"))) - ); - - fake_server.end_progress(&progress_token).await; - fake_server.end_progress(&progress_token).await; - assert_eq!( - events.next().await.unwrap(), - Event::DiskBasedDiagnosticsUpdated - ); - - let buffer = tree - .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx)) - .await - .unwrap(); - - buffer.read_with(&cx, |buffer, _| { - let snapshot = buffer.snapshot(); - let diagnostics = snapshot - .diagnostics_in_range::<_, Point>(0..buffer.len()) - .collect::>(); - assert_eq!( - diagnostics, - &[DiagnosticEntry { - range: Point::new(0, 9)..Point::new(0, 10), - diagnostic: Diagnostic { - severity: lsp::DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string(), - group_id: 0, - is_primary: true, - ..Default::default() - } - }] - ) - }); - } - #[gpui::test] async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { let fs = Arc::new(FakeFs::new()); @@ -3911,13 +3741,12 @@ mod tests { user_store, "/the-dir".as_ref(), fs, - Default::default(), &mut cx.to_async(), ) .await .unwrap(); - let buffer = worktree + let (buffer, _) = worktree .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx)) .await .unwrap(); @@ -4024,7 +3853,12 @@ mod tests { worktree .update(&mut cx, |tree, cx| { - tree.update_diagnostics(message, &Default::default(), cx) + tree.as_local_mut().unwrap().update_diagnostics( + Arc::from("a.rs".as_ref()), + message, + &Default::default(), + cx, + ) }) .unwrap(); let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 0c1f6a52873de6f96f765dc6befd92aea16741eb..29480fb4920054ef0800c850909173e0770b2090 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1213,7 +1213,8 @@ mod tests { let buffer_b = worktree_b .update(&mut cx_b, |worktree, cx| worktree.open_buffer("b.txt", cx)) .await - .unwrap(); + .unwrap() + .0; let buffer_b = cx_b.add_model(|cx| MultiBuffer::singleton(buffer_b, cx)); buffer_b.read_with(&cx_b, |buf, cx| { assert_eq!(buf.read(cx).text(), "b-contents") @@ -1222,7 +1223,8 @@ mod tests { let buffer_a = worktree_a .update(&mut cx_a, |tree, cx| tree.open_buffer("b.txt", cx)) .await - .unwrap(); + .unwrap() + .0; let editor_b = cx_b.add_view(window_b, |cx| { Editor::for_buffer(buffer_b, Arc::new(|cx| EditorSettings::test(cx)), cx) @@ -1436,11 +1438,13 @@ mod tests { let buffer_b = worktree_b .update(&mut cx_b, |tree, cx| tree.open_buffer("file1", cx)) .await - .unwrap(); + .unwrap() + .0; let buffer_c = worktree_c .update(&mut cx_c, |tree, cx| tree.open_buffer("file1", cx)) .await - .unwrap(); + .unwrap() + .0; buffer_b.update(&mut cx_b, |buf, cx| buf.edit([0..0], "i-am-b, ", cx)); buffer_c.update(&mut cx_c, |buf, cx| buf.edit([0..0], "i-am-c, ", cx)); @@ -1448,7 +1452,8 @@ mod tests { let buffer_a = worktree_a .update(&mut cx_a, |tree, cx| tree.open_buffer("file1", cx)) .await - .unwrap(); + .unwrap() + .0; buffer_a .condition(&mut cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ") @@ -1574,7 +1579,8 @@ mod tests { let buffer_b = worktree_b .update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.txt", cx)) .await - .unwrap(); + .unwrap() + .0; let mtime = buffer_b.read_with(&cx_b, |buf, _| buf.file().unwrap().mtime()); buffer_b.update(&mut cx_b, |buf, cx| buf.edit([0..0], "world ", cx)); @@ -1669,7 +1675,8 @@ mod tests { let buffer_a = worktree_a .update(&mut cx_a, |tree, cx| tree.open_buffer("a.txt", cx)) .await - .unwrap(); + .unwrap() + .0; // Start opening the same buffer as client B let buffer_b = cx_b @@ -1681,7 +1688,7 @@ mod tests { buffer_a.update(&mut cx_a, |buf, cx| buf.edit([0..0], "z", cx)); let text = buffer_a.read_with(&cx_a, |buf, _| buf.text()); - let buffer_b = buffer_b.await.unwrap(); + let buffer_b = buffer_b.await.unwrap().0; buffer_b.condition(&cx_b, |buf, _| buf.text() == text).await; } @@ -2016,7 +2023,8 @@ mod tests { .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) .await - .unwrap(); + .unwrap() + .0; buffer_b.read_with(&cx_b, |buffer, _| { assert_eq!( @@ -2128,7 +2136,8 @@ mod tests { .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) .await - .unwrap(); + .unwrap() + .0; let format = buffer_b.update(&mut cx_b, |buffer, cx| buffer.format(cx)); let (request_id, _) = fake_language_server From 0992132a0d63ea041de74816bc41ea63586a6185 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 19 Jan 2022 14:48:54 -0800 Subject: [PATCH 08/31] Always open buffers via the project --- crates/editor/src/items.rs | 10 ++--- crates/project/src/project.rs | 43 +++++++++++++++---- crates/project/src/worktree.rs | 68 +++++-------------------------- crates/workspace/src/workspace.rs | 15 ++----- 4 files changed, 53 insertions(+), 83 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 6b170e12e96bac341c2a1e729d56e13c17361ea0..24dbebb6c7b9e227c69e8c37806c6485a1229050 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -7,7 +7,7 @@ use gpui::{ use language::{Bias, Buffer, Diagnostic}; use postage::watch; use project::worktree::File; -use project::{Project, ProjectEntry, ProjectPath, Worktree}; +use project::{Project, ProjectEntry, ProjectPath}; use std::rc::Rc; use std::{fmt::Write, path::PathBuf}; use text::{Point, Selection}; @@ -28,13 +28,13 @@ struct WeakBufferItemHandle(WeakModelHandle); impl PathOpener for BufferOpener { fn open( &self, - worktree: &mut Worktree, + project: &mut Project, project_path: ProjectPath, - cx: &mut ModelContext, + cx: &mut ModelContext, ) -> Option>>> { - let buffer = worktree.open_buffer(project_path.path, cx); + let buffer = project.open_buffer(project_path, cx); let task = cx.spawn(|_, _| async move { - let buffer = buffer.await?.0; + let buffer = buffer.await?; Ok(Box::new(BufferItemHandle(buffer)) as Box) }); Some(task) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 93c91cdb22359d1cf6630f09a1018d2cd019270c..f3b29467f25c19720a43ce145d5aca74da4edf2f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1088,26 +1088,51 @@ impl Project { rpc: Arc, cx: &mut ModelContext, ) -> anyhow::Result<()> { + let receipt = envelope.receipt(); + let peer_id = envelope.original_sender_id()?; let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); - if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { - return worktree.update(cx, |worktree, cx| { - worktree.handle_open_buffer(envelope, rpc, cx) - }); - } else { - Err(anyhow!("no such worktree")) - } + let worktree = self + .worktree_for_id(worktree_id, cx) + .ok_or_else(|| anyhow!("no such worktree"))?; + + let task = self.open_buffer( + ProjectPath { + worktree_id, + path: PathBuf::from(envelope.payload.path).into(), + }, + cx, + ); + cx.spawn(|_, mut cx| { + async move { + let buffer = task.await?; + let response = worktree.update(&mut cx, |worktree, cx| { + worktree + .as_local_mut() + .unwrap() + .open_remote_buffer(peer_id, buffer, cx) + }); + rpc.respond(receipt, response).await?; + Ok(()) + } + .log_err() + }) + .detach(); + Ok(()) } pub fn handle_close_buffer( &mut self, envelope: TypedEnvelope, - rpc: Arc, + _: Arc, cx: &mut ModelContext, ) -> anyhow::Result<()> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { worktree.update(cx, |worktree, cx| { - worktree.handle_close_buffer(envelope, rpc, cx) + worktree + .as_local_mut() + .unwrap() + .close_remote_buffer(envelope, cx) })?; } Ok(()) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ab26524473f8d1a5c70763b9ebbadf817bab1418..15b82bc884a0b3575afe60778c11ca7e5e9fa939 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -286,43 +286,6 @@ impl Worktree { } } - pub fn handle_open_buffer( - &mut self, - envelope: TypedEnvelope, - rpc: Arc, - cx: &mut ModelContext, - ) -> anyhow::Result<()> { - let receipt = envelope.receipt(); - - let response = self - .as_local_mut() - .unwrap() - .open_remote_buffer(envelope, cx); - - cx.background() - .spawn( - async move { - rpc.respond(receipt, response.await?).await?; - Ok(()) - } - .log_err(), - ) - .detach(); - - Ok(()) - } - - pub fn handle_close_buffer( - &mut self, - envelope: TypedEnvelope, - _: Arc, - cx: &mut ModelContext, - ) -> anyhow::Result<()> { - self.as_local_mut() - .unwrap() - .close_remote_buffer(envelope, cx) - } - pub fn diagnostic_summaries<'a>( &'a self, ) -> impl Iterator, DiagnosticSummary)> + 'a { @@ -1006,28 +969,17 @@ impl LocalWorktree { pub fn open_remote_buffer( &mut self, - envelope: TypedEnvelope, + peer_id: PeerId, + buffer: ModelHandle, cx: &mut ModelContext, - ) -> Task> { - cx.spawn(|this, mut cx| async move { - let peer_id = envelope.original_sender_id(); - let path = Path::new(&envelope.payload.path); - let (buffer, _) = this - .update(&mut cx, |this, cx| this.open_buffer(path, cx)) - .await?; - this.update(&mut cx, |this, cx| { - this.as_local_mut() - .unwrap() - .shared_buffers - .entry(peer_id?) - .or_default() - .insert(buffer.id() as u64, buffer.clone()); - - Ok(proto::OpenBufferResponse { - buffer: Some(buffer.update(cx.as_mut(), |buffer, _| buffer.to_proto())), - }) - }) - }) + ) -> proto::OpenBufferResponse { + self.shared_buffers + .entry(peer_id) + .or_default() + .insert(buffer.id() as u64, buffer.clone()); + proto::OpenBufferResponse { + buffer: Some(buffer.update(cx.as_mut(), |buffer, _| buffer.to_proto())), + } } pub fn close_remote_buffer( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index da7c40d5f6402c1aec897dbceea19c0a403cec0f..64eeb3ee72a71b0a1f715cb3263ebc8daea1de73 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -125,9 +125,9 @@ pub struct JoinProjectParams { pub trait PathOpener { fn open( &self, - worktree: &mut Worktree, + project: &mut Project, path: ProjectPath, - cx: &mut ModelContext, + cx: &mut ModelContext, ) -> Option>>>; } @@ -766,18 +766,11 @@ impl Workspace { return Task::ready(Ok(existing_item)); } - let worktree = match self.project.read(cx).worktree_for_id(path.worktree_id, cx) { - Some(worktree) => worktree, - None => { - return Task::ready(Err(anyhow!("worktree {} does not exist", path.worktree_id))); - } - }; - let project_path = path.clone(); let path_openers = self.path_openers.clone(); - worktree.update(cx, |worktree, cx| { + self.project.update(cx, |project, cx| { for opener in path_openers.iter() { - if let Some(task) = opener.open(worktree, project_path.clone(), cx) { + if let Some(task) = opener.open(project, project_path.clone(), cx) { return task; } } From e56c043693fef846f8f903d58fed1bf1bbe7dbfa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 19 Jan 2022 16:32:55 -0800 Subject: [PATCH 09/31] Get tests passing, centralize more diagnostic logic in Project --- crates/diagnostics/src/diagnostics.rs | 18 +- crates/project/src/project.rs | 266 ++++++++++++++------------ crates/project/src/worktree.rs | 45 ++--- crates/server/src/rpc.rs | 10 +- 4 files changed, 177 insertions(+), 162 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 4334429a52e227f63c3536a38349496c93181e9a..2f253bff28922d932d4aaa9d20d921c550976889 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -680,7 +680,6 @@ mod tests { use editor::{display_map::BlockContext, DisplayPoint, EditorSnapshot}; use gpui::TestAppContext; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16}; - use project::worktree; use serde_json::json; use std::sync::Arc; use unindent::Unindent as _; @@ -727,6 +726,7 @@ mod tests { }) .await .unwrap(); + let worktree_id = worktree.read_with(&cx, |tree, _| tree.id()); // Create some diagnostics worktree.update(&mut cx, |worktree, cx| { @@ -903,7 +903,13 @@ mod tests { cx, ) .unwrap(); - cx.emit(worktree::Event::DiskBasedDiagnosticsUpdated); + }); + project.update(&mut cx, |_, cx| { + cx.emit(project::Event::DiagnosticsUpdated(ProjectPath { + worktree_id, + path: Arc::from("/test/consts.rs".as_ref()), + })); + cx.emit(project::Event::DiskBasedDiagnosticsUpdated { worktree_id }); }); view.next_notification(&cx).await; @@ -1017,7 +1023,13 @@ mod tests { cx, ) .unwrap(); - cx.emit(worktree::Event::DiskBasedDiagnosticsUpdated); + }); + project.update(&mut cx, |_, cx| { + cx.emit(project::Event::DiagnosticsUpdated(ProjectPath { + worktree_id, + path: Arc::from("/test/consts.rs".as_ref()), + })); + cx.emit(project::Event::DiskBasedDiagnosticsUpdated { worktree_id }); }); view.next_notification(&cx).await; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f3b29467f25c19720a43ce145d5aca74da4edf2f..4b16494d9dfa3e204ad3d6c6c5c204e2922eb3f3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -14,6 +14,7 @@ use gpui::{ use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry}; use lsp::{DiagnosticSeverity, LanguageServer}; use postage::{prelude::Stream, watch}; +use smol::block_on; use std::{ path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, @@ -34,7 +35,7 @@ pub struct Project { client_state: ProjectClientState, collaborators: HashMap, subscriptions: Vec, - pending_disk_based_diagnostics: isize, + language_servers_with_diagnostics_running: isize, } enum ProjectClientState { @@ -58,7 +59,7 @@ pub struct Collaborator { pub replica_id: ReplicaId, } -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(WorktreeId), @@ -191,7 +192,7 @@ impl Project { client, user_store, fs, - pending_disk_based_diagnostics: 0, + language_servers_with_diagnostics_running: 0, language_servers: Default::default(), } }) @@ -283,7 +284,7 @@ impl Project { remote_id, replica_id, }, - pending_disk_based_diagnostics: 0, + language_servers_with_diagnostics_running: 0, language_servers: Default::default(), }; for worktree in worktrees { @@ -473,7 +474,7 @@ impl Project { let (buffer, buffer_is_new) = buffer_task.await?; if buffer_is_new { this.update(&mut cx, |this, cx| { - this.buffer_added(worktree, buffer.clone(), cx) + this.assign_language_to_buffer(worktree, buffer.clone(), cx) }); } Ok(buffer) @@ -497,12 +498,14 @@ impl Project { .save_buffer_as(buffer.clone(), path, cx) }) .await?; - this.update(&mut cx, |this, cx| this.buffer_added(worktree, buffer, cx)); + this.update(&mut cx, |this, cx| { + this.assign_language_to_buffer(worktree, buffer, cx) + }); Ok(()) }) } - fn buffer_added( + fn assign_language_to_buffer( &mut self, worktree: ModelHandle, buffer: ModelHandle, @@ -548,17 +551,16 @@ impl Project { worktree_path: &Path, cx: &mut ModelContext, ) -> Option> { + enum LspEvent { + DiagnosticsStart, + DiagnosticsUpdate(lsp::PublishDiagnosticsParams), + DiagnosticsFinish, + } + let language_server = language .start_server(worktree_path, cx) .log_err() .flatten()?; - - enum DiagnosticProgress { - Updating, - Publish(lsp::PublishDiagnosticsParams), - Updated, - } - let disk_based_sources = language .disk_based_diagnostic_sources() .cloned() @@ -569,22 +571,25 @@ impl Project { disk_based_diagnostics_progress_token.is_some(); let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + // Listen for `PublishDiagnostics` notifications. language_server .on_notification::({ let diagnostics_tx = diagnostics_tx.clone(); move |params| { if !has_disk_based_diagnostic_progress_token { - smol::block_on(diagnostics_tx.send(DiagnosticProgress::Updating)).ok(); + block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok(); } - smol::block_on(diagnostics_tx.send(DiagnosticProgress::Publish(params))).ok(); + block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok(); if !has_disk_based_diagnostic_progress_token { - smol::block_on(diagnostics_tx.send(DiagnosticProgress::Updated)).ok(); + block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok(); } } }) .detach(); - let mut pending_disk_based_diagnostics: i32 = 0; + // Listen for `Progress` notifications. Send an event when the language server + // transitions between running jobs and not running any jobs. + let mut running_jobs_for_this_server: i32 = 0; language_server .on_notification::(move |params| { let token = match params.token { @@ -596,21 +601,15 @@ impl Project { match params.value { lsp::ProgressParamsValue::WorkDone(progress) => match progress { lsp::WorkDoneProgress::Begin(_) => { - if pending_disk_based_diagnostics == 0 { - smol::block_on( - diagnostics_tx.send(DiagnosticProgress::Updating), - ) - .ok(); + running_jobs_for_this_server += 1; + if running_jobs_for_this_server == 1 { + block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok(); } - pending_disk_based_diagnostics += 1; } lsp::WorkDoneProgress::End(_) => { - pending_disk_based_diagnostics -= 1; - if pending_disk_based_diagnostics == 0 { - smol::block_on( - diagnostics_tx.send(DiagnosticProgress::Updated), - ) - .ok(); + running_jobs_for_this_server -= 1; + if running_jobs_for_this_server == 0 { + block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok(); } } _ => {} @@ -620,42 +619,43 @@ impl Project { }) .detach(); + // Process all the LSP events. cx.spawn_weak(|this, mut cx| async move { while let Ok(message) = diagnostics_rx.recv().await { let this = cx.read(|cx| this.upgrade(cx))?; match message { - DiagnosticProgress::Updating => { - let project_id = this.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsStarted); - this.remote_id() - }); - if let Some(project_id) = project_id { - rpc.send(proto::DiskBasedDiagnosticsUpdating { - project_id, - worktree_id: worktree_id.to_proto(), + LspEvent::DiagnosticsStart => { + let send = this.update(&mut cx, |this, cx| { + this.disk_based_diagnostics_started(worktree_id, cx); + this.remote_id().map(|project_id| { + rpc.send(proto::DiskBasedDiagnosticsUpdating { + project_id, + worktree_id: worktree_id.to_proto(), + }) }) - .await - .log_err(); + }); + if let Some(send) = send { + send.await.log_err(); } } - DiagnosticProgress::Publish(params) => { + LspEvent::DiagnosticsUpdate(params) => { this.update(&mut cx, |this, cx| { this.update_diagnostics(params, &disk_based_sources, cx) .log_err(); }); } - DiagnosticProgress::Updated => { - let project_id = this.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsFinished); - this.remote_id() - }); - if let Some(project_id) = project_id { - rpc.send(proto::DiskBasedDiagnosticsUpdated { - project_id, - worktree_id: worktree_id.to_proto(), + LspEvent::DiagnosticsFinish => { + let send = this.update(&mut cx, |this, cx| { + this.disk_based_diagnostics_finished(worktree_id, cx); + this.remote_id().map(|project_id| { + rpc.send(proto::DiskBasedDiagnosticsUpdated { + project_id, + worktree_id: worktree_id.to_proto(), + }) }) - .await - .log_err(); + }); + if let Some(send) = send { + send.await.log_err(); } } } @@ -682,17 +682,24 @@ impl Project { path.strip_prefix(tree.as_local()?.abs_path()).ok() }); if let Some(relative_path) = relative_path { - return tree.update(cx, |tree, cx| { + let worktree_id = tree.read(cx).id(); + let project_path = ProjectPath { + worktree_id, + path: relative_path.into(), + }; + tree.update(cx, |tree, cx| { tree.as_local_mut().unwrap().update_diagnostics( - relative_path.into(), + project_path.path.clone(), diagnostics, disk_based_sources, cx, ) - }); + })?; + cx.emit(Event::DiagnosticsUpdated(project_path)); + break; } } - todo!() + Ok(()) } pub fn worktree_for_abs_path( @@ -769,32 +776,6 @@ impl Project { fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - cx.subscribe(&worktree, move |this, worktree, event, cx| match event { - worktree::Event::DiagnosticsUpdated(path) => { - cx.emit(Event::DiagnosticsUpdated(ProjectPath { - worktree_id: worktree.read(cx).id(), - path: path.clone(), - })); - } - worktree::Event::DiskBasedDiagnosticsUpdating => { - if this.pending_disk_based_diagnostics == 0 { - cx.emit(Event::DiskBasedDiagnosticsStarted); - } - this.pending_disk_based_diagnostics += 1; - } - worktree::Event::DiskBasedDiagnosticsUpdated => { - this.pending_disk_based_diagnostics -= 1; - cx.emit(Event::DiskBasedDiagnosticsUpdated { - worktree_id: worktree.read(cx).id(), - }); - if this.pending_disk_based_diagnostics == 0 { - if this.pending_disk_based_diagnostics == 0 { - cx.emit(Event::DiskBasedDiagnosticsFinished); - } - } - } - }) - .detach(); self.worktrees.push(worktree); cx.notify(); } @@ -823,7 +804,7 @@ impl Project { } pub fn is_running_disk_based_diagnostics(&self) -> bool { - self.pending_disk_based_diagnostics > 0 + self.language_servers_with_diagnostics_running > 0 } pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary { @@ -850,6 +831,25 @@ impl Project { }) } + fn disk_based_diagnostics_started(&mut self, _: WorktreeId, cx: &mut ModelContext) { + self.language_servers_with_diagnostics_running += 1; + if self.language_servers_with_diagnostics_running == 1 { + cx.emit(Event::DiskBasedDiagnosticsStarted); + } + } + + fn disk_based_diagnostics_finished( + &mut self, + worktree_id: WorktreeId, + cx: &mut ModelContext, + ) { + cx.emit(Event::DiskBasedDiagnosticsUpdated { worktree_id }); + self.language_servers_with_diagnostics_running -= 1; + if self.language_servers_with_diagnostics_running == 0 { + cx.emit(Event::DiskBasedDiagnosticsFinished); + } + } + pub fn active_entry(&self) -> Option { self.active_entry } @@ -991,12 +991,19 @@ impl Project { ) -> Result<()> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { - worktree.update(cx, |worktree, cx| { - worktree - .as_remote_mut() - .unwrap() - .update_diagnostic_summary(envelope, cx); - }); + if let Some(summary) = envelope.payload.summary { + let project_path = ProjectPath { + worktree_id, + path: Path::new(&summary.path).into(), + }; + worktree.update(cx, |worktree, _| { + worktree + .as_remote_mut() + .unwrap() + .update_diagnostic_summary(project_path.path.clone(), &summary); + }); + cx.emit(Event::DiagnosticsUpdated(project_path)); + } } Ok(()) } @@ -1007,15 +1014,10 @@ impl Project { _: Arc, cx: &mut ModelContext, ) -> Result<()> { - let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); - if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { - worktree.update(cx, |worktree, cx| { - worktree - .as_remote() - .unwrap() - .disk_based_diagnostics_updating(cx); - }); - } + self.disk_based_diagnostics_started( + WorktreeId::from_proto(envelope.payload.worktree_id), + cx, + ); Ok(()) } @@ -1025,15 +1027,10 @@ impl Project { _: Arc, cx: &mut ModelContext, ) -> Result<()> { - let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); - if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { - worktree.update(cx, |worktree, cx| { - worktree - .as_remote() - .unwrap() - .disk_based_diagnostics_updated(cx); - }); - } + self.disk_based_diagnostics_finished( + WorktreeId::from_proto(envelope.payload.worktree_id), + cx, + ); Ok(()) } @@ -1318,7 +1315,7 @@ impl Collaborator { #[cfg(test)] mod tests { - use super::*; + use super::{Event, *}; use client::test::FakeHttpClient; use fs::RealFs; use futures::StreamExt; @@ -1402,6 +1399,7 @@ mod tests { .disk_based_diagnostics_progress_token .clone() .unwrap(); + let mut languages = LanguageRegistry::new(); languages.add(Arc::new(Language::new( LanguageConfig { @@ -1422,30 +1420,47 @@ mod tests { let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let tree = Worktree::open_local( - client, - user_store, - dir.path(), - Arc::new(RealFs), - &mut cx.to_async(), - ) - .await - .unwrap(); + let project = cx.update(|cx| { + Project::local( + client, + user_store, + Arc::new(languages), + Arc::new(RealFs), + cx, + ) + }); + + let tree = project + .update(&mut cx, |project, cx| { + project.add_local_worktree(dir.path(), cx) + }) + .await + .unwrap(); + let worktree_id = tree.read_with(&cx, |tree, _| tree.id()); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; // Cause worktree to start the fake language server - let _buffer = tree - .update(&mut cx, |tree, cx| tree.open_buffer("b.rs", cx)) + let _buffer = project + .update(&mut cx, |project, cx| { + project.open_buffer( + ProjectPath { + worktree_id, + path: Path::new("b.rs").into(), + }, + cx, + ) + }) .await .unwrap(); - let mut events = subscribe(&tree, &mut cx); + let mut events = subscribe(&project, &mut cx); fake_server.start_progress(&progress_token).await; assert_eq!( events.next().await.unwrap(), - Event::DiskBasedDiagnosticsUpdating + Event::DiskBasedDiagnosticsStarted ); fake_server.start_progress(&progress_token).await; @@ -1466,14 +1481,17 @@ mod tests { .await; assert_eq!( events.next().await.unwrap(), - Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs"))) + Event::DiagnosticsUpdated(ProjectPath { + worktree_id, + path: Arc::from(Path::new("a.rs")) + }) ); fake_server.end_progress(&progress_token).await; fake_server.end_progress(&progress_token).await; assert_eq!( events.next().await.unwrap(), - Event::DiskBasedDiagnosticsUpdated + Event::DiskBasedDiagnosticsUpdated { worktree_id } ); let (buffer, _) = tree diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 15b82bc884a0b3575afe60778c11ca7e5e9fa939..af28441f155dbf6fbbc9510812e814a7482f4dac 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -64,15 +64,8 @@ pub enum Worktree { Remote(RemoteWorktree), } -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Event { - DiskBasedDiagnosticsUpdating, - DiskBasedDiagnosticsUpdated, - DiagnosticsUpdated(Arc), -} - impl Entity for Worktree { - type Event = Event; + type Event = (); } impl Worktree { @@ -1139,8 +1132,6 @@ impl LocalWorktree { .insert(PathKey(worktree_path.clone()), summary.clone()); self.diagnostics.insert(worktree_path.clone(), diagnostics); - cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); - if let Some(share) = self.share.as_ref() { cx.foreground() .spawn({ @@ -1535,30 +1526,18 @@ impl RemoteWorktree { pub fn update_diagnostic_summary( &mut self, - envelope: TypedEnvelope, - cx: &mut ModelContext, + path: Arc, + summary: &proto::DiagnosticSummary, ) { - if let Some(summary) = envelope.payload.summary { - let path: Arc = Path::new(&summary.path).into(); - self.diagnostic_summaries.insert( - PathKey(path.clone()), - DiagnosticSummary { - error_count: summary.error_count as usize, - warning_count: summary.warning_count as usize, - info_count: summary.info_count as usize, - hint_count: summary.hint_count as usize, - }, - ); - cx.emit(Event::DiagnosticsUpdated(path)); - } - } - - pub fn disk_based_diagnostics_updating(&self, cx: &mut ModelContext) { - cx.emit(Event::DiskBasedDiagnosticsUpdating); - } - - pub fn disk_based_diagnostics_updated(&self, cx: &mut ModelContext) { - cx.emit(Event::DiskBasedDiagnosticsUpdated); + self.diagnostic_summaries.insert( + PathKey(path.clone()), + DiagnosticSummary { + error_count: summary.error_count as usize, + warning_count: summary.warning_count as usize, + info_count: summary.info_count as usize, + hint_count: summary.hint_count as usize, + }, + ); } pub fn remove_collaborator(&mut self, replica_id: ReplicaId, cx: &mut ModelContext) { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 29480fb4920054ef0800c850909173e0770b2090..cf00149fb1fa0a5e08fc8dd061456a456cf59af5 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1906,8 +1906,14 @@ mod tests { // Cause the language server to start. let _ = cx_a .background() - .spawn(worktree_a.update(&mut cx_a, |worktree, cx| { - worktree.open_buffer("other.rs", cx) + .spawn(project_a.update(&mut cx_a, |project, cx| { + project.open_buffer( + ProjectPath { + worktree_id, + path: Path::new("other.rs").into(), + }, + cx, + ) })) .await .unwrap(); From 634340dd843a3b7e926960679a5857c9049ace4d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 09:51:29 +0100 Subject: [PATCH 10/31] Return a task from `Workspace::save_active_item` This required changing our approach to OS prompts and this commit greatly simplifies that. We now avoid passing a callback and return a simple future instead. This lets callers spawn tasks to handle those futures. --- crates/gpui/src/app.rs | 91 +++++++----------------- crates/gpui/src/platform.rs | 18 ++--- crates/gpui/src/platform/mac/platform.rs | 26 +++---- crates/gpui/src/platform/mac/window.rs | 12 ++-- crates/gpui/src/platform/test.rs | 30 ++++---- crates/workspace/src/workspace.rs | 76 ++++++++++---------- crates/zed/src/zed.rs | 68 +++++++----------- 7 files changed, 134 insertions(+), 187 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 892c1295475f06fafb8df3581ef7819c3a568638..cedba0a8db09a90eaece9608a5833665e2815db9 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -11,7 +11,7 @@ use anyhow::{anyhow, Result}; use keymap::MatchResult; use parking_lot::Mutex; use platform::Event; -use postage::{mpsc, sink::Sink as _, stream::Stream as _}; +use postage::{mpsc, oneshot, sink::Sink as _, stream::Stream as _}; use smol::prelude::*; use std::{ any::{type_name, Any, TypeId}, @@ -498,11 +498,11 @@ impl TestAppContext { .as_any_mut() .downcast_mut::() .unwrap(); - let callback = test_window + let mut done_tx = test_window .last_prompt .take() .expect("prompt was not called"); - (callback)(answer); + let _ = done_tx.try_send(answer); } } @@ -922,61 +922,26 @@ impl MutableAppContext { self.foreground_platform.set_menus(menus); } - fn prompt( + fn prompt( &self, window_id: usize, level: PromptLevel, msg: &str, answers: &[&str], - done_fn: F, - ) where - F: 'static + FnOnce(usize, &mut MutableAppContext), - { - let app = self.weak_self.as_ref().unwrap().upgrade().unwrap(); - let foreground = self.foreground.clone(); + ) -> oneshot::Receiver { let (_, window) = &self.presenters_and_platform_windows[&window_id]; - window.prompt( - level, - msg, - answers, - Box::new(move |answer| { - foreground - .spawn(async move { (done_fn)(answer, &mut *app.borrow_mut()) }) - .detach(); - }), - ); + window.prompt(level, msg, answers) } - pub fn prompt_for_paths(&self, options: PathPromptOptions, done_fn: F) - where - F: 'static + FnOnce(Option>, &mut MutableAppContext), - { - let app = self.weak_self.as_ref().unwrap().upgrade().unwrap(); - let foreground = self.foreground.clone(); - self.foreground_platform.prompt_for_paths( - options, - Box::new(move |paths| { - foreground - .spawn(async move { (done_fn)(paths, &mut *app.borrow_mut()) }) - .detach(); - }), - ); + pub fn prompt_for_paths( + &self, + options: PathPromptOptions, + ) -> oneshot::Receiver>> { + self.foreground_platform.prompt_for_paths(options) } - pub fn prompt_for_new_path(&self, directory: &Path, done_fn: F) - where - F: 'static + FnOnce(Option, &mut MutableAppContext), - { - let app = self.weak_self.as_ref().unwrap().upgrade().unwrap(); - let foreground = self.foreground.clone(); - self.foreground_platform.prompt_for_new_path( - directory, - Box::new(move |path| { - foreground - .spawn(async move { (done_fn)(path, &mut *app.borrow_mut()) }) - .detach(); - }), - ); + pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { + self.foreground_platform.prompt_for_new_path(directory) } pub fn subscribe(&mut self, handle: &H, mut callback: F) -> Subscription @@ -2234,26 +2199,24 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.platform() } - pub fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str], done_fn: F) - where - F: 'static + FnOnce(usize, &mut MutableAppContext), - { - self.app - .prompt(self.window_id, level, msg, answers, done_fn) + pub fn prompt( + &self, + level: PromptLevel, + msg: &str, + answers: &[&str], + ) -> oneshot::Receiver { + self.app.prompt(self.window_id, level, msg, answers) } - pub fn prompt_for_paths(&self, options: PathPromptOptions, done_fn: F) - where - F: 'static + FnOnce(Option>, &mut MutableAppContext), - { - self.app.prompt_for_paths(options, done_fn) + pub fn prompt_for_paths( + &self, + options: PathPromptOptions, + ) -> oneshot::Receiver>> { + self.app.prompt_for_paths(options) } - pub fn prompt_for_new_path(&self, directory: &Path, done_fn: F) - where - F: 'static + FnOnce(Option, &mut MutableAppContext), - { - self.app.prompt_for_new_path(directory, done_fn) + pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { + self.app.prompt_for_new_path(directory) } pub fn debug_elements(&self) -> crate::json::Value { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 0e1bb3fca97f9861a1caa8eaa6d07df62a54f1c1..0d81ab59dec43d1602574d16237adf14c5e88896 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -20,6 +20,7 @@ use crate::{ use anyhow::Result; use async_task::Runnable; pub use event::Event; +use postage::oneshot; use std::{ any::Any, path::{Path, PathBuf}, @@ -70,13 +71,8 @@ pub(crate) trait ForegroundPlatform { fn prompt_for_paths( &self, options: PathPromptOptions, - done_fn: Box>)>, - ); - fn prompt_for_new_path( - &self, - directory: &Path, - done_fn: Box)>, - ); + ) -> oneshot::Receiver>>; + fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver>; } pub trait Dispatcher: Send + Sync { @@ -89,13 +85,7 @@ pub trait Window: WindowContext { fn on_event(&mut self, callback: Box); fn on_resize(&mut self, callback: Box); fn on_close(&mut self, callback: Box); - fn prompt( - &self, - level: PromptLevel, - msg: &str, - answers: &[&str], - done_fn: Box, - ); + fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; } pub trait WindowContext { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 9aec0b5c04ff88888388c23605ac6e9132138843..0b612c978cde8b84cd8377a5bcc836519fbe3ca7 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -33,6 +33,7 @@ use objc::{ runtime::{Class, Object, Sel}, sel, sel_impl, }; +use postage::oneshot; use ptr::null_mut; use std::{ cell::{Cell, RefCell}, @@ -248,15 +249,15 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { fn prompt_for_paths( &self, options: platform::PathPromptOptions, - done_fn: Box>)>, - ) { + ) -> oneshot::Receiver>> { unsafe { let panel = NSOpenPanel::openPanel(nil); panel.setCanChooseDirectories_(options.directories.to_objc()); panel.setCanChooseFiles_(options.files.to_objc()); panel.setAllowsMultipleSelection_(options.multiple.to_objc()); panel.setResolvesAliases_(false.to_objc()); - let done_fn = Cell::new(Some(done_fn)); + let (done_tx, done_rx) = oneshot::channel(); + let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |response: NSModalResponse| { let result = if response == NSModalResponse::NSModalResponseOk { let mut result = Vec::new(); @@ -275,27 +276,25 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { None }; - if let Some(done_fn) = done_fn.take() { - (done_fn)(result); + if let Some(mut done_tx) = done_tx.take() { + let _ = postage::sink::Sink::try_send(&mut done_tx, result); } }); let block = block.copy(); let _: () = msg_send![panel, beginWithCompletionHandler: block]; + done_rx } } - fn prompt_for_new_path( - &self, - directory: &Path, - done_fn: Box)>, - ) { + fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { unsafe { let panel = NSSavePanel::savePanel(nil); let path = ns_string(directory.to_string_lossy().as_ref()); let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc()); panel.setDirectoryURL(url); - let done_fn = Cell::new(Some(done_fn)); + let (done_tx, done_rx) = oneshot::channel(); + let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |response: NSModalResponse| { let result = if response == NSModalResponse::NSModalResponseOk { let url = panel.URL(); @@ -311,12 +310,13 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { None }; - if let Some(done_fn) = done_fn.take() { - (done_fn)(result); + if let Some(mut done_tx) = done_tx.take() { + let _ = postage::sink::Sink::try_send(&mut done_tx, result); } }); let block = block.copy(); let _: () = msg_send![panel, beginWithCompletionHandler: block]; + done_rx } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 518c91a5f541f2598d29200b23146010d1949c81..e383286ae9ceb7b4d4945fb802dd20d0bcc4b138 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -28,6 +28,7 @@ use objc::{ runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES}, sel, sel_impl, }; +use postage::oneshot; use smol::Timer; use std::{ any::Any, @@ -317,8 +318,7 @@ impl platform::Window for Window { level: platform::PromptLevel, msg: &str, answers: &[&str], - done_fn: Box, - ) { + ) -> oneshot::Receiver { unsafe { let alert: id = msg_send![class!(NSAlert), alloc]; let alert: id = msg_send![alert, init]; @@ -333,10 +333,11 @@ impl platform::Window for Window { let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; let _: () = msg_send![button, setTag: ix as NSInteger]; } - let done_fn = Cell::new(Some(done_fn)); + let (done_tx, done_rx) = oneshot::channel(); + let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |answer: NSInteger| { - if let Some(done_fn) = done_fn.take() { - (done_fn)(answer.try_into().unwrap()); + if let Some(mut done_tx) = done_tx.take() { + let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap()); } }); let block = block.copy(); @@ -345,6 +346,7 @@ impl platform::Window for Window { beginSheetModalForWindow: self.0.borrow().native_window completionHandler: block ]; + done_rx } } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 984da925f46e2115bba6a2d27bf02ed509b2df1d..4f0306ff56fbf29deddc6e26bcb49183c61d24cf 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -5,9 +5,10 @@ use crate::{ }; use anyhow::{anyhow, Result}; use parking_lot::Mutex; +use postage::oneshot; use std::{ any::Any, - cell::RefCell, + cell::{Cell, RefCell}, path::{Path, PathBuf}, rc::Rc, sync::Arc, @@ -23,7 +24,7 @@ pub struct Platform { #[derive(Default)] pub struct ForegroundPlatform { - last_prompt_for_new_path_args: RefCell)>)>>, + last_prompt_for_new_path_args: RefCell>)>>, } struct Dispatcher; @@ -35,7 +36,7 @@ pub struct Window { event_handlers: Vec>, resize_handlers: Vec>, close_handlers: Vec>, - pub(crate) last_prompt: RefCell>>, + pub(crate) last_prompt: Cell>>, } impl ForegroundPlatform { @@ -43,11 +44,11 @@ impl ForegroundPlatform { &self, result: impl FnOnce(PathBuf) -> Option, ) { - let (dir_path, callback) = self + let (dir_path, mut done_tx) = self .last_prompt_for_new_path_args .take() .expect("prompt_for_new_path was not called"); - callback(result(dir_path)); + let _ = postage::sink::Sink::try_send(&mut done_tx, result(dir_path)); } pub(crate) fn did_prompt_for_new_path(&self) -> bool { @@ -77,12 +78,15 @@ impl super::ForegroundPlatform for ForegroundPlatform { fn prompt_for_paths( &self, _: super::PathPromptOptions, - _: Box>)>, - ) { + ) -> oneshot::Receiver>> { + let (_done_tx, done_rx) = oneshot::channel(); + done_rx } - fn prompt_for_new_path(&self, path: &Path, f: Box)>) { - *self.last_prompt_for_new_path_args.borrow_mut() = Some((path.to_path_buf(), f)); + fn prompt_for_new_path(&self, path: &Path) -> oneshot::Receiver> { + let (done_tx, done_rx) = oneshot::channel(); + *self.last_prompt_for_new_path_args.borrow_mut() = Some((path.to_path_buf(), done_tx)); + done_rx } } @@ -170,7 +174,7 @@ impl Window { close_handlers: Vec::new(), scale_factor: 1.0, current_scene: None, - last_prompt: RefCell::new(None), + last_prompt: Default::default(), } } } @@ -220,8 +224,10 @@ impl super::Window for Window { self.close_handlers.push(callback); } - fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str], f: Box) { - self.last_prompt.replace(Some(f)); + fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver { + let (done_tx, done_rx) = oneshot::channel(); + self.last_prompt.replace(Some(done_tx)); + done_rx } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 64eeb3ee72a71b0a1f715cb3263ebc8daea1de73..8fc329e4b50c1f4a8a57fbc90229d3ab6fec4642 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -66,7 +66,11 @@ pub fn init(cx: &mut MutableAppContext) { }); cx.add_action(Workspace::toggle_share); - cx.add_action(Workspace::save_active_item); + cx.add_action( + |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext| { + workspace.save_active_item(cx).detach_and_log_err(cx); + }, + ); cx.add_action(Workspace::debug_elements); cx.add_action(Workspace::toggle_sidebar_item); cx.add_action(Workspace::toggle_sidebar_item_focus); @@ -224,7 +228,7 @@ pub trait ItemViewHandle { project: ModelHandle, abs_path: PathBuf, cx: &mut MutableAppContext, - ) -> Task>; + ) -> Task>; } pub trait WeakItemViewHandle { @@ -804,36 +808,29 @@ impl Workspace { .and_then(|entry| self.project.read(cx).path_for_entry(entry, cx)) } - pub fn save_active_item(&mut self, _: &Save, cx: &mut ViewContext) { + pub fn save_active_item(&mut self, cx: &mut ViewContext) -> Task> { if let Some(item) = self.active_item(cx) { - let handle = cx.handle(); if item.can_save(cx) { if item.has_conflict(cx.as_ref()) { const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?"; - cx.prompt( + let mut answer = cx.prompt( PromptLevel::Warning, CONFLICT_MESSAGE, &["Overwrite", "Cancel"], - move |answer, cx| { - if answer == 0 { - cx.spawn(|mut cx| async move { - if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await - { - error!("failed to save item: {:?}, ", error); - } - }) - .detach(); - } - }, ); - } else { cx.spawn(|_, mut cx| async move { - if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await { - error!("failed to save item: {:?}, ", error); + let answer = answer.recv().await; + if answer == Some(0) { + cx.update(|cx| item.save(cx))?.await?; } + Ok(()) + }) + } else { + cx.spawn(|_, mut cx| async move { + cx.update(|cx| item.save(cx))?.await?; + Ok(()) }) - .detach(); } } else if item.can_save_as(cx) { let worktree = self.worktrees(cx).first(); @@ -841,13 +838,19 @@ impl Workspace { .and_then(|w| w.read(cx).as_local()) .map_or(Path::new(""), |w| w.abs_path()) .to_path_buf(); - cx.prompt_for_new_path(&start_abs_path, move |abs_path, cx| { - if let Some(abs_path) = abs_path { - let project = handle.read(cx).project().clone(); - cx.update(|cx| item.save_as(project, abs_path, cx).detach_and_log_err(cx)); + let mut abs_path = cx.prompt_for_new_path(&start_abs_path); + cx.spawn(|this, mut cx| async move { + if let Some(abs_path) = abs_path.recv().await.flatten() { + let project = this.read_with(&cx, |this, _| this.project().clone()); + cx.update(|cx| item.save_as(project, abs_path, cx)).await?; } - }); + Ok(()) + }) + } else { + Task::ready(Ok(())) } + } else { + Task::ready(Ok(())) } } @@ -1397,18 +1400,17 @@ impl std::fmt::Debug for OpenParams { fn open(action: &Open, cx: &mut MutableAppContext) { let app_state = action.0.clone(); - cx.prompt_for_paths( - PathPromptOptions { - files: true, - directories: true, - multiple: true, - }, - move |paths, cx| { - if let Some(paths) = paths { - cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state })); - } - }, - ); + let mut paths = cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }); + cx.spawn(|mut cx| async move { + if let Some(paths) = paths.recv().await.flatten() { + cx.update(|cx| cx.dispatch_global_action(OpenPaths(OpenParams { paths, app_state }))); + } + }) + .detach(); } pub fn open_paths( diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ac24bd9aea6c85bfaa30ec6caa4343a1e7740ecb..72fd6f28d35f4cb86c509ad8ba316114098216e1 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -214,18 +214,13 @@ mod tests { assert!(editor.text(cx).is_empty()); }); - workspace.update(&mut cx, |workspace, cx| { - workspace.save_active_item(&workspace::Save, cx) - }); - + let save_task = workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(cx)); app_state.fs.as_fake().insert_dir("/root").await.unwrap(); cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name"))); - - editor - .condition(&cx, |editor, cx| editor.title(cx) == "the-new-name") - .await; - editor.update(&mut cx, |editor, cx| { + save_task.await.unwrap(); + editor.read_with(&cx, |editor, cx| { assert!(!editor.is_dirty(cx)); + assert_eq!(editor.title(cx), "the-new-name"); }); } @@ -472,12 +467,13 @@ mod tests { .await; cx.read(|cx| assert!(editor.is_dirty(cx))); - cx.update(|cx| workspace.update(cx, |w, cx| w.save_active_item(&workspace::Save, cx))); + let save_task = workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(cx)); cx.simulate_prompt_answer(window_id, 0); - editor - .condition(&cx, |editor, cx| !editor.is_dirty(cx)) - .await; - cx.read(|cx| assert!(!editor.has_conflict(cx))); + save_task.await.unwrap(); + editor.read_with(&cx, |editor, cx| { + assert!(!editor.is_dirty(cx)); + assert!(!editor.has_conflict(cx)); + }); } #[gpui::test] @@ -525,9 +521,7 @@ mod tests { }); // Save the buffer. This prompts for a filename. - workspace.update(&mut cx, |workspace, cx| { - workspace.save_active_item(&workspace::Save, cx) - }); + let save_task = workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(cx)); cx.simulate_new_path_selection(|parent_dir| { assert_eq!(parent_dir, Path::new("/root")); Some(parent_dir.join("the-new-name.rs")) @@ -537,17 +531,13 @@ mod tests { assert_eq!(editor.title(cx), "untitled"); }); - // When the save completes, the buffer's title is updated. - editor - .condition(&cx, |editor, cx| !editor.is_dirty(cx)) - .await; - cx.read(|cx| { + // When the save completes, the buffer's title is updated and the language is assigned based + // on the path. + save_task.await.unwrap(); + editor.read_with(&cx, |editor, cx| { assert!(!editor.is_dirty(cx)); assert_eq!(editor.title(cx), "the-new-name.rs"); - }); - // The language is assigned based on the path - editor.read_with(&cx, |editor, cx| { - assert_eq!(editor.language(cx).unwrap().name(), "Rust") + assert_eq!(editor.language(cx).unwrap().name(), "Rust"); }); // Edit the file and save it again. This time, there is no filename prompt. @@ -555,14 +545,13 @@ mod tests { editor.handle_input(&editor::Input(" there".into()), cx); assert_eq!(editor.is_dirty(cx.as_ref()), true); }); - workspace.update(&mut cx, |workspace, cx| { - workspace.save_active_item(&workspace::Save, cx) - }); + let save_task = workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(cx)); + save_task.await.unwrap(); assert!(!cx.did_prompt_for_new_path()); - editor - .condition(&cx, |editor, cx| !editor.is_dirty(cx)) - .await; - cx.read(|cx| assert_eq!(editor.title(cx), "the-new-name.rs")); + editor.read_with(&cx, |editor, cx| { + assert!(!editor.is_dirty(cx)); + assert_eq!(editor.title(cx), "the-new-name.rs") + }); // Open the same newly-created file in another pane item. The new editor should reuse // the same buffer. @@ -624,17 +613,12 @@ mod tests { }); // Save the buffer. This prompts for a filename. - workspace.update(&mut cx, |workspace, cx| { - workspace.save_active_item(&workspace::Save, cx) - }); + let save_task = workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(cx)); cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs"))); - - editor - .condition(&cx, |editor, cx| !editor.is_dirty(cx)) - .await; - - // The language is assigned based on the path + save_task.await.unwrap(); + // The buffer is not dirty anymore and the language is assigned based on the path. editor.read_with(&cx, |editor, cx| { + assert!(!editor.is_dirty(cx)); assert_eq!(editor.language(cx).unwrap().name(), "Rust") }); } From 71082d4cdc3697005854b1cb0d6be8d1e3731e65 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 09:58:24 +0100 Subject: [PATCH 11/31] Return a `Task>` in `{ItemView,Buffer,MultiBuffer}::save` --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/items.rs | 10 ++++------ crates/editor/src/multi_buffer.rs | 8 ++++---- crates/language/src/buffer.rs | 15 ++++++++------- crates/project/src/worktree.rs | 6 +++--- crates/server/src/rpc.rs | 3 +-- crates/workspace/src/workspace.rs | 15 ++++++--------- 7 files changed, 27 insertions(+), 32 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 2f253bff28922d932d4aaa9d20d921c550976889..5c0fcc38bb800b14e3d349099c669630d5ff6a7c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -560,7 +560,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { true } - fn save(&mut self, cx: &mut ViewContext) -> Result>> { + fn save(&mut self, cx: &mut ViewContext) -> Task> { self.excerpts.update(cx, |excerpts, cx| excerpts.save(cx)) } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 24dbebb6c7b9e227c69e8c37806c6485a1229050..e534f3b8fa18d42be7bdd26e54bc036532c53dbf 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -160,20 +160,18 @@ impl ItemView for Editor { self.project_entry(cx).is_some() } - fn save(&mut self, cx: &mut ViewContext) -> Result>> { + fn save(&mut self, cx: &mut ViewContext) -> Task> { let buffer = self.buffer().clone(); - Ok(cx.spawn(|editor, mut cx| async move { + cx.spawn(|editor, mut cx| async move { buffer .update(&mut cx, |buffer, cx| buffer.format(cx).log_err()) .await; editor.update(&mut cx, |editor, cx| { editor.request_autoscroll(Autoscroll::Fit, cx) }); - buffer - .update(&mut cx, |buffer, cx| buffer.save(cx))? - .await?; + buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await?; Ok(()) - })) + }) } fn can_save_as(&self, _: &AppContext) -> bool { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7ae7b5686731d8da487307cf8157431df9f28255..04190ed39aad96e32bb354e8ec966929d06ba00d 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -812,18 +812,18 @@ impl MultiBuffer { }) } - pub fn save(&mut self, cx: &mut ModelContext) -> Result>> { + pub fn save(&mut self, cx: &mut ModelContext) -> Task> { let mut save_tasks = Vec::new(); for BufferState { buffer, .. } in self.buffers.borrow().values() { - save_tasks.push(buffer.update(cx, |buffer, cx| buffer.save(cx))?); + save_tasks.push(buffer.update(cx, |buffer, cx| buffer.save(cx))); } - Ok(cx.spawn(|_, _| async move { + cx.spawn(|_, _| async move { for save in save_tasks { save.await?; } Ok(()) - })) + }) } pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc> { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d0410b47013b872c166a9d30b1541d38f20ea6c7..042c0148449240261672d08ba24169a23bb570ac 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -510,21 +510,22 @@ impl Buffer { pub fn save( &mut self, cx: &mut ModelContext, - ) -> Result>> { - let file = self - .file - .as_ref() - .ok_or_else(|| anyhow!("buffer has no file"))?; + ) -> Task> { + let file = if let Some(file) = self.file.as_ref() { + file + } else { + return Task::ready(Err(anyhow!("buffer has no file"))); + }; let text = self.as_rope().clone(); let version = self.version(); let save = file.save(self.remote_id(), text, version, cx.as_mut()); - Ok(cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { let (version, mtime) = save.await?; this.update(&mut cx, |this, cx| { this.did_save(version.clone(), mtime, None, cx); }); Ok((version, mtime)) - })) + }) } pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index af28441f155dbf6fbbc9510812e814a7482f4dac..9c322f267e4d679284ca9e4fca7e3c0ecffee011 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -455,7 +455,7 @@ impl Worktree { let worktree_id = envelope.payload.worktree_id; let buffer_id = envelope.payload.buffer_id; let save = cx.spawn(|_, mut cx| async move { - buffer.update(&mut cx, |buffer, cx| buffer.save(cx))?.await + buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await }); cx.background() @@ -3094,7 +3094,7 @@ mod tests { .unwrap(); let save = buffer.update(&mut cx, |buffer, cx| { buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx); - buffer.save(cx).unwrap() + buffer.save(cx) }); save.await.unwrap(); @@ -3132,7 +3132,7 @@ mod tests { .unwrap(); let save = buffer.update(&mut cx, |buffer, cx| { buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx); - buffer.save(cx).unwrap() + buffer.save(cx) }); save.await.unwrap(); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index cf00149fb1fa0a5e08fc8dd061456a456cf59af5..adfbeacd13eaff50f39e4ad04f890b10b7762622 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1474,7 +1474,7 @@ mod tests { .await; // Edit the buffer as the host and concurrently save as guest B. - let save_b = buffer_b.update(&mut cx_b, |buf, cx| buf.save(cx).unwrap()); + let save_b = buffer_b.update(&mut cx_b, |buf, cx| buf.save(cx)); buffer_a.update(&mut cx_a, |buf, cx| buf.edit([0..0], "hi-a, ", cx)); save_b.await.unwrap(); assert_eq!( @@ -1591,7 +1591,6 @@ mod tests { buffer_b .update(&mut cx_b, |buf, cx| buf.save(cx)) - .unwrap() .await .unwrap(); worktree_b diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8fc329e4b50c1f4a8a57fbc90229d3ab6fec4642..242fbcf56060d16c6997e1790f18646007b602ac 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -168,14 +168,14 @@ pub trait ItemView: View { false } fn can_save(&self, cx: &AppContext) -> bool; - fn save(&mut self, cx: &mut ViewContext) -> Result>>; + fn save(&mut self, cx: &mut ViewContext) -> Task>; fn can_save_as(&self, cx: &AppContext) -> bool; fn save_as( &mut self, project: ModelHandle, abs_path: PathBuf, cx: &mut ViewContext, - ) -> Task>; + ) -> Task>; fn should_activate_item_on_event(_: &Self::Event) -> bool { false } @@ -222,7 +222,7 @@ pub trait ItemViewHandle { fn has_conflict(&self, cx: &AppContext) -> bool; fn can_save(&self, cx: &AppContext) -> bool; fn can_save_as(&self, cx: &AppContext) -> bool; - fn save(&self, cx: &mut MutableAppContext) -> Result>>; + fn save(&self, cx: &mut MutableAppContext) -> Task>; fn save_as( &self, project: ModelHandle, @@ -377,7 +377,7 @@ impl ItemViewHandle for ViewHandle { self.update(cx, |this, cx| this.navigate(data, cx)); } - fn save(&self, cx: &mut MutableAppContext) -> Result>> { + fn save(&self, cx: &mut MutableAppContext) -> Task> { self.update(cx, |item, cx| item.save(cx)) } @@ -822,15 +822,12 @@ impl Workspace { cx.spawn(|_, mut cx| async move { let answer = answer.recv().await; if answer == Some(0) { - cx.update(|cx| item.save(cx))?.await?; + cx.update(|cx| item.save(cx)).await?; } Ok(()) }) } else { - cx.spawn(|_, mut cx| async move { - cx.update(|cx| item.save(cx))?.await?; - Ok(()) - }) + item.save(cx) } } else if item.can_save_as(cx) { let worktree = self.worktrees(cx).first(); From 6b1f989c2b9deca2f993b077403f2dede97ca58a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 10:13:27 +0100 Subject: [PATCH 12/31] Omit worktree id when emitting `Event::DiskBasedDiagnosticsUpdated` Sometimes we will have more than one worktree associated with the same language server and in that case it's unclear which worktree id we should report an event for. --- crates/diagnostics/src/diagnostics.rs | 20 ++++----- crates/diagnostics/src/items.rs | 2 +- crates/project/src/project.rs | 59 ++++++++++----------------- crates/rpc/proto/zed.proto | 2 - 4 files changed, 30 insertions(+), 53 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 5c0fcc38bb800b14e3d349099c669630d5ff6a7c..3971fabf64abe1a9f29fcb37a8e4ada78b896a91 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -14,7 +14,7 @@ use gpui::{ }; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; -use project::{Project, ProjectPath, WorktreeId}; +use project::{Project, ProjectPath}; use std::{cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; use util::TryFutureExt; use workspace::{NavHistory, Workspace}; @@ -49,7 +49,7 @@ struct ProjectDiagnosticsEditor { editor: ViewHandle, excerpts: ModelHandle, path_states: Vec, - paths_to_update: HashMap>, + paths_to_update: BTreeSet, build_settings: BuildSettings, settings: watch::Receiver, } @@ -119,16 +119,12 @@ impl ProjectDiagnosticsEditor { ) -> Self { let project = model.read(cx).project.clone(); cx.subscribe(&project, |this, _, event, cx| match event { - project::Event::DiskBasedDiagnosticsUpdated { worktree_id } => { - if let Some(paths) = this.paths_to_update.remove(&worktree_id) { - this.update_excerpts(paths, cx); - } + project::Event::DiskBasedDiagnosticsFinished => { + let paths = mem::take(&mut this.paths_to_update); + this.update_excerpts(paths, cx); } project::Event::DiagnosticsUpdated(path) => { - this.paths_to_update - .entry(path.worktree_id) - .or_default() - .insert(path.clone()); + this.paths_to_update.insert(path.clone()); } _ => {} }) @@ -909,7 +905,7 @@ mod tests { worktree_id, path: Arc::from("/test/consts.rs".as_ref()), })); - cx.emit(project::Event::DiskBasedDiagnosticsUpdated { worktree_id }); + cx.emit(project::Event::DiskBasedDiagnosticsFinished); }); view.next_notification(&cx).await; @@ -1029,7 +1025,7 @@ mod tests { worktree_id, path: Arc::from("/test/consts.rs".as_ref()), })); - cx.emit(project::Event::DiskBasedDiagnosticsUpdated { worktree_id }); + cx.emit(project::Event::DiskBasedDiagnosticsFinished); }); view.next_notification(&cx).await; diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 072738fa77417542fbc7cdfa1036f8d55418c6e8..3390f74a849c41cbc1268e4c05e2da6d99a18113 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -19,7 +19,7 @@ impl DiagnosticSummary { cx: &mut ViewContext, ) -> Self { cx.subscribe(project, |this, project, event, cx| match event { - project::Event::DiskBasedDiagnosticsUpdated { .. } => { + project::Event::DiskBasedDiagnosticsUpdated => { this.summary = project.read(cx).diagnostic_summary(cx); cx.notify(); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4b16494d9dfa3e204ad3d6c6c5c204e2922eb3f3..5eec2f9b9e757b36626b36763857df68f94224f2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -64,7 +64,7 @@ pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(WorktreeId), DiskBasedDiagnosticsStarted, - DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId }, + DiskBasedDiagnosticsUpdated, DiskBasedDiagnosticsFinished, DiagnosticsUpdated(ProjectPath), } @@ -527,14 +527,10 @@ impl Project { .entry((worktree_id, language.name().to_string())) { hash_map::Entry::Occupied(e) => Some(e.get().clone()), - hash_map::Entry::Vacant(e) => Self::start_language_server( - self.client.clone(), - language, - worktree_id, - &worktree_abs_path, - cx, - ) - .map(|server| e.insert(server).clone()), + hash_map::Entry::Vacant(e) => { + Self::start_language_server(self.client.clone(), language, &worktree_abs_path, cx) + .map(|server| e.insert(server).clone()) + } }; buffer.update(cx, |buffer, cx| { @@ -547,7 +543,6 @@ impl Project { fn start_language_server( rpc: Arc, language: Arc, - worktree_id: WorktreeId, worktree_path: &Path, cx: &mut ModelContext, ) -> Option> { @@ -626,12 +621,9 @@ impl Project { match message { LspEvent::DiagnosticsStart => { let send = this.update(&mut cx, |this, cx| { - this.disk_based_diagnostics_started(worktree_id, cx); + this.disk_based_diagnostics_started(cx); this.remote_id().map(|project_id| { - rpc.send(proto::DiskBasedDiagnosticsUpdating { - project_id, - worktree_id: worktree_id.to_proto(), - }) + rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id }) }) }); if let Some(send) = send { @@ -646,12 +638,9 @@ impl Project { } LspEvent::DiagnosticsFinish => { let send = this.update(&mut cx, |this, cx| { - this.disk_based_diagnostics_finished(worktree_id, cx); + this.disk_based_diagnostics_finished(cx); this.remote_id().map(|project_id| { - rpc.send(proto::DiskBasedDiagnosticsUpdated { - project_id, - worktree_id: worktree_id.to_proto(), - }) + rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id }) }) }); if let Some(send) = send { @@ -831,19 +820,15 @@ impl Project { }) } - fn disk_based_diagnostics_started(&mut self, _: WorktreeId, cx: &mut ModelContext) { + fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext) { self.language_servers_with_diagnostics_running += 1; if self.language_servers_with_diagnostics_running == 1 { cx.emit(Event::DiskBasedDiagnosticsStarted); } } - fn disk_based_diagnostics_finished( - &mut self, - worktree_id: WorktreeId, - cx: &mut ModelContext, - ) { - cx.emit(Event::DiskBasedDiagnosticsUpdated { worktree_id }); + fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext) { + cx.emit(Event::DiskBasedDiagnosticsUpdated); self.language_servers_with_diagnostics_running -= 1; if self.language_servers_with_diagnostics_running == 0 { cx.emit(Event::DiskBasedDiagnosticsFinished); @@ -1010,27 +995,21 @@ impl Project { fn handle_disk_based_diagnostics_updating( &mut self, - envelope: TypedEnvelope, + _: TypedEnvelope, _: Arc, cx: &mut ModelContext, ) -> Result<()> { - self.disk_based_diagnostics_started( - WorktreeId::from_proto(envelope.payload.worktree_id), - cx, - ); + self.disk_based_diagnostics_started(cx); Ok(()) } fn handle_disk_based_diagnostics_updated( &mut self, - envelope: TypedEnvelope, + _: TypedEnvelope, _: Arc, cx: &mut ModelContext, ) -> Result<()> { - self.disk_based_diagnostics_finished( - WorktreeId::from_proto(envelope.payload.worktree_id), - cx, - ); + self.disk_based_diagnostics_finished(cx); Ok(()) } @@ -1491,7 +1470,11 @@ mod tests { fake_server.end_progress(&progress_token).await; assert_eq!( events.next().await.unwrap(), - Event::DiskBasedDiagnosticsUpdated { worktree_id } + Event::DiskBasedDiagnosticsUpdated + ); + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsFinished ); let (buffer, _) = tree diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 47774bf360f92dcac041b561540c40d20483245b..973d459310498ccc88f842644c0272a2fec92103 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -191,12 +191,10 @@ message DiagnosticSummary { message DiskBasedDiagnosticsUpdating { uint64 project_id = 1; - uint64 worktree_id = 2; } message DiskBasedDiagnosticsUpdated { uint64 project_id = 1; - uint64 worktree_id = 2; } message GetChannels {} From 11a83d01c26390c197f21e44b24dd2af8e67bcd3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 12:10:01 +0100 Subject: [PATCH 13/31] Advertise link capability in LSP --- crates/lsp/src/lsp.rs | 80 ++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index ad4355e90293fcd6811768514213cadb63c8fa6c..abadb6922f0e4169fe1d557b002929999571b654 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -205,8 +205,7 @@ impl LanguageServer { output_done_rx: Mutex::new(Some(output_done_rx)), }); - let root_uri = - lsp_types::Url::from_file_path(root_path).map_err(|_| anyhow!("invalid root path"))?; + let root_uri = Url::from_file_path(root_path).map_err(|_| anyhow!("invalid root path"))?; executor .spawn({ let this = this.clone(); @@ -220,18 +219,25 @@ impl LanguageServer { Ok(this) } - async fn init(self: Arc, root_uri: lsp_types::Url) -> Result<()> { + async fn init(self: Arc, root_uri: Url) -> Result<()> { #[allow(deprecated)] - let params = lsp_types::InitializeParams { + let params = InitializeParams { process_id: Default::default(), root_path: Default::default(), root_uri: Some(root_uri), initialization_options: Default::default(), - capabilities: lsp_types::ClientCapabilities { + capabilities: ClientCapabilities { + text_document: Some(TextDocumentClientCapabilities { + definition: Some(GotoCapability { + link_support: Some(true), + ..Default::default() + }), + ..Default::default() + }), experimental: Some(json!({ "serverStatusNotification": true, })), - window: Some(lsp_types::WindowClientCapabilities { + window: Some(WindowClientCapabilities { work_done_progress: Some(true), ..Default::default() }), @@ -244,16 +250,16 @@ impl LanguageServer { }; let this = self.clone(); - let request = Self::request_internal::( + let request = Self::request_internal::( &this.next_id, &this.response_handlers, this.outbound_tx.read().as_ref(), params, ); request.await?; - Self::notify_internal::( + Self::notify_internal::( this.outbound_tx.read().as_ref(), - lsp_types::InitializedParams {}, + InitializedParams {}, )?; Ok(()) } @@ -265,14 +271,14 @@ impl LanguageServer { let next_id = AtomicUsize::new(self.next_id.load(SeqCst)); let mut output_done = self.output_done_rx.lock().take().unwrap(); Some(async move { - Self::request_internal::( + Self::request_internal::( &next_id, &response_handlers, outbound_tx.as_ref(), (), ) .await?; - Self::notify_internal::(outbound_tx.as_ref(), ())?; + Self::notify_internal::(outbound_tx.as_ref(), ())?; drop(outbound_tx); output_done.recv().await; drop(tasks); @@ -285,7 +291,7 @@ impl LanguageServer { pub fn on_notification(&self, mut f: F) -> Subscription where - T: lsp_types::notification::Notification, + T: notification::Notification, F: 'static + Send + Sync + FnMut(T::Params), { let prev_handler = self.notification_handlers.write().insert( @@ -309,8 +315,8 @@ impl LanguageServer { } } - pub fn request( - self: Arc, + pub fn request( + self: &Arc, params: T::Params, ) -> impl Future> where @@ -329,7 +335,7 @@ impl LanguageServer { } } - fn request_internal( + fn request_internal( next_id: &AtomicUsize, response_handlers: &Mutex>, outbound_tx: Option<&channel::Sender>>, @@ -376,7 +382,7 @@ impl LanguageServer { } } - pub fn notify( + pub fn notify( self: &Arc, params: T::Params, ) -> impl Future> { @@ -388,7 +394,7 @@ impl LanguageServer { } } - fn notify_internal( + fn notify_internal( outbound_tx: Option<&channel::Sender>>, params: T::Params, ) -> Result<()> { @@ -601,8 +607,7 @@ mod tests { "lib.rs": &lib_source } })); - let lib_file_uri = - lsp_types::Url::from_file_path(root_dir.path().join("src/lib.rs")).unwrap(); + let lib_file_uri = Url::from_file_path(root_dir.path().join("src/lib.rs")).unwrap(); let server = cx.read(|cx| { LanguageServer::new( @@ -615,24 +620,22 @@ mod tests { server.next_idle_notification().await; server - .notify::( - lsp_types::DidOpenTextDocumentParams { - text_document: lsp_types::TextDocumentItem::new( - lib_file_uri.clone(), - "rust".to_string(), - 0, - lib_source, - ), - }, - ) + .notify::(DidOpenTextDocumentParams { + text_document: TextDocumentItem::new( + lib_file_uri.clone(), + "rust".to_string(), + 0, + lib_source, + ), + }) .await .unwrap(); let hover = server - .request::(lsp_types::HoverParams { - text_document_position_params: lsp_types::TextDocumentPositionParams { - text_document: lsp_types::TextDocumentIdentifier::new(lib_file_uri), - position: lsp_types::Position::new(1, 21), + .request::(HoverParams { + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier::new(lib_file_uri), + position: Position::new(1, 21), }, work_done_progress_params: Default::default(), }) @@ -641,8 +644,8 @@ mod tests { .unwrap(); assert_eq!( hover.contents, - lsp_types::HoverContents::Markup(lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, + HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, value: "&str".to_string() }) ); @@ -705,10 +708,9 @@ mod tests { ); drop(server); - let (shutdown_request, _) = fake.receive_request::().await; + let (shutdown_request, _) = fake.receive_request::().await; fake.respond(shutdown_request, ()).await; - fake.receive_notification::() - .await; + fake.receive_notification::().await; } impl LanguageServer { @@ -726,7 +728,7 @@ mod tests { pub enum ServerStatusNotification {} - impl lsp_types::notification::Notification for ServerStatusNotification { + impl notification::Notification for ServerStatusNotification { type Params = ServerStatusParams; const METHOD: &'static str = "experimental/serverStatus"; } From cbbf7391e835e7a390a2c17b54643cb1c3d8a82f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 12:11:41 +0100 Subject: [PATCH 14/31] Start on `Project::definition` that only works locally (for now) --- crates/project/src/project.rs | 184 ++++++++++++++++++++++++++++-- crates/project/src/worktree.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- 3 files changed, 176 insertions(+), 12 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5eec2f9b9e757b36626b36763857df68f94224f2..3de0d564df5072ca421cd396541de5bfa65289a2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -11,11 +11,14 @@ use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry}; +use language::{ + Bias, Buffer, DiagnosticEntry, File as _, Language, LanguageRegistry, ToOffset, ToPointUtf16, +}; use lsp::{DiagnosticSeverity, LanguageServer}; use postage::{prelude::Stream, watch}; use smol::block_on; use std::{ + ops::Range, path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, }; @@ -83,6 +86,13 @@ pub struct DiagnosticSummary { pub hint_count: usize, } +#[derive(Debug)] +pub struct Definition { + pub source_range: Option>, + pub target_buffer: ModelHandle, + pub target_range: Range, +} + impl DiagnosticSummary { fn new<'a, T: 'a>(diagnostics: impl IntoIterator>) -> Self { let mut this = Self { @@ -487,7 +497,7 @@ impl Project { abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let worktree_task = self.worktree_for_abs_path(&abs_path, cx); + let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, cx); cx.spawn(|this, mut cx| async move { let (worktree, path) = worktree_task.await?; worktree @@ -691,26 +701,180 @@ impl Project { Ok(()) } - pub fn worktree_for_abs_path( + pub fn definition( + &self, + source_buffer_handle: &ModelHandle, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + let source_buffer_handle = source_buffer_handle.clone(); + let buffer = source_buffer_handle.read(cx); + let worktree; + let buffer_abs_path; + if let Some(file) = File::from_dyn(buffer.file()) { + worktree = file.worktree.clone(); + buffer_abs_path = file.abs_path(); + } else { + return Task::ready(Err(anyhow!("buffer does not belong to any worktree"))); + }; + + if worktree.read(cx).as_local().is_some() { + let point = buffer.offset_to_point_utf16(position.to_offset(buffer)); + let buffer_abs_path = buffer_abs_path.unwrap(); + let lang_name; + let lang_server; + if let Some(lang) = buffer.language() { + lang_name = lang.name().to_string(); + if let Some(server) = self + .language_servers + .get(&(worktree.read(cx).id(), lang_name.clone())) + { + lang_server = server.clone(); + } else { + return Task::ready(Err(anyhow!("buffer does not have a language server"))); + }; + } else { + return Task::ready(Err(anyhow!("buffer does not have a language"))); + } + + cx.spawn(|this, mut cx| async move { + let response = lang_server + .request::(lsp::GotoDefinitionParams { + text_document_position_params: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path(&buffer_abs_path).unwrap(), + ), + position: lsp::Position::new(point.row, point.column), + }, + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }) + .await?; + + let mut definitions = Vec::new(); + if let Some(response) = response { + let mut unresolved_locations = Vec::new(); + match response { + lsp::GotoDefinitionResponse::Scalar(loc) => { + unresolved_locations.push((None, loc.uri, loc.range)); + } + lsp::GotoDefinitionResponse::Array(locs) => { + unresolved_locations + .extend(locs.into_iter().map(|l| (None, l.uri, l.range))); + } + lsp::GotoDefinitionResponse::Link(links) => { + unresolved_locations.extend(links.into_iter().map(|l| { + ( + l.origin_selection_range, + l.target_uri, + l.target_selection_range, + ) + })); + } + } + + for (source_range, target_uri, target_range) in unresolved_locations { + let abs_path = target_uri + .to_file_path() + .map_err(|_| anyhow!("invalid target path"))?; + + let (worktree, relative_path) = if let Some(result) = this + .read_with(&cx, |this, cx| { + this.find_worktree_for_abs_path(&abs_path, cx) + }) { + result + } else { + let (worktree, relative_path) = this + .update(&mut cx, |this, cx| { + this.create_worktree_for_abs_path(&abs_path, cx) + }) + .await?; + this.update(&mut cx, |this, cx| { + this.language_servers.insert( + (worktree.read(cx).id(), lang_name.clone()), + lang_server.clone(), + ); + }); + (worktree, relative_path) + }; + + let project_path = ProjectPath { + worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()), + path: relative_path.into(), + }; + let target_buffer_handle = this + .update(&mut cx, |this, cx| this.open_buffer(project_path, cx)) + .await?; + cx.read(|cx| { + let source_buffer = source_buffer_handle.read(cx); + let target_buffer = target_buffer_handle.read(cx); + let source_range = source_range.map(|range| { + let start = source_buffer + .clip_point_utf16(range.start.to_point_utf16(), Bias::Left); + let end = source_buffer + .clip_point_utf16(range.end.to_point_utf16(), Bias::Left); + source_buffer.anchor_after(start)..source_buffer.anchor_before(end) + }); + let target_start = target_buffer + .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left); + let target_end = target_buffer + .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left); + definitions.push(Definition { + source_range, + target_buffer: target_buffer_handle, + target_range: target_buffer.anchor_after(target_start) + ..target_buffer.anchor_before(target_end), + }); + }); + } + } + + Ok(definitions) + }) + } else { + todo!() + } + } + + pub fn find_or_create_worktree_for_abs_path( + &self, + abs_path: &Path, + cx: &mut ModelContext, + ) -> Task, PathBuf)>> { + if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) { + Task::ready(Ok((tree.clone(), relative_path.into()))) + } else { + self.create_worktree_for_abs_path(abs_path, cx) + } + } + + fn create_worktree_for_abs_path( &self, abs_path: &Path, cx: &mut ModelContext, ) -> Task, PathBuf)>> { + let worktree = self.add_local_worktree(abs_path, cx); + cx.background().spawn(async move { + let worktree = worktree.await?; + Ok((worktree, PathBuf::new())) + }) + } + + fn find_worktree_for_abs_path( + &self, + abs_path: &Path, + cx: &AppContext, + ) -> Option<(ModelHandle, PathBuf)> { for tree in &self.worktrees { if let Some(relative_path) = tree .read(cx) .as_local() .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok()) { - return Task::ready(Ok((tree.clone(), relative_path.into()))); + return Some((tree.clone(), relative_path.into())); } } - - let worktree = self.add_local_worktree(abs_path, cx); - cx.background().spawn(async move { - let worktree = worktree.await?; - Ok((worktree, PathBuf::new())) - }) + None } pub fn is_shared(&self) -> bool { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9c322f267e4d679284ca9e4fca7e3c0ecffee011..d734a62bc3fb5d09fd3589c6cf3a28597bb70521 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1947,7 +1947,7 @@ impl fmt::Debug for Snapshot { #[derive(Clone, PartialEq)] pub struct File { entry_id: Option, - worktree: ModelHandle, + pub worktree: ModelHandle, worktree_path: Arc, pub path: Arc, pub mtime: SystemTime, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 242fbcf56060d16c6997e1790f18646007b602ac..e865c7f2b12f407658b4793494fec59ac847b7d2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -684,7 +684,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let entry = self.project().update(cx, |project, cx| { - project.worktree_for_abs_path(abs_path, cx) + project.find_or_create_worktree_for_abs_path(abs_path, cx) }); cx.spawn(|_, cx| async move { let (worktree, path) = entry.await?; From 66734e11aff78252dffe7e9092a33d31206d31a0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 12:26:04 +0100 Subject: [PATCH 15/31] WIP: Start on a `GoToDefinition` action for the editor --- crates/editor/src/editor.rs | 46 +++++++++++++++++++++++++++++-- crates/editor/src/multi_buffer.rs | 13 +++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8a56170daa54d55bbae10f508414b2e1a66e5488..38adc65df9e356b560ed610512af03e13530ccd5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -25,8 +25,8 @@ use gpui::{ use items::BufferItemHandle; use itertools::Itertools as _; use language::{ - BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point, Selection, SelectionGoal, - TransactionId, + AnchorRangeExt as _, BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point, + Selection, SelectionGoal, TransactionId, }; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, ToPoint, @@ -107,6 +107,7 @@ action!(SelectLargerSyntaxNode); action!(SelectSmallerSyntaxNode); action!(MoveToEnclosingBracket); action!(ShowNextDiagnostic); +action!(GoToDefinition); action!(PageUp); action!(PageDown); action!(Fold); @@ -214,6 +215,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec, + ) { + let editor = workspace + .active_item(cx) + .and_then(|item| item.to_any().downcast::()) + .unwrap() + .read(cx); + let buffer = editor.buffer.read(cx); + let head = editor.newest_selection::(&buffer.read(cx)).head(); + let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx); + let definitions = workspace + .project() + .update(cx, |project, cx| project.definition(&buffer, head, cx)); + cx.spawn(|workspace, mut cx| async move { + let definitions = definitions.await?; + workspace.update(&mut cx, |workspace, cx| { + for definition in definitions { + let range = definition + .target_range + .to_offset(definition.target_buffer.read(cx)); + let target_editor = workspace + .open_item(BufferItemHandle(definition.target_buffer), cx) + .to_any() + .downcast::() + .unwrap(); + target_editor.update(cx, |target_editor, cx| { + target_editor.select_ranges([range], Some(Autoscroll::Fit), cx); + }); + } + }); + + Ok::<(), anyhow::Error>(()) + }) + .detach_and_log_err(cx); + } + fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext) { if let Some(active_diagnostics) = self.active_diagnostics.as_mut() { let buffer = self.buffer.read(cx).snapshot(cx); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 04190ed39aad96e32bb354e8ec966929d06ba00d..f3d906e27dd411fcf0938b923ac202c01b383735 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -789,6 +789,19 @@ impl MultiBuffer { cx.notify(); } + pub fn text_anchor_for_position<'a, T: ToOffset>( + &'a self, + position: T, + cx: &AppContext, + ) -> (ModelHandle, language::Anchor) { + let snapshot = self.read(cx); + let anchor = snapshot.anchor_before(position); + ( + self.buffers.borrow()[&anchor.buffer_id].buffer.clone(), + anchor.text_anchor, + ) + } + fn on_buffer_event( &mut self, _: ModelHandle, From a53c87edfedca9f636ba714e60366fe86153deea Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 15:26:10 +0100 Subject: [PATCH 16/31] :art: --- crates/project/src/project.rs | 38 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3de0d564df5072ca421cd396541de5bfa65289a2..79a0304a1b6e4e3ed18329a0b6ebc24b7f857391 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -676,28 +676,22 @@ impl Project { .uri .to_file_path() .map_err(|_| anyhow!("URI is not a file"))?; - for tree in &self.worktrees { - let relative_path = tree.update(cx, |tree, _| { - path.strip_prefix(tree.as_local()?.abs_path()).ok() - }); - if let Some(relative_path) = relative_path { - let worktree_id = tree.read(cx).id(); - let project_path = ProjectPath { - worktree_id, - path: relative_path.into(), - }; - tree.update(cx, |tree, cx| { - tree.as_local_mut().unwrap().update_diagnostics( - project_path.path.clone(), - diagnostics, - disk_based_sources, - cx, - ) - })?; - cx.emit(Event::DiagnosticsUpdated(project_path)); - break; - } - } + let (worktree, relative_path) = self + .find_worktree_for_abs_path(&path, cx) + .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?; + let project_path = ProjectPath { + worktree_id: worktree.read(cx).id(), + path: relative_path.into(), + }; + worktree.update(cx, |worktree, cx| { + worktree.as_local_mut().unwrap().update_diagnostics( + project_path.path.clone(), + diagnostics, + disk_based_sources, + cx, + ) + })?; + cx.emit(Event::DiagnosticsUpdated(project_path)); Ok(()) } From d92b40474fa128595f8e3efc86efcbb61b268bfd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 15:43:02 +0100 Subject: [PATCH 17/31] Change `GoToDefinition` binding to `F12` Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 38adc65df9e356b560ed610512af03e13530ccd5..f2acfc23f85e62428a34a9e79309cb93ac421f89 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -215,7 +215,7 @@ pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec Date: Thu, 20 Jan 2022 17:30:30 +0100 Subject: [PATCH 18/31] Make "go to definition" work in project diagnostics Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 22 +++++++++++++--- crates/editor/src/editor.rs | 17 +++++++----- crates/editor/src/items.rs | 4 +-- crates/go_to_line/src/go_to_line.rs | 18 +++++-------- crates/journal/src/journal.rs | 2 +- crates/outline/src/outline.rs | 2 +- crates/workspace/src/pane.rs | 2 +- crates/workspace/src/workspace.rs | 38 +++++++++++++++++++++++++-- crates/zed/src/zed.rs | 11 ++------ 9 files changed, 79 insertions(+), 37 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 3971fabf64abe1a9f29fcb37a8e4ada78b896a91..cff31fc6fbf84799f02bab5dd4b8d91892c64e3c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -9,13 +9,13 @@ use editor::{ Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, }; use gpui::{ - action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, - RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, + action, elements::*, keymap::Binding, AnyViewHandle, AppContext, Entity, ModelHandle, + MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath}; -use std::{cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; +use std::{any::TypeId, cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; use util::TryFutureExt; use workspace::{NavHistory, Workspace}; @@ -194,7 +194,6 @@ impl ProjectDiagnosticsEditor { } let editor = workspace .open_item(buffer, cx) - .to_any() .downcast::() .unwrap(); editor.update(cx, |editor, cx| { @@ -595,6 +594,21 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { cx, )) } + + fn act_as_type( + &self, + type_id: TypeId, + self_handle: &ViewHandle, + _: &AppContext, + ) -> Option { + if type_id == TypeId::of::() { + Some(self_handle.into()) + } else if type_id == TypeId::of::() { + Some((&self.editor).into()) + } else { + None + } + } } fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettings) -> RenderBlock { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f2acfc23f85e62428a34a9e79309cb93ac421f89..d7b1e54735772957507e2b0b6f036f52f4523e27 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2992,11 +2992,17 @@ impl Editor { _: &GoToDefinition, cx: &mut ViewContext, ) { - let editor = workspace - .active_item(cx) - .and_then(|item| item.to_any().downcast::()) - .unwrap() - .read(cx); + let active_item = workspace.active_item(cx); + let editor = if let Some(editor) = active_item + .as_ref() + .and_then(|item| item.act_as::(cx)) + { + editor + } else { + return; + }; + + let editor = editor.read(cx); let buffer = editor.buffer.read(cx); let head = editor.newest_selection::(&buffer.read(cx)).head(); let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx); @@ -3012,7 +3018,6 @@ impl Editor { .to_offset(definition.target_buffer.read(cx)); let target_editor = workspace .open_item(BufferItemHandle(definition.target_buffer), cx) - .to_any() .downcast::() .unwrap(); target_editor.update(cx, |target_editor, cx| { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e534f3b8fa18d42be7bdd26e54bc036532c53dbf..dd471a8c36dacdda4541240b6331c9a4ccdde6f0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -279,7 +279,7 @@ impl StatusItemView for CursorPosition { active_pane_item: Option<&dyn ItemViewHandle>, cx: &mut ViewContext, ) { - if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::()) { + if let Some(editor) = active_pane_item.and_then(|item| item.downcast::()) { self._observe_active_editor = Some(cx.observe(&editor, Self::update_position)); self.update_position(editor, cx); } else { @@ -365,7 +365,7 @@ impl StatusItemView for DiagnosticMessage { active_pane_item: Option<&dyn ItemViewHandle>, cx: &mut ViewContext, ) { - if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::()) { + if let Some(editor) = active_pane_item.and_then(|item| item.downcast::()) { self._observe_active_editor = Some(cx.observe(&editor, Self::update)); self.update(editor, cx); } else { diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index c282cd1c7510d6ae3aaf277894fcfceecfecfd9d..68d7424ae4b10b80782234710bc6b3a0bb7c4206 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -80,17 +80,13 @@ impl GoToLine { } fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { - workspace.toggle_modal(cx, |cx, workspace| { - let editor = workspace - .active_item(cx) - .unwrap() - .to_any() - .downcast::() - .unwrap(); - let view = cx.add_view(|cx| GoToLine::new(editor, workspace.settings.clone(), cx)); - cx.subscribe(&view, Self::on_event).detach(); - view - }); + if let Some(editor) = workspace.active_item(cx).unwrap().downcast::() { + workspace.toggle_modal(cx, |cx, workspace| { + let view = cx.add_view(|cx| GoToLine::new(editor, workspace.settings.clone(), cx)); + cx.subscribe(&view, Self::on_event).detach(); + view + }); + } } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 8adda8e1d37224d2a1df342e411f1ed1d314cd7a..460423f6dc7107e56563c442081c0942e6446108 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -55,7 +55,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { .await; if let Some(Some(Ok(item))) = opened.first() { - if let Some(editor) = item.to_any().downcast::() { + if let Some(editor) = item.downcast::() { editor.update(&mut cx, |editor, cx| { let len = editor.buffer().read(cx).read(cx).len(); editor.select_ranges([len..len], Some(Autoscroll::Center), cx); diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 46bf088c31479b0bcf26d40b70933cc3947080c3..151e3ec0bb3187caea98d0fe0b8f8464baaae80f 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -144,7 +144,7 @@ impl OutlineView { fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { if let Some(editor) = workspace .active_item(cx) - .and_then(|item| item.to_any().downcast::()) + .and_then(|item| item.downcast::()) { let settings = workspace.settings(); let buffer = editor diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3944a5d49d657c82d8a84bdd2d5dc18a222fc346..524ccf68e21ee551aa0f3395bba64e8230bf23e7 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -351,7 +351,7 @@ impl Pane { fn focus_active_item(&mut self, cx: &mut ViewContext) { if let Some(active_item) = self.active_item() { - cx.focus(active_item.to_any()); + cx.focus(active_item); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e865c7f2b12f407658b4793494fec59ac847b7d2..2f661789a3dab3f8157d02a724ea330c74923974 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -33,7 +33,7 @@ use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItem use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ - any::Any, + any::{Any, TypeId}, future::Future, hash::{Hash, Hasher}, path::{Path, PathBuf}, @@ -185,6 +185,18 @@ pub trait ItemView: View { fn should_update_tab_on_event(_: &Self::Event) -> bool { false } + fn act_as_type( + &self, + type_id: TypeId, + self_handle: &ViewHandle, + _: &AppContext, + ) -> Option { + if TypeId::of::() == type_id { + Some(self_handle.into()) + } else { + None + } + } } pub trait ItemHandle: Send + Sync { @@ -207,7 +219,7 @@ pub trait WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; } -pub trait ItemViewHandle { +pub trait ItemViewHandle: 'static { fn item_handle(&self, cx: &AppContext) -> Box; fn title(&self, cx: &AppContext) -> String; fn project_entry(&self, cx: &AppContext) -> Option; @@ -229,6 +241,7 @@ pub trait ItemViewHandle { abs_path: PathBuf, cx: &mut MutableAppContext, ) -> Task>; + fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; } pub trait WeakItemViewHandle { @@ -326,6 +339,17 @@ impl PartialEq for Box { impl Eq for Box {} +impl dyn ItemViewHandle { + pub fn downcast(&self) -> Option> { + self.to_any().downcast() + } + + pub fn act_as(&self, cx: &AppContext) -> Option> { + self.act_as_type(TypeId::of::(), cx) + .and_then(|t| t.downcast()) + } +} + impl ItemViewHandle for ViewHandle { fn item_handle(&self, cx: &AppContext) -> Box { Box::new(self.read(cx).item_handle(cx)) @@ -413,6 +437,16 @@ impl ItemViewHandle for ViewHandle { fn can_save_as(&self, cx: &AppContext) -> bool { self.read(cx).can_save_as(cx) } + + fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option { + self.read(cx).act_as_type(type_id, self, cx) + } +} + +impl Into for Box { + fn into(self) -> AnyViewHandle { + self.to_any() + } } impl Clone for Box { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 72fd6f28d35f4cb86c509ad8ba316114098216e1..22eef7fe40ae135b164d1299f17b7ae33f509976 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -205,7 +205,6 @@ mod tests { workspace .active_item(cx) .unwrap() - .to_any() .downcast::() .unwrap() }); @@ -451,7 +450,7 @@ mod tests { let editor = cx.read(|cx| { let pane = workspace.read(cx).active_pane().read(cx); let item = pane.active_item().unwrap(); - item.to_any().downcast::().unwrap() + item.downcast::().unwrap() }); cx.update(|cx| { @@ -504,7 +503,6 @@ mod tests { workspace .active_item(cx) .unwrap() - .to_any() .downcast::() .unwrap() }); @@ -573,7 +571,6 @@ mod tests { workspace .active_item(cx) .unwrap() - .to_any() .downcast::() .unwrap() }); @@ -598,7 +595,6 @@ mod tests { workspace .active_item(cx) .unwrap() - .to_any() .downcast::() .unwrap() }); @@ -738,7 +734,6 @@ mod tests { .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) .await .unwrap() - .to_any() .downcast::() .unwrap(); editor1.update(&mut cx, |editor, cx| { @@ -748,14 +743,12 @@ mod tests { .update(&mut cx, |w, cx| w.open_path(file2.clone(), cx)) .await .unwrap() - .to_any() .downcast::() .unwrap(); let editor3 = workspace .update(&mut cx, |w, cx| w.open_path(file3.clone(), cx)) .await .unwrap() - .to_any() .downcast::() .unwrap(); editor3.update(&mut cx, |editor, cx| { @@ -871,7 +864,7 @@ mod tests { ) -> (ProjectPath, DisplayPoint) { workspace.update(cx, |workspace, cx| { let item = workspace.active_item(cx).unwrap(); - let editor = item.to_any().downcast::().unwrap(); + let editor = item.downcast::().unwrap(); let selections = editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)); let path = workspace .project() From fad5c98b8d35eede1351f9fcfc811358b917bdf5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 17:33:07 +0100 Subject: [PATCH 19/31] Center selections when going to definition Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d7b1e54735772957507e2b0b6f036f52f4523e27..42b2fe30a19e2f04cc74611084e5410f4accd9c4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3021,7 +3021,7 @@ impl Editor { .downcast::() .unwrap(); target_editor.update(cx, |target_editor, cx| { - target_editor.select_ranges([range], Some(Autoscroll::Fit), cx); + target_editor.select_ranges([range], Some(Autoscroll::Center), cx); }); } }); From c450945001ad3323d731f8b5928d30061164b561 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 18:11:37 +0100 Subject: [PATCH 20/31] WIP --- crates/diagnostics/src/diagnostics.rs | 6 +- crates/editor/src/editor.rs | 35 +++++----- crates/editor/src/items.rs | 9 +-- crates/workspace/src/pane.rs | 98 +++++++++++++++++---------- crates/workspace/src/workspace.rs | 10 +-- 5 files changed, 94 insertions(+), 64 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index cff31fc6fbf84799f02bab5dd4b8d91892c64e3c..00e60f5c4f58eca1bd0a7373e5d82940d9d81d82 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -15,9 +15,9 @@ use gpui::{ use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath}; -use std::{any::TypeId, cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; +use std::{any::TypeId, cmp::Ordering, mem, ops::Range, path::PathBuf, sync::Arc}; use util::TryFutureExt; -use workspace::{NavHistory, Workspace}; +use workspace::{ItemNavHistory, Workspace}; action!(Deploy); action!(OpenExcerpts); @@ -517,7 +517,7 @@ impl workspace::Item for ProjectDiagnostics { fn build_view( handle: ModelHandle, workspace: &Workspace, - _: Rc, + _: ItemNavHistory, cx: &mut ViewContext, ) -> Self::View { ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), workspace.settings(), cx) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 42b2fe30a19e2f04cc74611084e5410f4accd9c4..daca2d2ca7c0a857170e7dbff4e593b2eedef72e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -41,7 +41,6 @@ use std::{ iter::{self, FromIterator}, mem, ops::{Deref, Range, RangeInclusive, Sub}, - rc::Rc, sync::Arc, time::{Duration, Instant}, }; @@ -49,7 +48,7 @@ use sum_tree::Bias; use text::rope::TextDimension; use theme::{DiagnosticStyle, EditorStyle}; use util::post_inc; -use workspace::{NavHistory, PathOpener, Workspace}; +use workspace::{ItemNavHistory, PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; @@ -382,7 +381,7 @@ pub struct Editor { mode: EditorMode, placeholder_text: Option>, highlighted_rows: Option>, - nav_history: Option>, + nav_history: Option, } pub struct EditorSnapshot { @@ -468,7 +467,10 @@ impl Editor { let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx); clone.scroll_position = self.scroll_position; clone.scroll_top_anchor = self.scroll_top_anchor.clone(); - clone.nav_history = self.nav_history.clone(); + clone.nav_history = self + .nav_history + .as_ref() + .map(|nav_history| nav_history.clone(&cx.handle())); clone } @@ -2476,13 +2478,10 @@ impl Editor { } } - nav_history.push( - Some(NavigationData { - anchor: position, - offset, - }), - cx, - ); + nav_history.push(Some(NavigationData { + anchor: position, + offset, + })); } } @@ -4041,7 +4040,7 @@ pub fn settings_builder( mod tests { use super::*; use language::LanguageConfig; - use std::time::Instant; + use std::{cell::RefCell, rc::Rc, time::Instant}; use text::Point; use unindent::Unindent; use util::test::sample_text; @@ -4220,22 +4219,22 @@ mod tests { fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.add_window(Default::default(), |cx| { use workspace::ItemView; - let nav_history = Rc::new(workspace::NavHistory::default()); + let nav_history = Rc::new(RefCell::new(workspace::NavHistory::default())); let settings = EditorSettings::test(&cx); let buffer = MultiBuffer::build_simple(&sample_text(30, 5, 'a'), cx); let mut editor = build_editor(buffer.clone(), settings, cx); - editor.nav_history = Some(nav_history.clone()); + editor.nav_history = Some(ItemNavHistory::new(nav_history.clone(), &cx.handle())); // Move the cursor a small distance. // Nothing is added to the navigation history. editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx); editor.select_display_ranges(&[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)], cx); - assert!(nav_history.pop_backward().is_none()); + assert!(nav_history.borrow_mut().pop_backward().is_none()); // Move the cursor a large distance. // The history can jump back to the previous position. editor.select_display_ranges(&[DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)], cx); - let nav_entry = nav_history.pop_backward().unwrap(); + let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item_view.id(), cx.view_id()); assert_eq!( @@ -4251,7 +4250,7 @@ mod tests { editor.selected_display_ranges(cx), &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] ); - assert!(nav_history.pop_backward().is_none()); + assert!(nav_history.borrow_mut().pop_backward().is_none()); // Move the cursor a large distance via the mouse. // The history can jump back to the previous position. @@ -4261,7 +4260,7 @@ mod tests { editor.selected_display_ranges(cx), &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)] ); - let nav_entry = nav_history.pop_backward().unwrap(); + let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item_view.id(), cx.view_id()); assert_eq!( diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index dd471a8c36dacdda4541240b6331c9a4ccdde6f0..07caad356263556526b08f31c2437ca8ce8b22f3 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -8,13 +8,14 @@ use language::{Bias, Buffer, Diagnostic}; use postage::watch; use project::worktree::File; use project::{Project, ProjectEntry, ProjectPath}; +use std::cell::RefCell; use std::rc::Rc; use std::{fmt::Write, path::PathBuf}; use text::{Point, Selection}; use util::TryFutureExt; use workspace::{ - ItemHandle, ItemView, ItemViewHandle, NavHistory, PathOpener, Settings, StatusItemView, - WeakItemHandle, Workspace, + ItemHandle, ItemNavHistory, ItemView, ItemViewHandle, NavHistory, PathOpener, Settings, + StatusItemView, WeakItemHandle, Workspace, }; pub struct BufferOpener; @@ -46,7 +47,7 @@ impl ItemHandle for BufferItemHandle { &self, window_id: usize, workspace: &Workspace, - nav_history: Rc, + nav_history: Rc>, cx: &mut MutableAppContext, ) -> Box { let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); @@ -57,7 +58,7 @@ impl ItemHandle for BufferItemHandle { crate::settings_builder(weak_buffer, workspace.settings()), cx, ); - editor.nav_history = Some(nav_history); + editor.nav_history = Some(ItemNavHistory::new(nav_history, &cx.handle())); editor })) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 524ccf68e21ee551aa0f3395bba64e8230bf23e7..6b099b492d958be5674cbdb44e5f17393420c946 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -76,14 +76,16 @@ pub struct Pane { item_views: Vec<(usize, Box)>, active_item_index: usize, settings: watch::Receiver, - nav_history: Rc, + nav_history: Rc>, } -#[derive(Default)] -pub struct NavHistory(RefCell); +pub struct ItemNavHistory { + history: Rc>, + item_view: Rc, +} #[derive(Default)] -struct NavHistoryState { +pub struct NavHistory { mode: NavigationMode, backward_stack: VecDeque, forward_stack: VecDeque, @@ -104,7 +106,7 @@ impl Default for NavigationMode { } pub struct NavigationEntry { - pub item_view: Box, + pub item_view: Rc, pub data: Option>, } @@ -148,7 +150,7 @@ impl Pane { ) -> Task<()> { let to_load = pane.update(cx, |pane, cx| { // Retrieve the weak item handle from the history. - let nav_entry = pane.nav_history.pop(mode)?; + let nav_entry = pane.nav_history.borrow_mut().pop(mode)?; // If the item is still present in this pane, then activate it. if let Some(index) = nav_entry @@ -157,9 +159,11 @@ impl Pane { .and_then(|v| pane.index_for_item_view(v.as_ref())) { if let Some(item_view) = pane.active_item() { - pane.nav_history.set_mode(mode); + pane.nav_history.borrow_mut().set_mode(mode); item_view.deactivated(cx); - pane.nav_history.set_mode(NavigationMode::Normal); + pane.nav_history + .borrow_mut() + .set_mode(NavigationMode::Normal); } pane.active_item_index = index; @@ -174,7 +178,6 @@ impl Pane { // project path in order to reopen it. else { pane.nav_history - .0 .borrow_mut() .project_entries_by_item .get(&nav_entry.item_view.id()) @@ -192,9 +195,11 @@ impl Pane { if let Some(pane) = cx.read(|cx| pane.upgrade(cx)) { if let Some(item) = item.log_err() { workspace.update(&mut cx, |workspace, cx| { - pane.update(cx, |p, _| p.nav_history.set_mode(mode)); + pane.update(cx, |p, _| p.nav_history.borrow_mut().set_mode(mode)); let item_view = workspace.open_item_in_pane(item, &pane, cx); - pane.update(cx, |p, _| p.nav_history.set_mode(NavigationMode::Normal)); + pane.update(cx, |p, _| { + p.nav_history.borrow_mut().set_mode(NavigationMode::Normal) + }); if let Some(data) = nav_entry.data { item_view.navigate(data, cx); @@ -322,7 +327,7 @@ impl Pane { item_view.deactivated(cx); } - let mut nav_history = self.nav_history.0.borrow_mut(); + let mut nav_history = self.nav_history.borrow_mut(); if let Some(entry) = item_view.project_entry(cx) { nav_history .project_entries_by_item @@ -538,16 +543,36 @@ impl View for Pane { } } +impl ItemNavHistory { + pub fn new(history: Rc>, item_view: &ViewHandle) -> Self { + Self { + history, + item_view: Rc::new(item_view.downgrade()), + } + } + + pub fn clone(&self, item_view: &ViewHandle) -> Self { + Self { + history: self.history.clone(), + item_view: Rc::new(item_view.downgrade()), + } + } + + pub fn push(&self, data: Option) { + self.history.borrow_mut().push(data, self.item_view.clone()); + } +} + impl NavHistory { - pub fn pop_backward(&self) -> Option { - self.0.borrow_mut().backward_stack.pop_back() + pub fn pop_backward(&mut self) -> Option { + self.backward_stack.pop_back() } - pub fn pop_forward(&self) -> Option { - self.0.borrow_mut().forward_stack.pop_back() + pub fn pop_forward(&mut self) -> Option { + self.forward_stack.pop_back() } - fn pop(&self, mode: NavigationMode) -> Option { + fn pop(&mut self, mode: NavigationMode) -> Option { match mode { NavigationMode::Normal => None, NavigationMode::GoingBack => self.pop_backward(), @@ -555,38 +580,41 @@ impl NavHistory { } } - fn set_mode(&self, mode: NavigationMode) { - self.0.borrow_mut().mode = mode; + fn set_mode(&mut self, mode: NavigationMode) { + self.mode = mode; } - pub fn push(&self, data: Option, cx: &mut ViewContext) { - let mut state = self.0.borrow_mut(); - match state.mode { + pub fn push( + &mut self, + data: Option, + item_view: Rc, + ) { + match self.mode { NavigationMode::Normal => { - if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.backward_stack.pop_front(); + if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + self.backward_stack.pop_front(); } - state.backward_stack.push_back(NavigationEntry { - item_view: Box::new(cx.weak_handle()), + self.backward_stack.push_back(NavigationEntry { + item_view, data: data.map(|data| Box::new(data) as Box), }); - state.forward_stack.clear(); + self.forward_stack.clear(); } NavigationMode::GoingBack => { - if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.forward_stack.pop_front(); + if self.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + self.forward_stack.pop_front(); } - state.forward_stack.push_back(NavigationEntry { - item_view: Box::new(cx.weak_handle()), + self.forward_stack.push_back(NavigationEntry { + item_view, data: data.map(|data| Box::new(data) as Box), }); } NavigationMode::GoingForward => { - if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.backward_stack.pop_front(); + if self.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + self.backward_stack.pop_front(); } - state.backward_stack.push_back(NavigationEntry { - item_view: Box::new(cx.weak_handle()), + self.backward_stack.push_back(NavigationEntry { + item_view, data: data.map(|data| Box::new(data) as Box), }); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2f661789a3dab3f8157d02a724ea330c74923974..51709749a66130fbce98617c5f35fa68111e2917 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -34,6 +34,7 @@ use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ any::{Any, TypeId}, + cell::RefCell, future::Future, hash::{Hash, Hasher}, path::{Path, PathBuf}, @@ -141,7 +142,7 @@ pub trait Item: Entity + Sized { fn build_view( handle: ModelHandle, workspace: &Workspace, - nav_history: Rc, + nav_history: ItemNavHistory, cx: &mut ViewContext, ) -> Self::View; fn project_entry(&self) -> Option; @@ -205,7 +206,7 @@ pub trait ItemHandle: Send + Sync { &self, window_id: usize, workspace: &Workspace, - nav_history: Rc, + nav_history: Rc>, cx: &mut MutableAppContext, ) -> Box; fn boxed_clone(&self) -> Box; @@ -258,10 +259,11 @@ impl ItemHandle for ModelHandle { &self, window_id: usize, workspace: &Workspace, - nav_history: Rc, + nav_history: Rc>, cx: &mut MutableAppContext, ) -> Box { Box::new(cx.add_view(window_id, |cx| { + let nav_history = ItemNavHistory::new(nav_history, &cx.handle()); T::build_view(self.clone(), workspace, nav_history, cx) })) } @@ -292,7 +294,7 @@ impl ItemHandle for Box { &self, window_id: usize, workspace: &Workspace, - nav_history: Rc, + nav_history: Rc>, cx: &mut MutableAppContext, ) -> Box { ItemHandle::add_view(self.as_ref(), window_id, workspace, nav_history, cx) From 377e41a90f5932d90033d44afeef15d0f136e022 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 20 Jan 2022 18:21:48 +0100 Subject: [PATCH 21/31] Make navigation history work with project diagnostics Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/diagnostics/src/diagnostics.rs | 44 ++++++++++++++++++++++++--- crates/editor/src/editor.rs | 10 +++++- crates/workspace/src/pane.rs | 7 ++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 00e60f5c4f58eca1bd0a7373e5d82940d9d81d82..779f7f998142f7cd87cfeadcb1827e89b6bf7979 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -15,7 +15,14 @@ use gpui::{ use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath}; -use std::{any::TypeId, cmp::Ordering, mem, ops::Range, path::PathBuf, sync::Arc}; +use std::{ + any::{Any, TypeId}, + cmp::Ordering, + mem, + ops::Range, + path::PathBuf, + sync::Arc, +}; use util::TryFutureExt; use workspace::{ItemNavHistory, Workspace}; @@ -517,10 +524,19 @@ impl workspace::Item for ProjectDiagnostics { fn build_view( handle: ModelHandle, workspace: &Workspace, - _: ItemNavHistory, + nav_history: ItemNavHistory, cx: &mut ViewContext, ) -> Self::View { - ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), workspace.settings(), cx) + let diagnostics = ProjectDiagnosticsEditor::new( + handle, + workspace.weak_handle(), + workspace.settings(), + cx, + ); + diagnostics + .editor + .update(cx, |editor, _| editor.set_nav_history(Some(nav_history))); + diagnostics } fn project_entry(&self) -> Option { @@ -543,6 +559,11 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { None } + fn navigate(&mut self, data: Box, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.navigate(data, cx)); + } + fn is_dirty(&self, cx: &AppContext) -> bool { self.excerpts.read(cx).read(cx).is_dirty() } @@ -587,12 +608,21 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { where Self: Sized, { - Some(ProjectDiagnosticsEditor::new( + let diagnostics = ProjectDiagnosticsEditor::new( self.model.clone(), self.workspace.clone(), self.settings.clone(), cx, - )) + ); + diagnostics.editor.update(cx, |editor, cx| { + let nav_history = self + .editor + .read(cx) + .nav_history() + .map(|nav_history| ItemNavHistory::new(nav_history.history(), &cx.handle())); + editor.set_nav_history(nav_history); + }); + Some(diagnostics) } fn act_as_type( @@ -609,6 +639,10 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { None } } + + fn deactivated(&mut self, cx: &mut ViewContext) { + self.editor.update(cx, |editor, cx| editor.deactivated(cx)); + } } fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettings) -> RenderBlock { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index daca2d2ca7c0a857170e7dbff4e593b2eedef72e..dc1df79b9165658a2adf41868f398cc243af7f88 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -470,7 +470,7 @@ impl Editor { clone.nav_history = self .nav_history .as_ref() - .map(|nav_history| nav_history.clone(&cx.handle())); + .map(|nav_history| ItemNavHistory::new(nav_history.history(), &cx.handle())); clone } @@ -2459,6 +2459,14 @@ impl Editor { self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); } + pub fn set_nav_history(&mut self, nav_history: Option) { + self.nav_history = nav_history; + } + + pub fn nav_history(&self) -> Option<&ItemNavHistory> { + self.nav_history.as_ref() + } + fn push_to_nav_history( &self, position: Anchor, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6b099b492d958be5674cbdb44e5f17393420c946..dabf816d027572c5c0d7efea7ca02017a80d96ec 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -551,11 +551,8 @@ impl ItemNavHistory { } } - pub fn clone(&self, item_view: &ViewHandle) -> Self { - Self { - history: self.history.clone(), - item_view: Rc::new(item_view.downgrade()), - } + pub fn history(&self) -> Rc> { + self.history.clone() } pub fn push(&self, data: Option) { From 9505d6cdcfcafd935de9467367f08fc3ed6199fe Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 20 Jan 2022 21:33:16 -0700 Subject: [PATCH 22/31] Disable the nav history when selecting a definition in a different buffer When jumping between different buffers, we don't care about the cursor's previous location. When navigating backward, we want to jump directly to the site of the jump. --- crates/editor/src/editor.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dc1df79b9165658a2adf41868f398cc243af7f88..0c393d93ab68420585ec9da62b81f00f59031271 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3000,7 +3000,7 @@ impl Editor { cx: &mut ViewContext, ) { let active_item = workspace.active_item(cx); - let editor = if let Some(editor) = active_item + let editor_handle = if let Some(editor) = active_item .as_ref() .and_then(|item| item.act_as::(cx)) { @@ -3009,7 +3009,7 @@ impl Editor { return; }; - let editor = editor.read(cx); + let editor = editor_handle.read(cx); let buffer = editor.buffer.read(cx); let head = editor.newest_selection::(&buffer.read(cx)).head(); let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx); @@ -3023,12 +3023,23 @@ impl Editor { let range = definition .target_range .to_offset(definition.target_buffer.read(cx)); - let target_editor = workspace + let target_editor_handle = workspace .open_item(BufferItemHandle(definition.target_buffer), cx) .downcast::() .unwrap(); - target_editor.update(cx, |target_editor, cx| { + + target_editor_handle.update(cx, |target_editor, cx| { + // When selecting a definition in a different buffer, disable the nav history + // to avoid creating a history entry at the previous cursor location. + let disabled_history = if editor_handle == target_editor_handle { + None + } else { + target_editor.nav_history.take() + }; target_editor.select_ranges([range], Some(Autoscroll::Center), cx); + if disabled_history.is_some() { + target_editor.nav_history = disabled_history; + } }); } }); From ee95775b1c68565e14d904ff922ff8083d674f98 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 Jan 2022 10:07:32 +0100 Subject: [PATCH 23/31] Unregister worktree when its last handle to it gets released --- crates/project/src/project.rs | 45 +++++++++++------------ crates/project/src/worktree.rs | 66 +++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 26 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 79a0304a1b6e4e3ed18329a0b6ebc24b7f857391..b12e90d9bc783152efbd7178f19ef9ff0ecccd5c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -160,16 +160,13 @@ impl Project { if let Some(project_id) = remote_id { let mut registrations = Vec::new(); - this.read_with(&cx, |this, cx| { + this.update(&mut cx, |this, cx| { for worktree in &this.worktrees { - let worktree_id = worktree.id() as u64; - let worktree = worktree.read(cx).as_local().unwrap(); - registrations.push(rpc.request( - proto::RegisterWorktree { - project_id, - worktree_id, - root_name: worktree.root_name().to_string(), - authorized_logins: worktree.authorized_logins(), + registrations.push(worktree.update( + cx, + |worktree, cx| { + let worktree = worktree.as_local_mut().unwrap(); + worktree.register(project_id, cx) }, )); } @@ -407,7 +404,7 @@ impl Project { for worktree in &this.worktrees { worktree.update(cx, |worktree, cx| { let worktree = worktree.as_local_mut().unwrap(); - tasks.push(worktree.share(project_id, cx)); + tasks.push(worktree.share(cx)); }); } }); @@ -897,21 +894,15 @@ impl Project { }); if let Some(project_id) = remote_project_id { - let worktree_id = worktree.id() as u64; - let register_message = worktree.update(&mut cx, |worktree, _| { - let worktree = worktree.as_local_mut().unwrap(); - proto::RegisterWorktree { - project_id, - worktree_id, - root_name: worktree.root_name().to_string(), - authorized_logins: worktree.authorized_logins(), - } - }); - client.request(register_message).await?; + worktree + .update(&mut cx, |worktree, cx| { + worktree.as_local_mut().unwrap().register(project_id, cx) + }) + .await?; if is_shared { worktree .update(&mut cx, |worktree, cx| { - worktree.as_local_mut().unwrap().share(project_id, cx) + worktree.as_local_mut().unwrap().share(cx) }) .await?; } @@ -921,6 +912,12 @@ impl Project { }) } + pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext) { + self.worktrees + .retain(|worktree| worktree.read(cx).id() != id); + cx.notify(); + } + fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); self.worktrees.push(worktree); @@ -1104,9 +1101,7 @@ impl Project { cx: &mut ModelContext, ) -> Result<()> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); - self.worktrees - .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id); - cx.notify(); + self.remove_worktree(worktree_id, cx); Ok(()) } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index d734a62bc3fb5d09fd3589c6cf3a28597bb70521..ea245cacdc96dd89ec7a32c53af5f8490e1efc73 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -66,6 +66,24 @@ pub enum Worktree { impl Entity for Worktree { type Event = (); + + fn release(&mut self, cx: &mut MutableAppContext) { + if let Some(worktree) = self.as_local_mut() { + if let Registration::Done { project_id } = worktree.registration { + let client = worktree.client.clone(); + let unregister_message = proto::UnregisterWorktree { + project_id, + worktree_id: worktree.id().to_proto(), + }; + cx.foreground() + .spawn(async move { + client.send(unregister_message).await?; + Ok::<_, anyhow::Error>(()) + }) + .detach_and_log_err(cx); + } + } + } } impl Worktree { @@ -747,6 +765,7 @@ pub struct LocalWorktree { last_scan_state_rx: watch::Receiver, _background_scanner_task: Option>, poll_task: Option>, + registration: Registration, share: Option, loading_buffers: LoadingBuffers, open_buffers: HashMap>, @@ -759,6 +778,13 @@ pub struct LocalWorktree { fs: Arc, } +#[derive(Debug, Eq, PartialEq)] +enum Registration { + None, + Pending, + Done { project_id: u64 }, +} + struct ShareState { project_id: u64, snapshots_tx: Sender, @@ -851,6 +877,7 @@ impl LocalWorktree { background_snapshot: Arc::new(Mutex::new(snapshot)), last_scan_state_rx, _background_scanner_task: None, + registration: Registration::None, share: None, poll_task: None, loading_buffers: Default::default(), @@ -1316,11 +1343,48 @@ impl LocalWorktree { }) } - pub fn share( + pub fn register( &mut self, project_id: u64, cx: &mut ModelContext, ) -> Task> { + if self.registration != Registration::None { + return Task::ready(Ok(())); + } + + self.registration = Registration::Pending; + let client = self.client.clone(); + let register_message = proto::RegisterWorktree { + project_id, + worktree_id: self.id().to_proto(), + root_name: self.root_name().to_string(), + authorized_logins: self.authorized_logins(), + }; + cx.spawn(|this, mut cx| async move { + let response = client.request(register_message).await; + this.update(&mut cx, |this, _| { + let worktree = this.as_local_mut().unwrap(); + match response { + Ok(_) => { + worktree.registration = Registration::Done { project_id }; + Ok(()) + } + Err(error) => { + worktree.registration = Registration::None; + Err(error) + } + } + }) + }) + } + + pub fn share(&mut self, cx: &mut ModelContext) -> Task> { + let project_id = if let Registration::Done { project_id } = self.registration { + project_id + } else { + return Task::ready(Err(anyhow!("cannot share worktree before registering it"))); + }; + if self.share.is_some() { return Task::ready(Ok(())); } From 6e7e86e4915e909af691eb6760717437b4478db1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 Jan 2022 13:37:52 +0100 Subject: [PATCH 24/31] Remove weak worktrees from project when nobody references them Also, avoid showing them in the project panel as well as in the contacts panel. --- crates/diagnostics/src/diagnostics.rs | 4 +- crates/file_finder/src/file_finder.rs | 25 +++-- crates/project/src/project.rs | 97 +++++++++++++------ crates/project/src/worktree.rs | 32 +++++- crates/project_panel/src/project_panel.rs | 113 ++++++++++++---------- crates/rpc/proto/zed.proto | 1 + crates/server/src/rpc.rs | 82 ++++++++++------ crates/server/src/rpc/store.rs | 2 + crates/workspace/src/workspace.rs | 22 ++--- crates/zed/src/zed.rs | 55 +++++------ 10 files changed, 262 insertions(+), 171 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 779f7f998142f7cd87cfeadcb1827e89b6bf7979..358cc0f39200555690cd870249b2bd315357326d 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -764,9 +764,9 @@ mod tests { ) .await; - let worktree = project + let (worktree, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree("/test", cx) + project.find_or_create_worktree_for_abs_path("/test", false, cx) }) .await .unwrap(); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 00f253bc75eb09634f33920c3f2d3f10d15befc5..99a60cf28d04af95abc747887a798b52d8d61093 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -454,9 +454,10 @@ mod tests { .await; let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -514,9 +515,10 @@ mod tests { .await; let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree("/dir".as_ref(), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/dir"), false, cx) }) .await .unwrap(); @@ -579,9 +581,14 @@ mod tests { .await; let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root/the-parent-dir/the-file"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path( + Path::new("/root/the-parent-dir/the-file"), + false, + cx, + ) }) .await .unwrap(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b12e90d9bc783152efbd7178f19ef9ff0ecccd5c..70f1f3eb1f83ebe6acaa97141a8062dde6369261 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -10,6 +10,7 @@ use futures::Future; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, + WeakModelHandle, }; use language::{ Bias, Buffer, DiagnosticEntry, File as _, Language, LanguageRegistry, ToOffset, ToPointUtf16, @@ -28,7 +29,7 @@ pub use fs::*; pub use worktree::*; pub struct Project { - worktrees: Vec>, + worktrees: Vec, active_entry: Option, languages: Arc, language_servers: HashMap<(WorktreeId, String), Arc>, @@ -41,6 +42,11 @@ pub struct Project { language_servers_with_diagnostics_running: isize, } +enum WorktreeHandle { + Strong(ModelHandle), + Weak(WeakModelHandle), +} + enum ProjectClientState { Local { is_shared: bool, @@ -161,7 +167,7 @@ impl Project { if let Some(project_id) = remote_id { let mut registrations = Vec::new(); this.update(&mut cx, |this, cx| { - for worktree in &this.worktrees { + for worktree in this.worktrees(cx).collect::>() { registrations.push(worktree.update( cx, |worktree, cx| { @@ -295,7 +301,7 @@ impl Project { language_servers: Default::default(), }; for worktree in worktrees { - this.add_worktree(worktree, cx); + this.add_worktree(&worktree, false, cx); } this })) @@ -364,8 +370,13 @@ impl Project { &self.collaborators } - pub fn worktrees(&self) -> &[ModelHandle] { - &self.worktrees + pub fn worktrees<'a>( + &'a self, + cx: &'a AppContext, + ) -> impl 'a + Iterator> { + self.worktrees + .iter() + .filter_map(move |worktree| worktree.upgrade(cx)) } pub fn worktree_for_id( @@ -373,10 +384,8 @@ impl Project { id: WorktreeId, cx: &AppContext, ) -> Option> { - self.worktrees - .iter() + self.worktrees(cx) .find(|worktree| worktree.read(cx).id() == id) - .cloned() } pub fn share(&self, cx: &mut ModelContext) -> Task> { @@ -401,7 +410,7 @@ impl Project { rpc.request(proto::ShareProject { project_id }).await?; let mut tasks = Vec::new(); this.update(&mut cx, |this, cx| { - for worktree in &this.worktrees { + for worktree in this.worktrees(cx).collect::>() { worktree.update(cx, |worktree, cx| { let worktree = worktree.as_local_mut().unwrap(); tasks.push(worktree.share(cx)); @@ -438,7 +447,7 @@ impl Project { rpc.send(proto::UnshareProject { project_id }).await?; this.update(&mut cx, |this, cx| { this.collaborators.clear(); - for worktree in &this.worktrees { + for worktree in this.worktrees(cx).collect::>() { worktree.update(cx, |worktree, _| { worktree.as_local_mut().unwrap().unshare(); }); @@ -494,7 +503,7 @@ impl Project { abs_path: PathBuf, cx: &mut ModelContext, ) -> Task> { - let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, cx); + let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, false, cx); cx.spawn(|this, mut cx| async move { let (worktree, path) = worktree_task.await?; worktree @@ -777,7 +786,7 @@ impl Project { } else { let (worktree, relative_path) = this .update(&mut cx, |this, cx| { - this.create_worktree_for_abs_path(&abs_path, cx) + this.create_worktree_for_abs_path(&abs_path, true, cx) }) .await?; this.update(&mut cx, |this, cx| { @@ -829,22 +838,25 @@ impl Project { pub fn find_or_create_worktree_for_abs_path( &self, - abs_path: &Path, + abs_path: impl AsRef, + weak: bool, cx: &mut ModelContext, ) -> Task, PathBuf)>> { + let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) { Task::ready(Ok((tree.clone(), relative_path.into()))) } else { - self.create_worktree_for_abs_path(abs_path, cx) + self.create_worktree_for_abs_path(abs_path, weak, cx) } } fn create_worktree_for_abs_path( &self, abs_path: &Path, + weak: bool, cx: &mut ModelContext, ) -> Task, PathBuf)>> { - let worktree = self.add_local_worktree(abs_path, cx); + let worktree = self.add_local_worktree(abs_path, weak, cx); cx.background().spawn(async move { let worktree = worktree.await?; Ok((worktree, PathBuf::new())) @@ -856,7 +868,7 @@ impl Project { abs_path: &Path, cx: &AppContext, ) -> Option<(ModelHandle, PathBuf)> { - for tree in &self.worktrees { + for tree in self.worktrees(cx) { if let Some(relative_path) = tree .read(cx) .as_local() @@ -875,9 +887,10 @@ impl Project { } } - pub fn add_local_worktree( + fn add_local_worktree( &self, abs_path: impl AsRef, + weak: bool, cx: &mut ModelContext, ) -> Task>> { let fs = self.fs.clone(); @@ -886,10 +899,10 @@ impl Project { let path = Arc::from(abs_path.as_ref()); cx.spawn(|project, mut cx| async move { let worktree = - Worktree::open_local(client.clone(), user_store, path, fs, &mut cx).await?; + Worktree::open_local(client.clone(), user_store, path, weak, fs, &mut cx).await?; let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| { - project.add_worktree(worktree.clone(), cx); + project.add_worktree(&worktree, weak, cx); (project.remote_id(), project.is_shared()) }); @@ -913,14 +926,28 @@ impl Project { } pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext) { - self.worktrees - .retain(|worktree| worktree.read(cx).id() != id); + self.worktrees.retain(|worktree| { + worktree + .upgrade(cx) + .map_or(false, |w| w.read(cx).id() != id) + }); cx.notify(); } - fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { + fn add_worktree( + &mut self, + worktree: &ModelHandle, + weak: bool, + cx: &mut ModelContext, + ) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - self.worktrees.push(worktree); + if weak { + self.worktrees + .push(WorktreeHandle::Weak(worktree.downgrade())); + } else { + self.worktrees + .push(WorktreeHandle::Strong(worktree.clone())); + } cx.notify(); } @@ -966,7 +993,7 @@ impl Project { &'a self, cx: &'a AppContext, ) -> impl Iterator + 'a { - self.worktrees.iter().flat_map(move |worktree| { + self.worktrees(cx).flat_map(move |worktree| { let worktree = worktree.read(cx); let worktree_id = worktree.id(); worktree @@ -1059,7 +1086,7 @@ impl Project { .remove(&peer_id) .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))? .replica_id; - for worktree in &self.worktrees { + for worktree in self.worktrees(cx).collect::>() { worktree.update(cx, |worktree, cx| { worktree.remove_collaborator(peer_id, replica_id, cx); }) @@ -1085,7 +1112,7 @@ impl Project { let worktree = Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx) .await?; - this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx)); + this.update(&mut cx, |this, cx| this.add_worktree(&worktree, false, cx)); Ok(()) } .log_err() @@ -1293,8 +1320,7 @@ impl Project { ) -> impl 'a + Future> { let include_root_name = self.worktrees.len() > 1; let candidate_sets = self - .worktrees - .iter() + .worktrees(cx) .map(|worktree| CandidateSet { snapshot: worktree.read(cx).snapshot(), include_ignored, @@ -1317,6 +1343,15 @@ impl Project { } } +impl WorktreeHandle { + pub fn upgrade(&self, cx: &AppContext) -> Option> { + match self { + WorktreeHandle::Strong(handle) => Some(handle.clone()), + WorktreeHandle::Weak(handle) => handle.upgrade(cx), + } + } +} + struct CandidateSet { snapshot: Snapshot, include_ignored: bool, @@ -1489,7 +1524,7 @@ mod tests { let tree = project .update(&mut cx, |project, cx| { - project.add_local_worktree(&root_link_path, cx) + project.add_local_worktree(&root_link_path, false, cx) }) .await .unwrap(); @@ -1564,7 +1599,7 @@ mod tests { let tree = project .update(&mut cx, |project, cx| { - project.add_local_worktree(dir.path(), cx) + project.add_local_worktree(dir.path(), false, cx) }) .await .unwrap(); @@ -1670,7 +1705,7 @@ mod tests { let project = build_project(&mut cx); let tree = project .update(&mut cx, |project, cx| { - project.add_local_worktree(&dir.path(), cx) + project.add_local_worktree(&dir.path(), false, cx) }) .await .unwrap(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ea245cacdc96dd89ec7a32c53af5f8490e1efc73..eb42b95bc5c5c2caae542f219988830236ad29fa 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -91,11 +91,12 @@ impl Worktree { client: Arc, user_store: ModelHandle, path: impl Into>, + weak: bool, fs: Arc, cx: &mut AsyncAppContext, ) -> Result> { let (tree, scan_states_tx) = - LocalWorktree::new(client, user_store, path, fs.clone(), cx).await?; + LocalWorktree::new(client, user_store, path, weak, fs.clone(), cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.snapshot.abs_path.clone(); @@ -126,6 +127,7 @@ impl Worktree { .map(|c| c.to_ascii_lowercase()) .collect(); let root_name = worktree.root_name.clone(); + let weak = worktree.weak; let (entries_by_path, entries_by_id, diagnostic_summaries) = cx .background() .spawn(async move { @@ -225,6 +227,7 @@ impl Worktree { queued_operations: Default::default(), user_store, diagnostic_summaries, + weak, }) }) }); @@ -271,6 +274,13 @@ impl Worktree { } } + pub fn is_weak(&self) -> bool { + match self { + Worktree::Local(worktree) => worktree.weak, + Worktree::Remote(worktree) => worktree.weak, + } + } + pub fn replica_id(&self) -> ReplicaId { match self { Worktree::Local(_) => 0, @@ -776,6 +786,7 @@ pub struct LocalWorktree { client: Arc, user_store: ModelHandle, fs: Arc, + weak: bool, } #[derive(Debug, Eq, PartialEq)] @@ -803,6 +814,7 @@ pub struct RemoteWorktree { user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, diagnostic_summaries: TreeMap, + weak: bool, } type LoadingBuffers = HashMap< @@ -822,6 +834,7 @@ impl LocalWorktree { client: Arc, user_store: ModelHandle, path: impl Into>, + weak: bool, fs: Arc, cx: &mut AsyncAppContext, ) -> Result<(ModelHandle, Sender)> { @@ -889,6 +902,7 @@ impl LocalWorktree { client, user_store, fs, + weak, }; cx.spawn_weak(|this, mut cx| async move { @@ -1415,10 +1429,11 @@ impl LocalWorktree { }); let diagnostic_summaries = self.diagnostic_summaries.clone(); + let weak = self.weak; let share_message = cx.background().spawn(async move { proto::ShareWorktree { project_id, - worktree: Some(snapshot.to_proto(&diagnostic_summaries)), + worktree: Some(snapshot.to_proto(&diagnostic_summaries, weak)), } }); @@ -1636,6 +1651,7 @@ impl Snapshot { pub fn to_proto( &self, diagnostic_summaries: &TreeMap, + weak: bool, ) -> proto::Worktree { let root_name = self.root_name.clone(); proto::Worktree { @@ -1651,6 +1667,7 @@ impl Snapshot { .iter() .map(|(path, summary)| summary.to_proto(path.0.clone())) .collect(), + weak, } } @@ -3110,6 +3127,7 @@ mod tests { client, user_store, Arc::from(Path::new("/root")), + false, Arc::new(fs), &mut cx.to_async(), ) @@ -3147,6 +3165,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3181,6 +3200,7 @@ mod tests { client, user_store, file_path.clone(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3229,6 +3249,7 @@ mod tests { client, user_store.clone(), dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3265,7 +3286,7 @@ mod tests { let remote = Worktree::remote( 1, 1, - initial_snapshot.to_proto(&Default::default()), + initial_snapshot.to_proto(&Default::default(), Default::default()), Client::new(http_client.clone()), user_store, &mut cx.to_async(), @@ -3379,6 +3400,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3431,6 +3453,7 @@ mod tests { client.clone(), user_store, "/the-dir".as_ref(), + false, fs, &mut cx.to_async(), ) @@ -3485,6 +3508,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3621,6 +3645,7 @@ mod tests { client, user_store, dir.path(), + false, Arc::new(RealFs), &mut cx.to_async(), ) @@ -3735,6 +3760,7 @@ mod tests { client.clone(), user_store, "/the-dir".as_ref(), + false, fs, &mut cx.to_async(), ) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 382a94284991d6d49775ffc988b73d9ae081d705..781c4cd586a88300b5aebc8e6885600557b82ac9 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,8 +6,8 @@ use gpui::{ }, keymap::{self, Binding}, platform::CursorStyle, - AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, ReadModel, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, View, ViewContext, + ViewHandle, WeakViewHandle, }; use postage::watch; use project::{Project, ProjectEntry, ProjectPath, Worktree, WorktreeId}; @@ -24,7 +24,7 @@ use workspace::{ pub struct ProjectPanel { project: ModelHandle, list: UniformListState, - visible_entries: Vec>, + visible_entries: Vec<(WorktreeId, Vec)>, expanded_dir_ids: HashMap>, selection: Option, settings: watch::Receiver, @@ -260,7 +260,11 @@ impl ProjectPanel { } fn select_first(&mut self, cx: &mut ViewContext) { - if let Some(worktree) = self.project.read(cx).worktrees().first() { + let worktree = self + .visible_entries + .first() + .and_then(|(worktree_id, _)| self.project.read(cx).worktree_for_id(*worktree_id, cx)); + if let Some(worktree) = worktree { let worktree = worktree.read(cx); let worktree_id = worktree.id(); if let Some(root_entry) = worktree.root_entry() { @@ -289,10 +293,11 @@ impl ProjectPanel { let project = self.project.read(cx); let mut offset = None; let mut ix = 0; - for (worktree_ix, visible_entries) in self.visible_entries.iter().enumerate() { + for (worktree_id, visible_entries) in &self.visible_entries { if target_ix < ix + visible_entries.len() { - let worktree = project.worktrees()[worktree_ix].read(cx); - offset = Some((worktree, visible_entries[target_ix - ix])); + offset = project + .worktree_for_id(*worktree_id, cx) + .map(|w| (w.read(cx), visible_entries[target_ix - ix])); break; } else { ix += visible_entries.len(); @@ -318,7 +323,11 @@ impl ProjectPanel { new_selected_entry: Option<(WorktreeId, usize)>, cx: &mut ViewContext, ) { - let worktrees = self.project.read(cx).worktrees(); + let worktrees = self + .project + .read(cx) + .worktrees(cx) + .filter(|worktree| !worktree.read(cx).is_weak()); self.visible_entries.clear(); let mut entry_ix = 0; @@ -369,7 +378,8 @@ impl ProjectPanel { } entry_iter.advance(); } - self.visible_entries.push(visible_worktree_entries); + self.visible_entries + .push((worktree_id, visible_worktree_entries)); } } @@ -404,16 +414,14 @@ impl ProjectPanel { } } - fn for_each_visible_entry( + fn for_each_visible_entry( &self, range: Range, - cx: &mut C, - mut callback: impl FnMut(ProjectEntry, EntryDetails, &mut C), + cx: &mut ViewContext, + mut callback: impl FnMut(ProjectEntry, EntryDetails, &mut ViewContext), ) { - let project = self.project.read(cx); - let worktrees = project.worktrees().to_vec(); let mut ix = 0; - for (worktree_ix, visible_worktree_entries) in self.visible_entries.iter().enumerate() { + for (worktree_id, visible_worktree_entries) in &self.visible_entries { if ix >= range.end { return; } @@ -423,37 +431,38 @@ impl ProjectPanel { } let end_ix = range.end.min(ix + visible_worktree_entries.len()); - let worktree = &worktrees[worktree_ix]; - let snapshot = worktree.read(cx).snapshot(); - let expanded_entry_ids = self - .expanded_dir_ids - .get(&snapshot.id()) - .map(Vec::as_slice) - .unwrap_or(&[]); - let root_name = OsStr::new(snapshot.root_name()); - let mut cursor = snapshot.entries(false); - - for ix in visible_worktree_entries[range.start.saturating_sub(ix)..end_ix - ix] - .iter() - .copied() - { - cursor.advance_to_offset(ix); - if let Some(entry) = cursor.entry() { - let filename = entry.path.file_name().unwrap_or(root_name); - let details = EntryDetails { - filename: filename.to_string_lossy().to_string(), - depth: entry.path.components().count(), - is_dir: entry.is_dir(), - is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(), - is_selected: self.selection.map_or(false, |e| { - e.worktree_id == snapshot.id() && e.entry_id == entry.id - }), - }; - let entry = ProjectEntry { - worktree_id: snapshot.id(), - entry_id: entry.id, - }; - callback(entry, details, cx); + if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { + let snapshot = worktree.read(cx).snapshot(); + let expanded_entry_ids = self + .expanded_dir_ids + .get(&snapshot.id()) + .map(Vec::as_slice) + .unwrap_or(&[]); + let root_name = OsStr::new(snapshot.root_name()); + let mut cursor = snapshot.entries(false); + + for ix in visible_worktree_entries[range.start.saturating_sub(ix)..end_ix - ix] + .iter() + .copied() + { + cursor.advance_to_offset(ix); + if let Some(entry) = cursor.entry() { + let filename = entry.path.file_name().unwrap_or(root_name); + let details = EntryDetails { + filename: filename.to_string_lossy().to_string(), + depth: entry.path.components().count(), + is_dir: entry.is_dir(), + is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(), + is_selected: self.selection.map_or(false, |e| { + e.worktree_id == snapshot.id() && e.entry_id == entry.id + }), + }; + let entry = ProjectEntry { + worktree_id: snapshot.id(), + entry_id: entry.id, + }; + callback(entry, details, cx); + } } } ix = end_ix; @@ -545,7 +554,7 @@ impl View for ProjectPanel { self.list.clone(), self.visible_entries .iter() - .map(|worktree_entries| worktree_entries.len()) + .map(|(_, worktree_entries)| worktree_entries.len()) .sum(), move |range, items, cx| { let theme = &settings.borrow().theme.project_panel; @@ -633,18 +642,18 @@ mod tests { cx, ) }); - let root1 = project + let (root1, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree("/root1", cx) + project.find_or_create_worktree_for_abs_path("/root1", false, cx) }) .await .unwrap(); root1 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete()) .await; - let root2 = project + let (root2, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree("/root2", cx) + project.find_or_create_worktree_for_abs_path("/root2", false, cx) }) .await .unwrap(); @@ -827,7 +836,7 @@ mod tests { ) { let path = path.as_ref(); panel.update(cx, |panel, cx| { - for worktree in panel.project.read(cx).worktrees() { + for worktree in panel.project.read(cx).worktrees(cx).collect::>() { let worktree = worktree.read(cx); if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) { let entry_id = worktree.entry_for_path(relative_path).unwrap().id; diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 973d459310498ccc88f842644c0272a2fec92103..9891ce21cffafeaa3e7d622ac86d01a30f195cae 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -272,6 +272,7 @@ message Worktree { string root_name = 2; repeated Entry entries = 3; repeated DiagnosticSummary diagnostic_summaries = 4; + bool weak = 5; } message Entry { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index adfbeacd13eaff50f39e4ad04f890b10b7762622..3ad10421363ff31eb1f68ecc6c52783aa3f94e22 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -309,6 +309,7 @@ impl Server { .values() .cloned() .collect(), + weak: worktree.weak, }) }) .collect(); @@ -421,6 +422,7 @@ impl Server { authorized_user_ids: contact_user_ids.clone(), root_name: request.payload.root_name, share: None, + weak: false, }, ); @@ -1158,8 +1160,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1184,7 +1188,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let replica_id_b = project_b.read_with(&cx_b, |project, _| { assert_eq!( @@ -1293,8 +1297,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1321,7 +1327,7 @@ mod tests { .await .unwrap(); - let worktree_b = project_b.read_with(&cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.read_with(&cx_b, |p, cx| p.worktrees(cx).next().unwrap()); worktree_b .update(&mut cx_b, |tree, cx| tree.open_buffer("a.txt", cx)) .await @@ -1353,7 +1359,7 @@ mod tests { ) .await .unwrap(); - let worktree_c = project_c.read_with(&cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_c = project_c.read_with(&cx_b, |p, cx| p.worktrees(cx).next().unwrap()); worktree_c .update(&mut cx_b, |tree, cx| tree.open_buffer("a.txt", cx)) .await @@ -1395,8 +1401,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1433,8 +1441,8 @@ mod tests { .unwrap(); // Open and edit a buffer as both guests B and C. - let worktree_b = project_b.read_with(&cx_b, |p, _| p.worktrees()[0].clone()); - let worktree_c = project_c.read_with(&cx_c, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.read_with(&cx_b, |p, cx| p.worktrees(cx).next().unwrap()); + let worktree_c = project_c.read_with(&cx_c, |p, cx| p.worktrees(cx).next().unwrap()); let buffer_b = worktree_b .update(&mut cx_b, |tree, cx| tree.open_buffer("file1", cx)) .await @@ -1547,8 +1555,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/dir", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/dir", false, cx) + }) .await .unwrap(); worktree_a @@ -1573,7 +1583,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); // Open a buffer as client B let buffer_b = worktree_b @@ -1642,8 +1652,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/dir", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/dir", false, cx) + }) .await .unwrap(); worktree_a @@ -1668,7 +1680,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); // Open a buffer as client A let buffer_a = worktree_a @@ -1723,8 +1735,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/dir", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/dir", false, cx) + }) .await .unwrap(); worktree_a @@ -1749,7 +1763,7 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); // See that a guest has joined as client A. project_a @@ -1799,8 +1813,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -1886,8 +1902,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -2023,7 +2041,7 @@ mod tests { .await; // Open the file with the errors on client B. They should be present. - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let buffer_b = cx_b .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) @@ -2108,8 +2126,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a @@ -2136,7 +2156,7 @@ mod tests { .unwrap(); // Open the file to be formatted on client B. - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + let worktree_b = project_b.update(&mut cx_b, |p, cx| p.worktrees(cx).next().unwrap()); let buffer_b = cx_b .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) @@ -2616,8 +2636,10 @@ mod tests { cx, ) }); - let worktree_a = project_a - .update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx)) + let (worktree_a, _) = project_a + .update(&mut cx_a, |p, cx| { + p.find_or_create_worktree_for_abs_path("/a", false, cx) + }) .await .unwrap(); worktree_a diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index a115b27db651e2f707443af50fc90456bd55fd93..b7aec2689bd5b575ee335775f881356a8fa09207 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -31,6 +31,7 @@ pub struct Worktree { pub authorized_user_ids: Vec, pub root_name: String, pub share: Option, + pub weak: bool, } #[derive(Default)] @@ -202,6 +203,7 @@ impl Store { let mut worktree_root_names = project .worktrees .values() + .filter(|worktree| !worktree.weak) .map(|worktree| worktree.root_name.clone()) .collect::>(); worktree_root_names.sort_unstable(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 51709749a66130fbce98617c5f35fa68111e2917..ebd29e69e7555953bfb8e00c69261be843f61a4c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -639,8 +639,11 @@ impl Workspace { &self.project } - pub fn worktrees<'a>(&self, cx: &'a AppContext) -> &'a [ModelHandle] { - &self.project.read(cx).worktrees() + pub fn worktrees<'a>( + &self, + cx: &'a AppContext, + ) -> impl 'a + Iterator> { + self.project.read(cx).worktrees(cx) } pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool { @@ -660,7 +663,6 @@ impl Workspace { pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { let futures = self .worktrees(cx) - .iter() .filter_map(|worktree| worktree.read(cx).as_local()) .map(|worktree| worktree.scan_complete()) .collect::>(); @@ -720,7 +722,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let entry = self.project().update(cx, |project, cx| { - project.find_or_create_worktree_for_abs_path(abs_path, cx) + project.find_or_create_worktree_for_abs_path(abs_path, false, cx) }); cx.spawn(|_, cx| async move { let (worktree, path) = entry.await?; @@ -731,15 +733,6 @@ impl Workspace { }) } - pub fn add_worktree( - &self, - path: &Path, - cx: &mut ViewContext, - ) -> Task>> { - self.project - .update(cx, |project, cx| project.add_local_worktree(path, cx)) - } - pub fn toggle_modal(&mut self, cx: &mut ViewContext, add_view: F) where V: 'static + View, @@ -866,7 +859,7 @@ impl Workspace { item.save(cx) } } else if item.can_save_as(cx) { - let worktree = self.worktrees(cx).first(); + let worktree = self.worktrees(cx).next(); let start_abs_path = worktree .and_then(|w| w.read(cx).as_local()) .map_or(Path::new(""), |w| w.abs_path()) @@ -1343,7 +1336,6 @@ impl WorkspaceHandle for ViewHandle { fn file_project_paths(&self, cx: &AppContext) -> Vec { self.read(cx) .worktrees(cx) - .iter() .flat_map(|worktree| { let worktree_id = worktree.read(cx).id(); worktree.read(cx).files(true, 0).map(move |f| ProjectPath { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 22eef7fe40ae135b164d1299f17b7ae33f509976..e636cced9334b4404b5fbd73c70be8d874d8101c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -175,7 +175,7 @@ mod tests { assert_eq!(cx.window_ids().len(), 1); let workspace_1 = cx.root_view::(cx.window_ids()[0]).unwrap(); workspace_1.read_with(&cx, |workspace, cx| { - assert_eq!(workspace.worktrees(cx).len(), 2) + assert_eq!(workspace.worktrees(cx).count(), 2) }); cx.update(|cx| { @@ -242,9 +242,10 @@ mod tests { .await; let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -366,9 +367,10 @@ mod tests { let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree("/dir1".as_ref(), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/dir1"), false, cx) }) .await .unwrap(); @@ -402,7 +404,6 @@ mod tests { let worktree_roots = workspace .read(cx) .worktrees(cx) - .iter() .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); assert_eq!( @@ -433,9 +434,10 @@ mod tests { let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -481,21 +483,14 @@ mod tests { app_state.fs.as_fake().insert_dir("/root").await.unwrap(); let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); - let worktree = cx.read(|cx| { - workspace - .read(cx) - .worktrees(cx) - .iter() - .next() - .unwrap() - .clone() - }); + let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); // Create a new untitled buffer cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone())); @@ -640,9 +635,10 @@ mod tests { let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); @@ -717,9 +713,10 @@ mod tests { .await; let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); - workspace - .update(&mut cx, |workspace, cx| { - workspace.add_worktree(Path::new("/root"), cx) + params + .project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(Path::new("/root"), false, cx) }) .await .unwrap(); From e5662dd426b1a4354fb4d1d6b61d6d9a28bb850a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 Jan 2022 14:22:06 +0100 Subject: [PATCH 25/31] Allow observing the release of entities Co-Authored-By: Nathan Sobo --- crates/gpui/src/app.rs | 148 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 14 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index cedba0a8db09a90eaece9608a5833665e2815db9..42179dfcc05310640c3426a600a23e36eafa9aee 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -660,6 +660,7 @@ type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext); type SubscriptionCallback = Box bool>; type ObservationCallback = Box bool>; +type ReleaseObservationCallback = Box; pub struct MutableAppContext { weak_self: Option>>, @@ -674,6 +675,7 @@ pub struct MutableAppContext { next_subscription_id: usize, subscriptions: Arc>>>, observations: Arc>>>, + release_observations: Arc>>>, presenters_and_platform_windows: HashMap>, Box)>, debug_elements_callbacks: HashMap crate::json::Value>>, @@ -717,6 +719,7 @@ impl MutableAppContext { next_subscription_id: 0, subscriptions: Default::default(), observations: Default::default(), + release_observations: Default::default(), presenters_and_platform_windows: HashMap::new(), debug_elements_callbacks: HashMap::new(), foreground, @@ -1030,6 +1033,27 @@ impl MutableAppContext { observations: Some(Arc::downgrade(&self.observations)), } } + + pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription + where + E: Entity, + E::Event: 'static, + H: Handle, + F: 'static + FnMut(&mut Self), + { + let id = post_inc(&mut self.next_subscription_id); + self.release_observations + .lock() + .entry(handle.id()) + .or_default() + .insert(id, Box::new(move |cx| callback(cx))); + Subscription::ReleaseObservation { + id, + entity_id: handle.id(), + observations: Some(Arc::downgrade(&self.release_observations)), + } + } + pub(crate) fn notify_model(&mut self, model_id: usize) { if self.pending_notifications.insert(model_id) { self.pending_effects @@ -1208,6 +1232,7 @@ impl MutableAppContext { self.cx.windows.remove(&window_id); self.presenters_and_platform_windows.remove(&window_id); self.remove_dropped_entities(); + self.flush_effects(); } fn open_platform_window(&mut self, window_id: usize, window_options: WindowOptions) { @@ -1358,6 +1383,9 @@ impl MutableAppContext { self.observations.lock().remove(&model_id); let mut model = self.cx.models.remove(&model_id).unwrap(); model.release(self); + self.pending_effects.push_back(Effect::Release { + entity_id: model_id, + }); } for (window_id, view_id) in dropped_views { @@ -1381,6 +1409,9 @@ impl MutableAppContext { if let Some(view_id) = change_focus_to { self.focus(window_id, view_id); } + + self.pending_effects + .push_back(Effect::Release { entity_id: view_id }); } for key in dropped_element_states { @@ -1406,6 +1437,7 @@ impl MutableAppContext { Effect::ViewNotification { window_id, view_id } => { self.notify_view_observers(window_id, view_id) } + Effect::Release { entity_id } => self.notify_release_observers(entity_id), Effect::Focus { window_id, view_id } => { self.focus(window_id, view_id); } @@ -1568,6 +1600,15 @@ impl MutableAppContext { } } + fn notify_release_observers(&mut self, entity_id: usize) { + let callbacks = self.release_observations.lock().remove(&entity_id); + if let Some(callbacks) = callbacks { + for (_, mut callback) in callbacks { + callback(self); + } + } + } + fn focus(&mut self, window_id: usize, focused_id: usize) { if self .cx @@ -1824,6 +1865,9 @@ pub enum Effect { window_id: usize, view_id: usize, }, + Release { + entity_id: usize, + }, Focus { window_id: usize, view_id: usize, @@ -1850,6 +1894,10 @@ impl Debug for Effect { .field("window_id", window_id) .field("view_id", view_id) .finish(), + Effect::Release { entity_id } => f + .debug_struct("Effect::Release") + .field("entity_id", entity_id) + .finish(), Effect::Focus { window_id, view_id } => f .debug_struct("Effect::Focus") .field("window_id", window_id) @@ -2072,6 +2120,25 @@ impl<'a, T: Entity> ModelContext<'a, T> { }) } + pub fn observe_release( + &mut self, + handle: &ModelHandle, + mut callback: F, + ) -> Subscription + where + S: Entity, + F: 'static + FnMut(&mut T, &mut ModelContext), + { + let observer = self.weak_handle(); + self.app.observe_release(handle, move |cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, cx); + }); + } + }) + } + pub fn handle(&self) -> ModelHandle { ModelHandle::new(self.model_id, &self.app.cx.ref_counts) } @@ -2305,6 +2372,22 @@ impl<'a, T: View> ViewContext<'a, T> { }) } + pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription + where + E: Entity, + H: Handle, + F: 'static + FnMut(&mut T, &mut ViewContext), + { + let observer = self.weak_handle(); + self.app.observe_release(handle, move |cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, cx); + }); + } + }) + } + pub fn emit(&mut self, payload: T::Event) { self.app.pending_effects.push_back(Effect::Event { entity_id: self.view_id, @@ -3263,6 +3346,12 @@ pub enum Subscription { entity_id: usize, observations: Option>>>>, }, + ReleaseObservation { + id: usize, + entity_id: usize, + observations: + Option>>>>, + }, } impl Subscription { @@ -3274,6 +3363,9 @@ impl Subscription { Subscription::Observation { observations, .. } => { observations.take(); } + Subscription::ReleaseObservation { observations, .. } => { + observations.take(); + } } } } @@ -3292,6 +3384,17 @@ impl Drop for Subscription { } } } + Subscription::ReleaseObservation { + id, + entity_id, + observations, + } => { + if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) { + if let Some(observations) = observations.lock().get_mut(entity_id) { + observations.remove(id); + } + } + } Subscription::Subscription { id, entity_id, @@ -3401,7 +3504,10 @@ mod tests { use super::*; use crate::elements::*; use smol::future::poll_once; - use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + use std::{ + cell::Cell, + sync::atomic::{AtomicUsize, Ordering::SeqCst}, + }; #[crate::test(self)] fn test_model_handles(cx: &mut MutableAppContext) { @@ -3652,18 +3758,18 @@ mod tests { #[crate::test(self)] fn test_entity_release_hooks(cx: &mut MutableAppContext) { struct Model { - released: Arc>, + released: Rc>, } struct View { - released: Arc>, + released: Rc>, } impl Entity for Model { type Event = (); fn release(&mut self, _: &mut MutableAppContext) { - *self.released.lock() = true; + self.released.set(true); } } @@ -3671,7 +3777,7 @@ mod tests { type Event = (); fn release(&mut self, _: &mut MutableAppContext) { - *self.released.lock() = true; + self.released.set(true); } } @@ -3685,27 +3791,41 @@ mod tests { } } - let model_released = Arc::new(Mutex::new(false)); - let view_released = Arc::new(Mutex::new(false)); + let model_released = Rc::new(Cell::new(false)); + let model_release_observed = Rc::new(Cell::new(false)); + let view_released = Rc::new(Cell::new(false)); + let view_release_observed = Rc::new(Cell::new(false)); let model = cx.add_model(|_| Model { released: model_released.clone(), }); - - let (window_id, _) = cx.add_window(Default::default(), |_| View { + let (window_id, view) = cx.add_window(Default::default(), |_| View { released: view_released.clone(), }); + assert!(!model_released.get()); + assert!(!view_released.get()); - assert!(!*model_released.lock()); - assert!(!*view_released.lock()); + cx.observe_release(&model, { + let model_release_observed = model_release_observed.clone(); + move |_| model_release_observed.set(true) + }) + .detach(); + cx.observe_release(&view, { + let view_release_observed = view_release_observed.clone(); + move |_| view_release_observed.set(true) + }) + .detach(); cx.update(move |_| { drop(model); }); - assert!(*model_released.lock()); + assert!(model_released.get()); + assert!(model_release_observed.get()); - drop(cx.remove_window(window_id)); - assert!(*view_released.lock()); + drop(view); + cx.remove_window(window_id); + assert!(view_released.get()); + assert!(view_release_observed.get()); } #[crate::test(self)] From 2fcf1aee6b44fd57448926e3f43be3d9d1aa1ded Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 Jan 2022 14:33:36 +0100 Subject: [PATCH 26/31] Remove weak handles when worktree gets dropped Co-Authored-By: Nathan Sobo --- crates/project/src/project.rs | 26 ++++++++++++++++---------- crates/project/src/worktree.rs | 4 ++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 70f1f3eb1f83ebe6acaa97141a8062dde6369261..7ae502ad0c133f2a69e211e811a9b6d3db10c43d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -301,7 +301,7 @@ impl Project { language_servers: Default::default(), }; for worktree in worktrees { - this.add_worktree(&worktree, false, cx); + this.add_worktree(&worktree, cx); } this })) @@ -902,7 +902,7 @@ impl Project { Worktree::open_local(client.clone(), user_store, path, weak, fs, &mut cx).await?; let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| { - project.add_worktree(&worktree, weak, cx); + project.add_worktree(&worktree, cx); (project.remote_id(), project.is_shared()) }); @@ -934,14 +934,20 @@ impl Project { cx.notify(); } - fn add_worktree( - &mut self, - worktree: &ModelHandle, - weak: bool, - cx: &mut ModelContext, - ) { + fn add_worktree(&mut self, worktree: &ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - if weak { + + let push_weak_handle = { + let worktree = worktree.read(cx); + worktree.is_local() && worktree.is_weak() + }; + if push_weak_handle { + cx.observe_release(&worktree, |this, cx| { + this.worktrees + .retain(|worktree| worktree.upgrade(cx).is_some()); + cx.notify(); + }) + .detach(); self.worktrees .push(WorktreeHandle::Weak(worktree.downgrade())); } else { @@ -1112,7 +1118,7 @@ impl Project { let worktree = Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx) .await?; - this.update(&mut cx, |this, cx| this.add_worktree(&worktree, false, cx)); + this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx)); Ok(()) } .log_err() diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index eb42b95bc5c5c2caae542f219988830236ad29fa..9b9a161b1a66160a5d438da3a7b434c424a44420 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -267,6 +267,10 @@ impl Worktree { } } + pub fn is_local(&self) -> bool { + matches!(self, Worktree::Local(_)) + } + pub fn snapshot(&self) -> Snapshot { match self { Worktree::Local(worktree) => worktree.snapshot(), From 64f5a453977ba0ad3a7e7cf41f4278616d7d0312 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 Jan 2022 14:39:35 +0100 Subject: [PATCH 27/31] Hide weak worktrees in the file finder Co-Authored-By: Nathan Sobo --- crates/project/src/project.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7ae502ad0c133f2a69e211e811a9b6d3db10c43d..6d65b3ab54f031f9ee432bd5393cac0f7a63ec86 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1324,9 +1324,13 @@ impl Project { cancel_flag: &'a AtomicBool, cx: &AppContext, ) -> impl 'a + Future> { - let include_root_name = self.worktrees.len() > 1; - let candidate_sets = self + let worktrees = self .worktrees(cx) + .filter(|worktree| !worktree.read(cx).is_weak()) + .collect::>(); + let include_root_name = worktrees.len() > 1; + let candidate_sets = worktrees + .into_iter() .map(|worktree| CandidateSet { snapshot: worktree.read(cx).snapshot(), include_ignored, From 4698d57ddd64785679e085f9d552150daf70811e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 Jan 2022 15:05:25 +0100 Subject: [PATCH 28/31] Add unit test for `Project::definition` Co-Authored-By: Nathan Sobo --- crates/project/src/project.rs | 135 ++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 7 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 6d65b3ab54f031f9ee432bd5393cac0f7a63ec86..c396c455935f887eb5f5bd7fcf63984487538410 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1498,7 +1498,8 @@ mod tests { use futures::StreamExt; use gpui::{test::subscribe, TestAppContext}; use language::{ - tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point, + tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry, + LanguageServerConfig, Point, }; use lsp::Url; use serde_json::json; @@ -1532,9 +1533,9 @@ mod tests { let project = build_project(&mut cx); - let tree = project + let (tree, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree(&root_link_path, false, cx) + project.find_or_create_worktree_for_abs_path(&root_link_path, false, cx) }) .await .unwrap(); @@ -1607,9 +1608,9 @@ mod tests { ) }); - let tree = project + let (tree, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree(dir.path(), false, cx) + project.find_or_create_worktree_for_abs_path(dir.path(), false, cx) }) .await .unwrap(); @@ -1713,9 +1714,9 @@ mod tests { })); let project = build_project(&mut cx); - let tree = project + let (tree, _) = project .update(&mut cx, |project, cx| { - project.add_local_worktree(&dir.path(), false, cx) + project.find_or_create_worktree_for_abs_path(&dir.path(), false, cx) }) .await .unwrap(); @@ -1733,6 +1734,126 @@ mod tests { assert!(results.is_empty()); } + #[gpui::test] + async fn test_definition(mut cx: gpui::TestAppContext) { + let (language_server_config, mut fake_server) = + LanguageServerConfig::fake(cx.background()).await; + + let mut languages = LanguageRegistry::new(); + languages.add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + language_server: Some(language_server_config), + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); + + let dir = temp_tree(json!({ + "a.rs": "const fn a() { A }", + "b.rs": "const y: i32 = crate::a()", + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let project = cx.update(|cx| { + Project::local( + client, + user_store, + Arc::new(languages), + Arc::new(RealFs), + cx, + ) + }); + + let (tree, _) = project + .update(&mut cx, |project, cx| { + project.find_or_create_worktree_for_abs_path(dir.path().join("b.rs"), false, cx) + }) + .await + .unwrap(); + let worktree_id = tree.read_with(&cx, |tree, _| tree.id()); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + // Cause worktree to start the fake language server + let buffer = project + .update(&mut cx, |project, cx| { + project.open_buffer( + ProjectPath { + worktree_id, + path: Path::new("").into(), + }, + cx, + ) + }) + .await + .unwrap(); + let definitions = + project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx)); + let (request_id, request) = fake_server + .receive_request::() + .await; + let request_params = request.text_document_position_params; + assert_eq!( + request_params.text_document.uri.to_file_path().unwrap(), + dir.path().join("b.rs") + ); + assert_eq!(request_params.position, lsp::Position::new(0, 22)); + + fake_server + .respond( + request_id, + Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new( + lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(), + lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + ))), + ) + .await; + let mut definitions = definitions.await.unwrap(); + assert_eq!(definitions.len(), 1); + let definition = definitions.pop().unwrap(); + cx.update(|cx| { + let target_buffer = definition.target_buffer.read(cx); + assert_eq!( + target_buffer.file().unwrap().abs_path(), + Some(dir.path().join("a.rs")) + ); + assert_eq!(definition.target_range.to_offset(target_buffer), 9..10); + assert_eq!( + list_worktrees(&project, cx), + [ + (dir.path().join("b.rs"), false), + (dir.path().join("a.rs"), true) + ] + ); + + drop(definition); + }); + cx.read(|cx| { + assert_eq!( + list_worktrees(&project, cx), + [(dir.path().join("b.rs"), false)] + ); + }); + + fn list_worktrees(project: &ModelHandle, cx: &AppContext) -> Vec<(PathBuf, bool)> { + project + .read(cx) + .worktrees(cx) + .map(|worktree| { + let worktree = worktree.read(cx); + ( + worktree.as_local().unwrap().abs_path().to_path_buf(), + worktree.is_weak(), + ) + }) + .collect::>() + } + } + fn build_project(cx: &mut TestAppContext) -> ModelHandle { let languages = Arc::new(LanguageRegistry::new()); let fs = Arc::new(RealFs); From 1d72e8faceb5878541f0b2795d2e6e5e31b5187d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 21 Jan 2022 07:31:02 -0700 Subject: [PATCH 29/31] Remove source_range from definition We don't use it now, and plan on dealing with it in a dedicated way when we need mouse hover interactions. Co-Authored-By: Antonio Scandurra --- crates/project/src/project.rs | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c396c455935f887eb5f5bd7fcf63984487538410..8502f2e9ca8d11dd0d9ec90b20c4bac673425c4a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -94,7 +94,6 @@ pub struct DiagnosticSummary { #[derive(Debug)] pub struct Definition { - pub source_range: Option>, pub target_buffer: ModelHandle, pub target_range: Range, } @@ -756,24 +755,21 @@ impl Project { let mut unresolved_locations = Vec::new(); match response { lsp::GotoDefinitionResponse::Scalar(loc) => { - unresolved_locations.push((None, loc.uri, loc.range)); + unresolved_locations.push((loc.uri, loc.range)); } lsp::GotoDefinitionResponse::Array(locs) => { - unresolved_locations - .extend(locs.into_iter().map(|l| (None, l.uri, l.range))); + unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range))); } lsp::GotoDefinitionResponse::Link(links) => { - unresolved_locations.extend(links.into_iter().map(|l| { - ( - l.origin_selection_range, - l.target_uri, - l.target_selection_range, - ) - })); + unresolved_locations.extend( + links + .into_iter() + .map(|l| (l.target_uri, l.target_selection_range)), + ); } } - for (source_range, target_uri, target_range) in unresolved_locations { + for (target_uri, target_range) in unresolved_locations { let abs_path = target_uri .to_file_path() .map_err(|_| anyhow!("invalid target path"))?; @@ -806,21 +802,12 @@ impl Project { .update(&mut cx, |this, cx| this.open_buffer(project_path, cx)) .await?; cx.read(|cx| { - let source_buffer = source_buffer_handle.read(cx); let target_buffer = target_buffer_handle.read(cx); - let source_range = source_range.map(|range| { - let start = source_buffer - .clip_point_utf16(range.start.to_point_utf16(), Bias::Left); - let end = source_buffer - .clip_point_utf16(range.end.to_point_utf16(), Bias::Left); - source_buffer.anchor_after(start)..source_buffer.anchor_before(end) - }); let target_start = target_buffer .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left); let target_end = target_buffer .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left); definitions.push(Definition { - source_range, target_buffer: target_buffer_handle, target_range: target_buffer.anchor_after(target_start) ..target_buffer.anchor_before(target_end), From a73671e57ca2ef0b99cde444aaac872cb5bac1dc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 21 Jan 2022 11:07:10 -0700 Subject: [PATCH 30/31] Revert "Replace `project_path` with `project_entry` in `workspace::{Item, ItemView}`" This reverts commit 9c9a09cccb41c4ea2a3d06b32170876e77946ba1. --- crates/diagnostics/src/diagnostics.rs | 4 +- crates/editor/src/items.rs | 25 ++++++---- crates/project/src/project.rs | 8 ---- crates/project/src/worktree.rs | 9 +--- crates/workspace/src/pane.rs | 30 ++++++------ crates/workspace/src/workspace.rs | 50 +++++++------------- crates/zed/src/zed.rs | 67 +++++++-------------------- 7 files changed, 67 insertions(+), 126 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 358cc0f39200555690cd870249b2bd315357326d..4abea30800cfb6c5d0abb88f41c5aa5139791032 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -539,7 +539,7 @@ impl workspace::Item for ProjectDiagnostics { diagnostics } - fn project_entry(&self) -> Option { + fn project_path(&self) -> Option { None } } @@ -555,7 +555,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { "Project Diagnostics".to_string() } - fn project_entry(&self, _: &AppContext) -> Option { + fn project_path(&self, _: &AppContext) -> Option { None } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 07caad356263556526b08f31c2437ca8ce8b22f3..dd4b1620272ec1976abe9322bda06b0176a1fcc0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -4,13 +4,12 @@ use gpui::{ elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; -use language::{Bias, Buffer, Diagnostic}; +use language::{Bias, Buffer, Diagnostic, File as _}; use postage::watch; -use project::worktree::File; -use project::{Project, ProjectEntry, ProjectPath}; -use std::cell::RefCell; +use project::{File, Project, ProjectPath}; +use std::path::PathBuf; use std::rc::Rc; -use std::{fmt::Write, path::PathBuf}; +use std::{cell::RefCell, fmt::Write}; use text::{Point, Selection}; use util::TryFutureExt; use workspace::{ @@ -75,8 +74,11 @@ impl ItemHandle for BufferItemHandle { Box::new(WeakBufferItemHandle(self.0.downgrade())) } - fn project_entry(&self, cx: &AppContext) -> Option { - File::from_dyn(self.0.read(cx).file()).and_then(|f| f.project_entry(cx)) + fn project_path(&self, cx: &AppContext) -> Option { + File::from_dyn(self.0.read(cx).file()).map(|f| ProjectPath { + worktree_id: f.worktree_id(cx), + path: f.path().clone(), + }) } fn id(&self) -> usize { @@ -132,8 +134,11 @@ impl ItemView for Editor { } } - fn project_entry(&self, cx: &AppContext) -> Option { - File::from_dyn(self.buffer().read(cx).file(cx)).and_then(|file| file.project_entry(cx)) + fn project_path(&self, cx: &AppContext) -> Option { + File::from_dyn(self.buffer().read(cx).file(cx)).map(|file| ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path().clone(), + }) } fn clone_on_split(&self, cx: &mut ViewContext) -> Option @@ -158,7 +163,7 @@ impl ItemView for Editor { } fn can_save(&self, cx: &AppContext) -> bool { - self.project_entry(cx).is_some() + self.project_path(cx).is_some() } fn save(&mut self, cx: &mut ViewContext) -> Task> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8502f2e9ca8d11dd0d9ec90b20c4bac673425c4a..5891a1c9bccad8b0bbdc1d6eb1311d75ca48813b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -959,14 +959,6 @@ impl Project { } } - pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option { - let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx); - Some(ProjectPath { - worktree_id: entry.worktree_id, - path: worktree.entry_for_id(entry.entry_id)?.path.clone(), - }) - } - pub fn is_running_disk_based_diagnostics(&self) -> bool { self.language_servers_with_diagnostics_running > 0 } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9b9a161b1a66160a5d438da3a7b434c424a44420..e0a54e45ed776e9cbdb0743e6254c0fa714a0d97 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1,7 +1,7 @@ use super::{ fs::{self, Fs}, ignore::IgnoreStack, - DiagnosticSummary, ProjectEntry, + DiagnosticSummary, }; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Result}; @@ -2202,13 +2202,6 @@ impl File { pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId { self.worktree.read(cx).id() } - - pub fn project_entry(&self, cx: &AppContext) -> Option { - self.entry_id.map(|entry_id| ProjectEntry { - worktree_id: self.worktree_id(cx), - entry_id, - }) - } } #[derive(Clone, Debug)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index dabf816d027572c5c0d7efea7ca02017a80d96ec..17f370ac4fa574dc3c088b46191bd19942089f31 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -10,7 +10,7 @@ use gpui::{ Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext, ViewHandle, }; use postage::watch; -use project::ProjectEntry; +use project::ProjectPath; use std::{any::Any, cell::RefCell, cmp, mem, rc::Rc}; use util::ResultExt; @@ -89,7 +89,7 @@ pub struct NavHistory { mode: NavigationMode, backward_stack: VecDeque, forward_stack: VecDeque, - project_entries_by_item: HashMap, + paths_by_item: HashMap, } #[derive(Copy, Clone)] @@ -150,10 +150,10 @@ impl Pane { ) -> Task<()> { let to_load = pane.update(cx, |pane, cx| { // Retrieve the weak item handle from the history. - let nav_entry = pane.nav_history.borrow_mut().pop(mode)?; + let entry = pane.nav_history.borrow_mut().pop(mode)?; // If the item is still present in this pane, then activate it. - if let Some(index) = nav_entry + if let Some(index) = entry .item_view .upgrade(cx) .and_then(|v| pane.index_for_item_view(v.as_ref())) @@ -168,7 +168,7 @@ impl Pane { pane.active_item_index = index; pane.focus_active_item(cx); - if let Some(data) = nav_entry.data { + if let Some(data) = entry.data { pane.active_item()?.navigate(data, cx); } cx.notify(); @@ -179,17 +179,17 @@ impl Pane { else { pane.nav_history .borrow_mut() - .project_entries_by_item - .get(&nav_entry.item_view.id()) + .paths_by_item + .get(&entry.item_view.id()) .cloned() - .map(|project_entry| (project_entry, nav_entry)) + .map(|project_path| (project_path, entry)) } }); - if let Some((project_entry, nav_entry)) = to_load { + if let Some((project_path, entry)) = to_load { // If the item was no longer present, then load it again from its previous path. let pane = pane.downgrade(); - let task = workspace.load_entry(project_entry, cx); + let task = workspace.load_path(project_path, cx); cx.spawn(|workspace, mut cx| async move { let item = task.await; if let Some(pane) = cx.read(|cx| pane.upgrade(cx)) { @@ -201,7 +201,7 @@ impl Pane { p.nav_history.borrow_mut().set_mode(NavigationMode::Normal) }); - if let Some(data) = nav_entry.data { + if let Some(data) = entry.data { item_view.navigate(data, cx); } }); @@ -328,12 +328,10 @@ impl Pane { } let mut nav_history = self.nav_history.borrow_mut(); - if let Some(entry) = item_view.project_entry(cx) { - nav_history - .project_entries_by_item - .insert(item_view.id(), entry); + if let Some(path) = item_view.project_path(cx) { + nav_history.paths_by_item.insert(item_view.id(), path); } else { - nav_history.project_entries_by_item.remove(&item_view.id()); + nav_history.paths_by_item.remove(&item_view.id()); } item_ix += 1; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ebd29e69e7555953bfb8e00c69261be843f61a4c..0f0f2a77fb5c2a10b3e07139db228d6244ee797d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -27,7 +27,7 @@ pub use pane::*; pub use pane_group::*; use parking_lot::Mutex; use postage::{prelude::Stream, watch}; -use project::{fs, Fs, Project, ProjectEntry, ProjectPath, Worktree}; +use project::{fs, Fs, Project, ProjectPath, Worktree}; pub use settings::Settings; use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}; use status_bar::StatusBar; @@ -145,7 +145,8 @@ pub trait Item: Entity + Sized { nav_history: ItemNavHistory, cx: &mut ViewContext, ) -> Self::View; - fn project_entry(&self) -> Option; + + fn project_path(&self) -> Option; } pub trait ItemView: View { @@ -155,7 +156,7 @@ pub trait ItemView: View { fn navigate(&mut self, _: Box, _: &mut ViewContext) {} fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn title(&self, cx: &AppContext) -> String; - fn project_entry(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; fn clone_on_split(&self, _: &mut ViewContext) -> Option where Self: Sized, @@ -212,7 +213,7 @@ pub trait ItemHandle: Send + Sync { fn boxed_clone(&self) -> Box; fn downgrade(&self) -> Box; fn to_any(&self) -> AnyModelHandle; - fn project_entry(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; } pub trait WeakItemHandle { @@ -223,7 +224,7 @@ pub trait WeakItemHandle { pub trait ItemViewHandle: 'static { fn item_handle(&self, cx: &AppContext) -> Box; fn title(&self, cx: &AppContext) -> String; - fn project_entry(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; fn added_to_pane(&mut self, cx: &mut ViewContext); @@ -280,8 +281,8 @@ impl ItemHandle for ModelHandle { self.clone().into() } - fn project_entry(&self, cx: &AppContext) -> Option { - self.read(cx).project_entry() + fn project_path(&self, cx: &AppContext) -> Option { + self.read(cx).project_path() } } @@ -312,8 +313,8 @@ impl ItemHandle for Box { self.as_ref().to_any() } - fn project_entry(&self, cx: &AppContext) -> Option { - self.as_ref().project_entry(cx) + fn project_path(&self, cx: &AppContext) -> Option { + self.as_ref().project_path(cx) } } @@ -361,8 +362,8 @@ impl ItemViewHandle for ViewHandle { self.read(cx).title(cx) } - fn project_entry(&self, cx: &AppContext) -> Option { - self.read(cx).project_entry(cx) + fn project_path(&self, cx: &AppContext) -> Option { + self.read(cx).project_path(cx) } fn boxed_clone(&self) -> Box { @@ -778,18 +779,6 @@ impl Workspace { }) } - pub fn load_entry( - &mut self, - path: ProjectEntry, - cx: &mut ViewContext, - ) -> Task>> { - if let Some(path) = self.project.read(cx).path_for_entry(path, cx) { - self.load_path(path, cx) - } else { - Task::ready(Err(anyhow!("entry does not exist"))) - } - } - pub fn load_path( &mut self, path: ProjectPath, @@ -812,13 +801,10 @@ impl Workspace { } fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option> { - let project = self.project.read(cx); - self.items.iter().filter_map(|i| i.upgrade(cx)).find(|i| { - let item_path = i - .project_entry(cx) - .and_then(|entry| project.path_for_entry(entry, cx)); - item_path.as_ref() == Some(path) - }) + self.items + .iter() + .filter_map(|i| i.upgrade(cx)) + .find(|i| i.project_path(cx).as_ref() == Some(path)) } pub fn item_of_type(&self, cx: &AppContext) -> Option> { @@ -832,9 +818,7 @@ impl Workspace { } fn active_project_path(&self, cx: &ViewContext) -> Option { - self.active_item(cx) - .and_then(|item| item.project_entry(cx)) - .and_then(|entry| self.project.read(cx).path_for_entry(entry, cx)) + self.active_item(cx).and_then(|item| item.project_path(cx)) } pub fn save_active_item(&mut self, cx: &mut ViewContext) -> Task> { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e636cced9334b4404b5fbd73c70be8d874d8101c..5f5422d25671708138fcc7ee697f4fd0203bfdd0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -263,11 +263,9 @@ mod tests { .await .unwrap(); cx.read(|cx| { - let workspace = workspace.read(cx); - let pane = workspace.active_pane().read(cx); - let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); + let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( - workspace.project().read(cx).path_for_entry(entry, cx), + pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); assert_eq!(pane.item_views().count(), 1); @@ -279,11 +277,9 @@ mod tests { .await .unwrap(); cx.read(|cx| { - let workspace = workspace.read(cx); - let pane = workspace.active_pane().read(cx); - let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); + let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( - workspace.project().read(cx).path_for_entry(entry, cx), + pane.active_item().unwrap().project_path(cx), Some(file2.clone()) ); assert_eq!(pane.item_views().count(), 2); @@ -297,11 +293,9 @@ mod tests { assert_eq!(entry_1.id(), entry_1b.id()); cx.read(|cx| { - let workspace = workspace.read(cx); - let pane = workspace.active_pane().read(cx); - let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); + let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( - workspace.project().read(cx).path_for_entry(entry, cx), + pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); assert_eq!(pane.item_views().count(), 2); @@ -316,11 +310,13 @@ mod tests { .await .unwrap(); - workspace.read_with(&cx, |workspace, cx| { - let pane = workspace.active_pane().read(cx); - let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); + workspace.read_with(&cx, |w, cx| { assert_eq!( - workspace.project().read(cx).path_for_entry(entry, cx), + w.active_pane() + .read(cx) + .active_item() + .unwrap() + .project_path(cx.as_ref()), Some(file2.clone()) ); }); @@ -335,22 +331,14 @@ mod tests { t1.await.unwrap(); t2.await.unwrap(); cx.read(|cx| { - let workspace = workspace.read(cx); - let pane = workspace.active_pane().read(cx); - let entry = pane.active_item().unwrap().project_entry(cx).unwrap(); + let pane = workspace.read(cx).active_pane().read(cx); assert_eq!( - workspace.project().read(cx).path_for_entry(entry, cx), + pane.active_item().unwrap().project_path(cx), Some(file3.clone()) ); let pane_entries = pane .item_views() - .map(|i| { - workspace - .project() - .read(cx) - .path_for_entry(i.project_entry(cx).unwrap(), cx) - .unwrap() - }) + .map(|i| i.project_path(cx).unwrap()) .collect::>(); assert_eq!(pane_entries, &[file1, file2, file3]); }); @@ -654,15 +642,8 @@ mod tests { .await .unwrap(); cx.read(|cx| { - let workspace = workspace.read(cx); - let pane1_entry = pane_1 - .read(cx) - .active_item() - .unwrap() - .project_entry(cx) - .unwrap(); assert_eq!( - workspace.project().read(cx).path_for_entry(pane1_entry, cx), + pane_1.read(cx).active_item().unwrap().project_path(cx), Some(file1.clone()) ); }); @@ -677,15 +658,7 @@ mod tests { assert_ne!(pane_1, pane_2); let pane2_item = pane_2.read(cx).active_item().unwrap(); - let pane2_entry = pane2_item.project_entry(cx).unwrap(); - assert_eq!( - workspace - .read(cx) - .project() - .read(cx) - .path_for_entry(pane2_entry, cx), - Some(file1.clone()) - ); + assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone())); cx.dispatch_action(window_id, vec![pane_2.id()], &workspace::CloseActiveItem); let workspace = workspace.read(cx); @@ -863,11 +836,7 @@ mod tests { let item = workspace.active_item(cx).unwrap(); let editor = item.downcast::().unwrap(); let selections = editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)); - let path = workspace - .project() - .read(cx) - .path_for_entry(item.project_entry(cx).unwrap(), cx); - (path.unwrap(), selections[0].start) + (item.project_path(cx).unwrap(), selections[0].start) }) } } From 3ecb7e81f1960cfc23cf9ca253b6fb11e7c94f29 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 21 Jan 2022 11:24:05 -0700 Subject: [PATCH 31/31] Remove panic when guest attempts to go to definition We'll implement this soon but want to merge something stable to main. Co-Authored-By: Max Brunsfeld --- crates/project/src/project.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5891a1c9bccad8b0bbdc1d6eb1311d75ca48813b..f01a875011ef29e733a71815eb056816e3caa62c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -819,7 +819,8 @@ impl Project { Ok(definitions) }) } else { - todo!() + log::info!("go to definition is not yet implemented for guests"); + Task::ready(Ok(Default::default())) } }