Remove `Editor::find_or_create`

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs                   | 26 +--------------
crates/editor/src/items.rs                    | 16 ++++++++-
crates/project/src/project.rs                 | 10 ++++++
crates/project_symbols/src/project_symbols.rs |  2 
crates/workspace/src/pane.rs                  |  2 
crates/workspace/src/workspace.rs             | 35 +++++++++++++++++++++
6 files changed, 63 insertions(+), 28 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -847,27 +847,6 @@ impl Editor {
         Self::new(EditorMode::Full, buffer, project, None, cx)
     }
 
-    pub fn find_or_create(
-        workspace: &mut Workspace,
-        buffer: ModelHandle<Buffer>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> ViewHandle<Editor> {
-        let project = workspace.project().clone();
-
-        if let Some(item) = project::File::from_dyn(buffer.read(cx).file())
-            .and_then(|file| file.project_entry_id(cx))
-            .and_then(|entry_id| workspace.active_pane().read(cx).item_for_entry(entry_id))
-            .and_then(|item| item.downcast())
-        {
-            workspace.activate_item(&item, cx);
-            return item;
-        }
-
-        let editor = cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx));
-        workspace.add_item(Box::new(editor.clone()), cx);
-        editor
-    }
-
     pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
         let mut clone = Self::new(
             self.mode,
@@ -4324,8 +4303,7 @@ impl Editor {
                 for definition in definitions {
                     let range = definition.range.to_offset(definition.buffer.read(cx));
 
-                    let target_editor_handle =
-                        Self::find_or_create(workspace, definition.buffer, cx);
+                    let target_editor_handle = workspace.open_project_item(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.
@@ -5597,7 +5575,7 @@ impl Editor {
             workspace.activate_next_pane(cx);
 
             for (buffer, ranges) in new_selections_by_buffer.into_iter() {
-                let editor = Self::find_or_create(workspace, buffer, cx);
+                let editor = workspace.open_project_item::<Self>(buffer, cx);
                 editor.update(cx, |editor, cx| {
                     editor.select_ranges(ranges, Some(Autoscroll::Newest), cx);
                 });

crates/editor/src/items.rs 🔗

@@ -4,13 +4,13 @@ use gpui::{
     elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, Task, View,
     ViewContext, ViewHandle,
 };
-use language::{Bias, Diagnostic, File as _};
+use language::{Bias, Buffer, Diagnostic, File as _};
 use project::{File, Project, ProjectPath};
 use std::fmt::Write;
 use std::path::PathBuf;
 use text::{Point, Selection};
 use util::ResultExt;
-use workspace::{Item, ItemHandle, ItemNavHistory, Settings, StatusItemView};
+use workspace::{Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView};
 
 impl Item for Editor {
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) {
@@ -132,6 +132,18 @@ impl Item for Editor {
     }
 }
 
+impl ProjectItem for Editor {
+    type Item = Buffer;
+
+    fn for_project_item(
+        project: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        Self::for_buffer(buffer, Some(project), cx)
+    }
+}
+
 pub struct CursorPosition {
     position: Option<Point>,
     selected_count: usize,

crates/project/src/project.rs 🔗

@@ -49,6 +49,10 @@ use util::{post_inc, ResultExt, TryFutureExt as _};
 pub use fs::*;
 pub use worktree::*;
 
+pub trait Item: Entity {
+    fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
+}
+
 pub struct Project {
     worktrees: Vec<WorktreeHandle>,
     active_entry: Option<ProjectEntryId>,
@@ -4522,6 +4526,12 @@ fn relativize_path(base: &Path, path: &Path) -> PathBuf {
     components.iter().map(|c| c.as_os_str()).collect()
 }
 
+impl Item for Buffer {
+    fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
+        File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::{Event, *};

crates/project_symbols/src/project_symbols.rs 🔗

@@ -354,7 +354,7 @@ impl ProjectSymbolsView {
                             .read(cx)
                             .clip_point_utf16(symbol.range.start, Bias::Left);
 
-                        let editor = Editor::find_or_create(workspace, buffer, cx);
+                        let editor = workspace.open_project_item::<Editor>(buffer, cx);
                         editor.update(cx, |editor, cx| {
                             editor.select_ranges(
                                 [position..position],

crates/workspace/src/pane.rs 🔗

@@ -280,7 +280,7 @@ impl Pane {
         }
     }
 
-    pub(crate) fn open_item(
+    pub fn open_item(
         &mut self,
         project_entry_id: ProjectEntryId,
         cx: &mut ViewContext<Self>,

crates/workspace/src/workspace.rs 🔗

@@ -195,6 +195,16 @@ pub trait Item: View {
     }
 }
 
+pub trait ProjectItem: Item {
+    type Item: project::Item;
+
+    fn for_project_item(
+        project: ModelHandle<Project>,
+        item: ModelHandle<Self::Item>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self;
+}
+
 pub trait ItemHandle: 'static {
     fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@@ -833,6 +843,31 @@ impl Workspace {
         })
     }
 
+    pub fn open_project_item<T>(
+        &mut self,
+        project_item: ModelHandle<T::Item>,
+        cx: &mut ViewContext<Self>,
+    ) -> ViewHandle<T>
+    where
+        T: ProjectItem,
+    {
+        use project::Item as _;
+
+        if let Some(item) = project_item
+            .read(cx)
+            .entry_id(cx)
+            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id))
+            .and_then(|item| item.downcast())
+        {
+            self.activate_item(&item, cx);
+            return item;
+        }
+
+        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+        self.add_item(Box::new(item.clone()), cx);
+        item
+    }
+
     pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
         let result = self.panes.iter().find_map(|pane| {
             if let Some(ix) = pane.read(cx).index_for_item(item) {