From a88320dc5f10a9b09ebbb7bc6d1ec02ec7ad4f76 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 16 Mar 2022 13:34:04 -0700 Subject: [PATCH] Remove workspace::Item trait Co-Authored-By: Nathan Sobo Co-Authored-By: Keith Simmons Co-Authored-By: Antonio Scandurra --- crates/diagnostics/src/diagnostics.rs | 103 +++---- crates/editor/src/editor.rs | 88 ++++-- crates/editor/src/items.rs | 151 ++-------- crates/gpui/src/app.rs | 14 + crates/project/src/project.rs | 10 + crates/project/src/worktree.rs | 9 + crates/project_symbols/src/project_symbols.rs | 10 +- crates/search/src/project_search.rs | 76 ++--- crates/workspace/src/pane.rs | 53 ++-- crates/workspace/src/workspace.rs | 284 +++++------------- 10 files changed, 281 insertions(+), 517 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 1a432f52e2..1db19f7f5e 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -25,7 +25,7 @@ use std::{ sync::Arc, }; use util::TryFutureExt; -use workspace::{ItemHandle, ItemNavHistory, ItemViewHandle as _, Settings, Workspace}; +use workspace::{ItemNavHistory, ItemViewHandle as _, Settings, Workspace}; action!(Deploy); @@ -38,12 +38,8 @@ pub fn init(cx: &mut MutableAppContext) { type Event = editor::Event; -struct ProjectDiagnostics { - project: ModelHandle, -} - struct ProjectDiagnosticsEditor { - model: ModelHandle, + project: ModelHandle, workspace: WeakViewHandle, editor: ViewHandle, summary: DiagnosticSummary, @@ -65,16 +61,6 @@ struct DiagnosticGroupState { block_count: usize, } -impl ProjectDiagnostics { - fn new(project: ModelHandle) -> Self { - Self { project } - } -} - -impl Entity for ProjectDiagnostics { - type Event = (); -} - impl Entity for ProjectDiagnosticsEditor { type Event = Event; } @@ -109,12 +95,11 @@ impl View for ProjectDiagnosticsEditor { impl ProjectDiagnosticsEditor { fn new( - model: ModelHandle, + project_handle: ModelHandle, workspace: WeakViewHandle, cx: &mut ViewContext, ) -> Self { - let project = model.read(cx).project.clone(); - cx.subscribe(&project, |this, _, event, cx| match event { + cx.subscribe(&project_handle, |this, _, event, cx| match event { project::Event::DiskBasedDiagnosticsFinished => { this.update_excerpts(cx); this.update_title(cx); @@ -126,20 +111,21 @@ impl ProjectDiagnosticsEditor { }) .detach(); - let excerpts = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id())); + let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id())); let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(excerpts.clone(), Some(project.clone()), cx); + let mut editor = Editor::for_buffer(excerpts.clone(), Some(project_handle.clone()), cx); editor.set_vertical_scroll_margin(5, cx); editor }); cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event)) .detach(); - let project = project.read(cx); + let project = project_handle.read(cx); let paths_to_update = project.diagnostic_summaries(cx).map(|e| e.0).collect(); + let summary = project.diagnostic_summary(cx); let mut this = Self { - model, - summary: project.diagnostic_summary(cx), + project: project_handle, + summary, workspace, excerpts, editor, @@ -151,18 +137,20 @@ impl ProjectDiagnosticsEditor { } fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { - if let Some(existing) = workspace.item_of_type::(cx) { + if let Some(existing) = workspace.item_of_type::(cx) { workspace.activate_item(&existing, cx); } else { - let diagnostics = - cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); - workspace.open_item(diagnostics, cx); + let workspace_handle = cx.weak_handle(); + let diagnostics = cx.add_view(|cx| { + ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx) + }); + workspace.open_item(Box::new(diagnostics), cx); } } fn update_excerpts(&mut self, cx: &mut ViewContext) { let paths = mem::take(&mut self.paths_to_update); - let project = self.model.read(cx).project.clone(); + let project = self.project.clone(); cx.spawn(|this, mut cx| { async move { for path in paths { @@ -443,37 +431,12 @@ impl ProjectDiagnosticsEditor { } fn update_title(&mut self, cx: &mut ViewContext) { - self.summary = self.model.read(cx).project.read(cx).diagnostic_summary(cx); + self.summary = self.project.read(cx).diagnostic_summary(cx); cx.emit(Event::TitleChanged); } } -impl workspace::Item for ProjectDiagnostics { - type View = ProjectDiagnosticsEditor; - - fn build_view( - handle: ModelHandle, - workspace: &Workspace, - nav_history: ItemNavHistory, - cx: &mut ViewContext, - ) -> Self::View { - let diagnostics = ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), cx); - diagnostics - .editor - .update(cx, |editor, _| editor.set_nav_history(Some(nav_history))); - diagnostics - } - - fn project_path(&self) -> Option { - None - } -} - impl workspace::ItemView for ProjectDiagnosticsEditor { - fn item(&self, _: &AppContext) -> Box { - Box::new(self.model.clone()) - } - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { render_summary( &self.summary, @@ -486,6 +449,10 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { None } + fn project_entry(&self, _: &AppContext) -> Option { + None + } + fn navigate(&mut self, data: Box, cx: &mut ViewContext) { self.editor .update(cx, |editor, cx| editor.navigate(data, cx)); @@ -532,20 +499,21 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { matches!(event, Event::Saved | Event::Dirtied | Event::TitleChanged) } - fn clone_on_split( - &self, - nav_history: ItemNavHistory, - cx: &mut ViewContext, - ) -> Option + fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext) { + self.editor.update(cx, |editor, _| { + editor.set_nav_history(Some(nav_history)); + }); + } + + fn clone_on_split(&self, cx: &mut ViewContext) -> Option where Self: Sized, { - let diagnostics = - ProjectDiagnosticsEditor::new(self.model.clone(), self.workspace.clone(), cx); - diagnostics.editor.update(cx, |editor, _| { - editor.set_nav_history(Some(nav_history)); - }); - Some(diagnostics) + Some(ProjectDiagnosticsEditor::new( + self.project.clone(), + self.workspace.clone(), + cx, + )) } fn act_as_type( @@ -829,9 +797,8 @@ mod tests { }); // Open the project diagnostics view while there are already diagnostics. - let model = cx.add_model(|_| ProjectDiagnostics::new(project.clone())); let view = cx.add_view(0, |cx| { - ProjectDiagnosticsEditor::new(model, workspace.downgrade(), cx) + ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx) }); view.next_notification(&cx).await; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8c6904d136..6a29859195 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -28,7 +28,6 @@ use gpui::{ ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use items::{BufferItemHandle, MultiBufferItemHandle}; use itertools::Itertools as _; pub use language::{char_kind, CharKind}; use language::{ @@ -836,7 +835,32 @@ impl Editor { Self::new(EditorMode::Full, buffer, project, None, cx) } - pub fn clone(&self, nav_history: ItemNavHistory, cx: &mut ViewContext) -> Self { + pub fn find_or_create( + workspace: &mut Workspace, + buffer: ModelHandle, + cx: &mut ViewContext, + ) -> ViewHandle { + let project = workspace.project().clone(); + + if let Some(project_entry) = + project::File::from_dyn(buffer.read(cx).file()).and_then(|file| file.project_entry(cx)) + { + return workspace + .open_item_for_project_entry(project_entry, cx, |cx| { + let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + Editor::for_buffer(multibuffer, Some(project.clone()), cx) + }) + .downcast::() + .unwrap(); + } + + let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + let editor = cx.add_view(|cx| Editor::for_buffer(multibuffer, Some(project.clone()), cx)); + workspace.open_item(Box::new(editor.clone()), cx); + editor + } + + pub fn clone(&self, cx: &mut ViewContext) -> Self { let mut clone = Self::new( self.mode, self.buffer.clone(), @@ -846,7 +870,6 @@ impl Editor { ); clone.scroll_position = self.scroll_position; clone.scroll_top_anchor = self.scroll_top_anchor.clone(); - clone.nav_history = Some(nav_history); clone.searchable = self.searchable; clone } @@ -938,14 +961,20 @@ impl Editor { _: &workspace::OpenNew, cx: &mut ViewContext, ) { - let project = workspace.project(); + let project = workspace.project().clone(); if project.read(cx).is_remote() { cx.propagate_action(); } else if let Some(buffer) = project .update(cx, |project, cx| project.create_buffer(cx)) .log_err() { - workspace.open_item(BufferItemHandle(buffer), cx); + let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + workspace.open_item( + Box::new( + cx.add_view(|cx| Editor::for_buffer(multibuffer, Some(project.clone()), cx)), + ), + cx, + ); } } @@ -2349,7 +2378,11 @@ impl Editor { }); workspace.update(&mut cx, |workspace, cx| { - let editor = workspace.open_item(MultiBufferItemHandle(excerpt_buffer), cx); + let project = workspace.project().clone(); + let editor = workspace.open_item( + Box::new(cx.add_view(|cx| Editor::for_buffer(excerpt_buffer, Some(project), cx))), + cx, + ); if let Some(editor) = editor.act_as::(cx) { editor.update(cx, |editor, cx| { let color = editor.style(cx).highlighted_line_background; @@ -4280,20 +4313,17 @@ impl Editor { return; }; - let definitions = workspace - .project() - .update(cx, |project, cx| project.definition(&buffer, head, cx)); + let project = workspace.project().clone(); + let definitions = 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| { let nav_history = workspace.active_pane().read(cx).nav_history().clone(); for definition in definitions { let range = definition.range.to_offset(definition.buffer.read(cx)); - let target_editor_handle = workspace - .open_item(BufferItemHandle(definition.buffer), cx) - .downcast::() - .unwrap(); + let target_editor_handle = + Self::find_or_create(workspace, definition.buffer, 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. @@ -4324,9 +4354,8 @@ impl Editor { let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx)?; let replica_id = editor.replica_id(cx); - let references = workspace - .project() - .update(cx, |project, cx| project.references(&buffer, head, cx)); + let project = workspace.project().clone(); + let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); Some(cx.spawn(|workspace, mut cx| async move { let mut locations = references.await?; if locations.is_empty() { @@ -4370,13 +4399,13 @@ impl Editor { }); workspace.update(&mut cx, |workspace, cx| { - let editor = workspace.open_item(MultiBufferItemHandle(excerpt_buffer), cx); - if let Some(editor) = editor.act_as::(cx) { - editor.update(cx, |editor, cx| { - let color = editor.style(cx).highlighted_line_background; - editor.highlight_background::(ranges_to_highlight, color, cx); - }); - } + let editor = + cx.add_view(|cx| Editor::for_buffer(excerpt_buffer, Some(project), cx)); + editor.update(cx, |editor, cx| { + let color = editor.style(cx).highlighted_line_background; + editor.highlight_background::(ranges_to_highlight, color, cx); + }); + workspace.open_item(Box::new(editor), cx); }); Ok(()) @@ -5563,17 +5592,10 @@ impl Editor { // and activating a new item causes the pane to call a method on us reentrantly, // which panics if we're on the stack. cx.defer(move |workspace, cx| { - for (ix, (buffer, ranges)) in new_selections_by_buffer.into_iter().enumerate() { - let buffer = BufferItemHandle(buffer); - if ix == 0 && !workspace.activate_pane_for_item(&buffer, cx) { - workspace.activate_next_pane(cx); - } - - let editor = workspace - .open_item(buffer, cx) - .downcast::() - .unwrap(); + workspace.activate_next_pane(cx); + for (buffer, ranges) in new_selections_by_buffer.into_iter() { + let editor = Self::find_or_create(workspace, buffer, cx); editor.update(cx, |editor, cx| { editor.select_ranges(ranges, Some(Autoscroll::Newest), cx); }); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index c9af9f0854..b84a095e6b 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,20 +1,16 @@ use crate::{Autoscroll, Editor, Event, MultiBuffer, NavigationData, ToOffset, ToPoint as _}; use anyhow::Result; use gpui::{ - elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, - Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, + elements::*, AppContext, Entity, ModelContext, ModelHandle, RenderContext, Subscription, Task, + View, ViewContext, ViewHandle, WeakModelHandle, }; use language::{Bias, Buffer, Diagnostic, File as _}; -use project::{File, Project, ProjectPath}; +use project::{File, Project, ProjectEntry, ProjectPath}; +use std::fmt::Write; use std::path::PathBuf; -use std::rc::Rc; -use std::{cell::RefCell, fmt::Write}; use text::{Point, Selection}; use util::ResultExt; -use workspace::{ - ItemHandle, ItemNavHistory, ItemView, ItemViewHandle, NavHistory, PathOpener, Settings, - StatusItemView, WeakItemHandle, Workspace, -}; +use workspace::{ItemNavHistory, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView}; pub struct BufferOpener; @@ -35,127 +31,22 @@ impl PathOpener for BufferOpener { &self, project: &mut Project, project_path: ProjectPath, + window_id: usize, cx: &mut ModelContext, - ) -> Option>>> { + ) -> Option>>> { let buffer = project.open_buffer(project_path, cx); - let task = cx.spawn(|_, _| async move { + Some(cx.spawn(|project, mut cx| async move { let buffer = buffer.await?; - Ok(Box::new(BufferItemHandle(buffer)) as Box) - }); - Some(task) - } -} - -impl ItemHandle for BufferItemHandle { - fn add_view( - &self, - window_id: usize, - workspace: &Workspace, - nav_history: Rc>, - cx: &mut MutableAppContext, - ) -> Box { - let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); - Box::new(cx.add_view(window_id, |cx| { - let mut editor = Editor::for_buffer(buffer, Some(workspace.project().clone()), cx); - editor.nav_history = Some(ItemNavHistory::new(nav_history, &cx.handle())); - editor + let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + let editor = cx.add_view(window_id, |cx| { + Editor::for_buffer(multibuffer, Some(project), cx) + }); + Ok(Box::new(editor) as Box) })) } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } - - fn to_any(&self) -> gpui::AnyModelHandle { - self.0.clone().into() - } - - fn downgrade(&self) -> Box { - 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 id(&self) -> usize { - self.0.id() - } -} - -impl ItemHandle for MultiBufferItemHandle { - fn add_view( - &self, - window_id: usize, - workspace: &Workspace, - nav_history: Rc>, - cx: &mut MutableAppContext, - ) -> Box { - Box::new(cx.add_view(window_id, |cx| { - let mut editor = - Editor::for_buffer(self.0.clone(), Some(workspace.project().clone()), cx); - editor.nav_history = Some(ItemNavHistory::new(nav_history, &cx.handle())); - editor - })) - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } - - fn to_any(&self) -> gpui::AnyModelHandle { - self.0.clone().into() - } - - fn downgrade(&self) -> Box { - Box::new(WeakMultiBufferItemHandle(self.0.downgrade())) - } - - fn project_path(&self, _: &AppContext) -> Option { - None - } - - fn id(&self) -> usize { - self.0.id() - } -} - -impl WeakItemHandle for WeakBufferItemHandle { - fn upgrade(&self, cx: &AppContext) -> Option> { - self.0 - .upgrade(cx) - .map(|buffer| Box::new(BufferItemHandle(buffer)) as Box) - } - - fn id(&self) -> usize { - self.0.id() - } -} - -impl WeakItemHandle for WeakMultiBufferItemHandle { - fn upgrade(&self, cx: &AppContext) -> Option> { - self.0 - .upgrade(cx) - .map(|buffer| Box::new(MultiBufferItemHandle(buffer)) as Box) - } - - fn id(&self) -> usize { - self.0.id() - } } impl ItemView for Editor { - fn item(&self, cx: &AppContext) -> Box { - if let Some(buffer) = self.buffer.read(cx).as_singleton() { - Box::new(BufferItemHandle(buffer)) - } else { - Box::new(MultiBufferItemHandle(self.buffer.clone())) - } - } - fn navigate(&mut self, data: Box, cx: &mut ViewContext) { if let Some(data) = data.downcast_ref::() { let buffer = self.buffer.read(cx).read(cx); @@ -184,15 +75,19 @@ impl ItemView for Editor { }) } - fn clone_on_split( - &self, - nav_history: ItemNavHistory, - cx: &mut ViewContext, - ) -> Option + 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 where Self: Sized, { - Some(self.clone(nav_history, cx)) + Some(self.clone(cx)) + } + + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); } fn deactivated(&mut self, cx: &mut ViewContext) { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a03e6e54ae..a479e5fba1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -595,6 +595,14 @@ impl AsyncAppContext { self.update(|cx| cx.add_model(build_model)) } + pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + self.update(|cx| cx.add_view(window_id, build_view)) + } + pub fn platform(&self) -> Arc { self.0.borrow().platform() } @@ -3459,6 +3467,12 @@ impl PartialEq for ViewHandle { } } +impl PartialEq> for ViewHandle { + fn eq(&self, other: &WeakViewHandle) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl Eq for ViewHandle {} impl Debug for ViewHandle { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f5af7fc70e..b9bb25a5fc 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3221,6 +3221,16 @@ impl Project { self.active_entry } + pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option { + self.worktree_for_id(path.worktree_id, cx)? + .read(cx) + .entry_for_path(&path.path) + .map(|entry| ProjectEntry { + worktree_id: path.worktree_id, + entry_id: entry.id, + }) + } + // RPC message handlers async fn handle_unshare_project( diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 1ef8dd34a0..4a121c82e4 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1,3 +1,5 @@ +use crate::ProjectEntry; + use super::{ fs::{self, Fs}, ignore::IgnoreStack, @@ -1502,6 +1504,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, PartialEq, Eq)] diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index bfd204671f..29808ff7d9 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,6 +1,5 @@ use editor::{ - combine_syntax_and_fuzzy_match_highlights, items::BufferItemHandle, styled_runs_for_code_label, - Autoscroll, Bias, Editor, + combine_syntax_and_fuzzy_match_highlights, styled_runs_for_code_label, Autoscroll, Bias, Editor, }; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ @@ -346,6 +345,7 @@ impl ProjectSymbolsView { let buffer = workspace .project() .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx)); + let symbol = symbol.clone(); cx.spawn(|workspace, mut cx| async move { let buffer = buffer.await?; @@ -353,10 +353,8 @@ impl ProjectSymbolsView { let position = buffer .read(cx) .clip_point_utf16(symbol.range.start, Bias::Left); - let editor = workspace - .open_item(BufferItemHandle(buffer), cx) - .downcast::() - .unwrap(); + + let editor = Editor::find_or_create(workspace, buffer, cx); editor.update(cx, |editor, cx| { editor.select_ranges( [position..position], diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b09c88a4a0..56395b107a 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,7 +7,7 @@ use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll}; use gpui::{ action, elements::*, keymap::Binding, platform::CursorStyle, AppContext, ElementBox, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, - ViewHandle, WeakModelHandle, + ViewHandle, WeakModelHandle, WeakViewHandle, }; use project::{search::SearchQuery, Project}; use std::{ @@ -16,7 +16,7 @@ use std::{ path::PathBuf, }; use util::ResultExt as _; -use workspace::{Item, ItemHandle, ItemNavHistory, ItemView, Settings, Workspace}; +use workspace::{ItemNavHistory, ItemView, Settings, Workspace}; action!(Deploy); action!(Search); @@ -26,7 +26,7 @@ action!(ToggleFocus); const MAX_TAB_TITLE_LEN: usize = 24; #[derive(Default)] -struct ActiveSearches(HashMap, WeakModelHandle>); +struct ActiveSearches(HashMap, WeakViewHandle>); pub fn init(cx: &mut MutableAppContext) { cx.add_app_state(ActiveSearches::default()); @@ -139,23 +139,6 @@ impl ProjectSearch { } } -impl Item for ProjectSearch { - type View = ProjectSearchView; - - fn build_view( - model: ModelHandle, - _: &Workspace, - nav_history: ItemNavHistory, - cx: &mut gpui::ViewContext, - ) -> Self::View { - ProjectSearchView::new(model, Some(nav_history), cx) - } - - fn project_path(&self) -> Option { - None - } -} - enum ViewEvent { UpdateTab, } @@ -199,11 +182,11 @@ impl View for ProjectSearchView { } fn on_focus(&mut self, cx: &mut ViewContext) { + let handle = cx.weak_handle(); cx.update_app_state(|state: &mut ActiveSearches, cx| { - state.0.insert( - self.model.read(cx).project.downgrade(), - self.model.downgrade(), - ) + state + .0 + .insert(self.model.read(cx).project.downgrade(), handle) }); if self.model.read(cx).match_ranges.is_empty() { @@ -235,10 +218,6 @@ impl ItemView for ProjectSearchView { .update(cx, |editor, cx| editor.deactivated(cx)); } - fn item(&self, _: &gpui::AppContext) -> Box { - Box::new(self.model.clone()) - } - fn tab_content(&self, tab_theme: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { let settings = cx.app_state::(); let search_theme = &settings.theme.search; @@ -271,6 +250,10 @@ impl ItemView for ProjectSearchView { None } + fn project_entry(&self, _: &AppContext) -> Option { + None + } + fn can_save(&self, _: &gpui::AppContext) -> bool { true } @@ -305,16 +288,18 @@ impl ItemView for ProjectSearchView { unreachable!("save_as should not have been called") } - fn clone_on_split( - &self, - nav_history: ItemNavHistory, - cx: &mut ViewContext, - ) -> Option + fn clone_on_split(&self, cx: &mut ViewContext) -> Option where Self: Sized, { let model = self.model.update(cx, |model, cx| model.clone(cx)); - Some(Self::new(model, Some(nav_history), cx)) + Some(Self::new(model, cx)) + } + + fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext) { + self.results_editor.update(cx, |editor, _| { + editor.set_nav_history(Some(nav_history)); + }); } fn navigate(&mut self, data: Box, cx: &mut ViewContext) { @@ -328,11 +313,7 @@ impl ItemView for ProjectSearchView { } impl ProjectSearchView { - fn new( - model: ModelHandle, - nav_history: Option, - cx: &mut ViewContext, - ) -> Self { + fn new(model: ModelHandle, cx: &mut ViewContext) -> Self { let project; let excerpts; let mut query_text = String::new(); @@ -364,7 +345,6 @@ impl ProjectSearchView { let results_editor = cx.add_view(|cx| { let mut editor = Editor::for_buffer(excerpts, Some(project), cx); editor.set_searchable(false); - editor.set_nav_history(nav_history); editor }); cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)) @@ -406,16 +386,19 @@ impl ProjectSearchView { let existing = active_search .and_then(|active_search| { workspace - .items_of_type::(cx) + .items_of_type::(cx) .find(|search| search == active_search) }) - .or_else(|| workspace.item_of_type::(cx)); + .or_else(|| workspace.item_of_type::(cx)); if let Some(existing) = existing { workspace.activate_item(&existing, cx); } else { let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); - workspace.open_item(model, cx); + workspace.open_item( + Box::new(cx.add_view(|cx| ProjectSearchView::new(model, cx))), + cx, + ); } } @@ -450,7 +433,10 @@ impl ProjectSearchView { model.search(new_query, cx); model }); - workspace.open_item(model, cx); + workspace.open_item( + Box::new(cx.add_view(|cx| ProjectSearchView::new(model, cx))), + cx, + ); } } } @@ -732,7 +718,7 @@ mod tests { let search = cx.add_model(|cx| ProjectSearch::new(project, cx)); let search_view = cx.add_view(Default::default(), |cx| { - ProjectSearchView::new(search.clone(), None, cx) + ProjectSearchView::new(search.clone(), cx) }); search_view.update(cx, |search_view, cx| { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c54a1b050b..9f5ee52274 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,5 +1,5 @@ use super::{ItemViewHandle, SplitDirection}; -use crate::{ItemHandle, ItemView, Settings, WeakItemViewHandle, Workspace}; +use crate::{ItemView, Settings, WeakItemViewHandle, Workspace}; use collections::{HashMap, VecDeque}; use gpui::{ action, @@ -10,7 +10,7 @@ use gpui::{ AnyViewHandle, Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::ProjectPath; +use project::{ProjectEntry, ProjectPath}; use std::{ any::{Any, TypeId}, cell::RefCell, @@ -97,7 +97,7 @@ pub enum Event { } pub struct Pane { - item_views: Vec<(usize, Box)>, + item_views: Vec<(Option, Box)>, active_item_index: usize, nav_history: Rc>, toolbars: HashMap>, @@ -281,27 +281,23 @@ impl Pane { } } - pub fn open_item( + pub fn open_item( &mut self, - item_handle: T, - workspace: &Workspace, + item_view_to_open: Box, cx: &mut ViewContext, - ) -> Box - where - T: 'static + ItemHandle, - { - for (ix, (item_id, item_view)) in self.item_views.iter().enumerate() { - if *item_id == item_handle.id() { + ) -> Box { + // Find an existing view for the same project entry. + for (ix, (entry_id, item_view)) in self.item_views.iter().enumerate() { + if *entry_id == item_view_to_open.project_entry_id(cx) { let item_view = item_view.boxed_clone(); self.activate_item(ix, cx); return item_view; } } - let item_view = - item_handle.add_view(cx.window_id(), workspace, self.nav_history.clone(), cx); - self.add_item_view(item_view.boxed_clone(), cx); - item_view + item_view_to_open.set_nav_history(self.nav_history.clone(), cx); + self.add_item_view(item_view_to_open.boxed_clone(), cx); + item_view_to_open } pub fn add_item_view( @@ -312,18 +308,11 @@ impl Pane { item_view.added_to_pane(cx); let item_idx = cmp::min(self.active_item_index + 1, self.item_views.len()); self.item_views - .insert(item_idx, (item_view.item(cx).id(), item_view)); + .insert(item_idx, (item_view.project_entry_id(cx), item_view)); self.activate_item(item_idx, cx); cx.notify(); } - pub fn contains_item(&self, item: &dyn ItemHandle) -> bool { - let item_id = item.id(); - self.item_views - .iter() - .any(|(existing_item_id, _)| *existing_item_id == item_id) - } - pub fn item_views(&self) -> impl Iterator> { self.item_views.iter().map(|(_, view)| view) } @@ -334,14 +323,26 @@ impl Pane { .map(|(_, view)| view.clone()) } + pub fn item_for_entry(&self, entry: ProjectEntry) -> Option> { + self.item_views.iter().find_map(|(id, view)| { + if *id == Some(entry.entry_id) { + Some(view.boxed_clone()) + } else { + None + } + }) + } + pub fn index_for_item_view(&self, item_view: &dyn ItemViewHandle) -> Option { self.item_views .iter() .position(|(_, i)| i.id() == item_view.id()) } - pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { - self.item_views.iter().position(|(id, _)| *id == item.id()) + pub fn index_for_item(&self, item: &dyn ItemViewHandle) -> Option { + self.item_views + .iter() + .position(|(_, my_item)| my_item.id() == item.id()) } pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6bff12a577..5a3385a014 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,7 +9,6 @@ mod status_bar; use anyhow::{anyhow, Result}; use client::{Authenticate, ChannelList, Client, User, UserStore}; use clock::ReplicaId; -use collections::BTreeMap; use gpui::{ action, color::Color, @@ -18,16 +17,16 @@ use gpui::{ json::{self, to_string_pretty, ToJson}, keymap::Binding, platform::{CursorStyle, WindowOptions}, - AnyModelHandle, AnyViewHandle, AppContext, ClipboardItem, Entity, ImageData, ModelContext, - ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, - ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, + AnyViewHandle, AppContext, ClipboardItem, Entity, ImageData, ModelContext, ModelHandle, + MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use language::LanguageRegistry; use log::error; pub use pane::*; pub use pane_group::*; use postage::prelude::Stream; -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; @@ -35,9 +34,7 @@ pub use status_bar::StatusItemView; use std::{ any::{Any, TypeId}, cell::RefCell, - cmp::Reverse, future::Future, - hash::{Hash, Hasher}, path::{Path, PathBuf}, rc::Rc, sync::Arc, @@ -131,30 +128,19 @@ pub trait PathOpener { &self, project: &mut Project, path: ProjectPath, + window_id: usize, cx: &mut ModelContext, - ) -> Option>>>; -} - -pub trait Item: Entity + Sized { - type View: ItemView; - - fn build_view( - handle: ModelHandle, - workspace: &Workspace, - nav_history: ItemNavHistory, - cx: &mut ViewContext, - ) -> Self::View; - - fn project_path(&self) -> Option; + ) -> Option>>>; } pub trait ItemView: View { fn deactivated(&mut self, _: &mut ViewContext) {} fn navigate(&mut self, _: Box, _: &mut ViewContext) {} - fn item(&self, cx: &AppContext) -> Box; fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; - fn clone_on_split(&self, _: ItemNavHistory, _: &mut ViewContext) -> Option + fn project_entry(&self, cx: &AppContext) -> Option; + fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); + fn clone_on_split(&self, _: &mut ViewContext) -> Option where Self: Sized, { @@ -202,36 +188,13 @@ pub trait ItemView: View { } } -pub trait ItemHandle: Send + Sync { - fn id(&self) -> usize; - fn add_view( - &self, - window_id: usize, - workspace: &Workspace, - nav_history: Rc>, - cx: &mut MutableAppContext, - ) -> Box; - fn boxed_clone(&self) -> Box; - fn downgrade(&self) -> Box; - fn to_any(&self) -> AnyModelHandle; - fn project_path(&self, cx: &AppContext) -> Option; -} - -pub trait WeakItemHandle { - fn id(&self) -> usize; - fn upgrade(&self, cx: &AppContext) -> Option>; -} - pub trait ItemViewHandle: 'static { - fn item(&self, cx: &AppContext) -> Box; fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; + fn project_entry_id(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; - fn clone_on_split( - &self, - nav_history: Rc>, - cx: &mut MutableAppContext, - ) -> Option>; + fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext); + fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; fn added_to_pane(&mut self, cx: &mut ViewContext); fn deactivated(&self, cx: &mut MutableAppContext); fn navigate(&self, data: Box, cx: &mut MutableAppContext); @@ -256,97 +219,6 @@ pub trait WeakItemViewHandle { fn upgrade(&self, cx: &AppContext) -> Option>; } -impl ItemHandle for ModelHandle { - fn id(&self) -> usize { - self.id() - } - - fn add_view( - &self, - window_id: usize, - workspace: &Workspace, - 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) - })) - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } - - fn downgrade(&self) -> Box { - Box::new(self.downgrade()) - } - - fn to_any(&self) -> AnyModelHandle { - self.clone().into() - } - - fn project_path(&self, cx: &AppContext) -> Option { - self.read(cx).project_path() - } -} - -impl ItemHandle for Box { - fn id(&self) -> usize { - ItemHandle::id(self.as_ref()) - } - - fn add_view( - &self, - window_id: usize, - workspace: &Workspace, - nav_history: Rc>, - cx: &mut MutableAppContext, - ) -> Box { - ItemHandle::add_view(self.as_ref(), window_id, workspace, nav_history, cx) - } - - fn boxed_clone(&self) -> Box { - self.as_ref().boxed_clone() - } - - fn downgrade(&self) -> Box { - self.as_ref().downgrade() - } - - fn to_any(&self) -> AnyModelHandle { - self.as_ref().to_any() - } - - fn project_path(&self, cx: &AppContext) -> Option { - self.as_ref().project_path(cx) - } -} - -impl WeakItemHandle for WeakModelHandle { - fn id(&self) -> usize { - WeakModelHandle::id(self) - } - - fn upgrade(&self, cx: &AppContext) -> Option> { - WeakModelHandle::::upgrade(self, cx).map(|i| Box::new(i) as Box) - } -} - -impl Hash for Box { - fn hash(&self, state: &mut H) { - self.id().hash(state); - } -} - -impl PartialEq for Box { - fn eq(&self, other: &Self) -> bool { - self.id() == other.id() - } -} - -impl Eq for Box {} - impl dyn ItemViewHandle { pub fn downcast(&self) -> Option> { self.to_any().downcast() @@ -359,10 +231,6 @@ impl dyn ItemViewHandle { } impl ItemViewHandle for ViewHandle { - fn item(&self, cx: &AppContext) -> Box { - self.read(cx).item(cx) - } - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { self.read(cx).tab_content(style, cx) } @@ -371,23 +239,31 @@ impl ItemViewHandle for ViewHandle { self.read(cx).project_path(cx) } + fn project_entry_id(&self, cx: &AppContext) -> Option { + Some(self.read(cx).project_entry(cx)?.entry_id) + } + fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn clone_on_split( &self, - nav_history: Rc>, + // nav_history: Rc>, cx: &mut MutableAppContext, ) -> Option> { self.update(cx, |item, cx| { - cx.add_option_view(|cx| { - item.clone_on_split(ItemNavHistory::new(nav_history, &cx.handle()), cx) - }) + cx.add_option_view(|cx| item.clone_on_split(cx)) }) .map(|handle| Box::new(handle) as Box) } + fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext) { + self.update(cx, |item, cx| { + item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx); + }) + } + fn added_to_pane(&mut self, cx: &mut ViewContext) { cx.subscribe(self, |pane, item, event, cx| { if T::should_close_item_on_event(event) { @@ -469,12 +345,6 @@ impl Clone for Box { } } -impl Clone for Box { - fn clone(&self) -> Box { - self.boxed_clone() - } -} - impl WeakItemViewHandle for WeakViewHandle { fn id(&self) -> usize { self.id() @@ -563,7 +433,7 @@ pub struct Workspace { status_bar: ViewHandle, project: ModelHandle, path_openers: Arc<[Box]>, - items: BTreeMap, Box>, + // items: BTreeMap, Box>, _observe_current_user: Task<()>, } @@ -627,7 +497,6 @@ impl Workspace { right_sidebar: Sidebar::new(Side::Right), project: params.project.clone(), path_openers: params.path_openers.clone(), - items: Default::default(), _observe_current_user, } } @@ -804,16 +673,23 @@ impl Workspace { &mut self, path: ProjectPath, cx: &mut ViewContext, - ) -> Task>> { - if let Some(existing_item) = self.item_for_path(&path, cx) { + ) -> Task>> { + let project_entry = self.project.read(cx).entry_for_path(&path, cx); + + if let Some(existing_item) = project_entry.and_then(|entry| { + self.panes + .iter() + .find_map(|pane| pane.read(cx).item_for_entry(entry)) + }) { return Task::ready(Ok(existing_item)); } let project_path = path.clone(); let path_openers = self.path_openers.clone(); + let window_id = cx.window_id(); self.project.update(cx, |project, cx| { for opener in path_openers.iter() { - if let Some(task) = opener.open(project, project_path.clone(), cx) { + if let Some(task) = opener.open(project, project_path.clone(), window_id, cx) { return task; } } @@ -821,26 +697,19 @@ impl Workspace { }) } - fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option> { - self.items - .values() - .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> { + self.items_of_type(cx).max_by_key(|item| item.id()) } - pub fn item_of_type(&self, cx: &AppContext) -> Option> { - self.items - .values() - .find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) - } - - pub fn items_of_type<'a, T: Item>( + pub fn items_of_type<'a, T: ItemView>( &'a self, cx: &'a AppContext, - ) -> impl 'a + Iterator> { - self.items - .values() - .filter_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) + ) -> impl 'a + Iterator> { + self.panes.iter().flat_map(|pane| { + pane.read(cx) + .item_views() + .filter_map(|item| item.to_any().downcast()) + }) } pub fn active_item(&self, cx: &AppContext) -> Option> { @@ -962,52 +831,46 @@ impl Workspace { pane } - pub fn open_item( + pub fn open_item( &mut self, - item_handle: T, + item_view: Box, cx: &mut ViewContext, - ) -> Box - where - T: 'static + ItemHandle, - { - self.open_item_in_pane(item_handle, &self.active_pane().clone(), cx) + ) -> Box { + self.open_item_in_pane(item_view, &self.active_pane().clone(), cx) } - pub fn open_item_in_pane( + pub fn open_item_in_pane( &mut self, - item_handle: T, + item_view: Box, pane: &ViewHandle, cx: &mut ViewContext, + ) -> Box { + pane.update(cx, |pane, cx| pane.open_item(item_view, cx)) + } + + pub fn open_item_for_project_entry( + &mut self, + project_entry: ProjectEntry, + cx: &mut ViewContext, + build_view: F, ) -> Box where - T: 'static + ItemHandle, + T: ItemView, + F: FnOnce(&mut ViewContext) -> T, { - self.items - .insert(Reverse(item_handle.id()), item_handle.downgrade()); - pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx)) - } - - pub fn activate_pane_for_item( - &mut self, - item: &dyn ItemHandle, - cx: &mut ViewContext, - ) -> bool { - let pane = self.panes.iter().find_map(|pane| { - if pane.read(cx).contains_item(item) { - Some(pane.clone()) - } else { - None - } - }); - if let Some(pane) = pane { - self.activate_pane(pane.clone(), cx); - true - } else { - false + if let Some(existing_item) = self + .panes + .iter() + .find_map(|pane| pane.read(cx).item_for_entry(project_entry)) + { + return existing_item.boxed_clone(); } + + let view = Box::new(cx.add_view(build_view)); + self.open_item(view, cx) } - pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { + pub fn activate_item(&mut self, item: &dyn ItemViewHandle, cx: &mut ViewContext) -> bool { let result = self.panes.iter().find_map(|pane| { if let Some(ix) = pane.read(cx).index_for_item(item) { Some((pane.clone(), ix)) @@ -1078,9 +941,8 @@ impl Workspace { self.activate_pane(new_pane.clone(), cx); if let Some(item) = pane.read(cx).active_item() { let nav_history = new_pane.read(cx).nav_history().clone(); - if let Some(clone) = item.clone_on_split(nav_history, cx.as_mut()) { - let item = clone.item(cx).downgrade(); - self.items.insert(Reverse(item.id()), item); + if let Some(clone) = item.clone_on_split(cx.as_mut()) { + clone.set_nav_history(nav_history, cx); new_pane.update(cx, |new_pane, cx| new_pane.add_item_view(clone, cx)); } }