Remove workspace::Item trait

Max Brunsfeld , Nathan Sobo , Keith Simmons , and Antonio Scandurra created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Keith Simmons <keith@zed.dev>
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

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             | 282 +++++---------------
10 files changed, 280 insertions(+), 516 deletions(-)

Detailed changes

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<Project>,
-}
-
 struct ProjectDiagnosticsEditor {
-    model: ModelHandle<ProjectDiagnostics>,
+    project: ModelHandle<Project>,
     workspace: WeakViewHandle<Workspace>,
     editor: ViewHandle<Editor>,
     summary: DiagnosticSummary,
@@ -65,16 +61,6 @@ struct DiagnosticGroupState {
     block_count: usize,
 }
 
-impl ProjectDiagnostics {
-    fn new(project: ModelHandle<Project>) -> 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<ProjectDiagnostics>,
+        project_handle: ModelHandle<Project>,
         workspace: WeakViewHandle<Workspace>,
         cx: &mut ViewContext<Self>,
     ) -> 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<Workspace>) {
-        if let Some(existing) = workspace.item_of_type::<ProjectDiagnostics>(cx) {
+        if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(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<Self>) {
         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>) {
-        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<Self>,
-        workspace: &Workspace,
-        nav_history: ItemNavHistory,
-        cx: &mut ViewContext<Self::View>,
-    ) -> 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<project::ProjectPath> {
-        None
-    }
-}
-
 impl workspace::ItemView for ProjectDiagnosticsEditor {
-    fn item(&self, _: &AppContext) -> Box<dyn ItemHandle> {
-        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<project::ProjectEntry> {
+        None
+    }
+
     fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) {
         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<Self>,
-    ) -> Option<Self>
+    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
+        self.editor.update(cx, |editor, _| {
+            editor.set_nav_history(Some(nav_history));
+        });
+    }
+
+    fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
     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;

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>) -> Self {
+    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(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::<Editor>()
+                .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>) -> 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<Workspace>,
     ) {
-        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::<Self>(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::<Self>()
-                        .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::<Self>(cx) {
-                    editor.update(cx, |editor, cx| {
-                        let color = editor.style(cx).highlighted_line_background;
-                        editor.highlight_background::<Self>(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::<Self>(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::<Editor>()
-                    .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);
                 });

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<Project>,
-    ) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
+    ) -> Option<Task<Result<Box<dyn ItemViewHandle>>>> {
         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<dyn ItemHandle>)
-        });
-        Some(task)
-    }
-}
-
-impl ItemHandle for BufferItemHandle {
-    fn add_view(
-        &self,
-        window_id: usize,
-        workspace: &Workspace,
-        nav_history: Rc<RefCell<NavHistory>>,
-        cx: &mut MutableAppContext,
-    ) -> Box<dyn ItemViewHandle> {
-        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
-        }))
-    }
-
-    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
-        Box::new(self.clone())
-    }
-
-    fn to_any(&self) -> gpui::AnyModelHandle {
-        self.0.clone().into()
-    }
-
-    fn downgrade(&self) -> Box<dyn workspace::WeakItemHandle> {
-        Box::new(WeakBufferItemHandle(self.0.downgrade()))
-    }
-
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-        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<RefCell<NavHistory>>,
-        cx: &mut MutableAppContext,
-    ) -> Box<dyn ItemViewHandle> {
-        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
+            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<dyn ItemViewHandle>)
         }))
     }
-
-    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
-        Box::new(self.clone())
-    }
-
-    fn to_any(&self) -> gpui::AnyModelHandle {
-        self.0.clone().into()
-    }
-
-    fn downgrade(&self) -> Box<dyn WeakItemHandle> {
-        Box::new(WeakMultiBufferItemHandle(self.0.downgrade()))
-    }
-
-    fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
-        None
-    }
-
-    fn id(&self) -> usize {
-        self.0.id()
-    }
-}
-
-impl WeakItemHandle for WeakBufferItemHandle {
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-        self.0
-            .upgrade(cx)
-            .map(|buffer| Box::new(BufferItemHandle(buffer)) as Box<dyn ItemHandle>)
-    }
-
-    fn id(&self) -> usize {
-        self.0.id()
-    }
-}
-
-impl WeakItemHandle for WeakMultiBufferItemHandle {
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-        self.0
-            .upgrade(cx)
-            .map(|buffer| Box::new(MultiBufferItemHandle(buffer)) as Box<dyn ItemHandle>)
-    }
-
-    fn id(&self) -> usize {
-        self.0.id()
-    }
 }
 
 impl ItemView for Editor {
-    fn item(&self, cx: &AppContext) -> Box<dyn ItemHandle> {
-        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<dyn std::any::Any>, cx: &mut ViewContext<Self>) {
         if let Some(data) = data.downcast_ref::<NavigationData>() {
             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<Self>,
-    ) -> Option<Self>
+    fn project_entry(&self, cx: &AppContext) -> Option<ProjectEntry> {
+        File::from_dyn(self.buffer().read(cx).file(cx)).and_then(|file| file.project_entry(cx))
+    }
+
+    fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
     where
         Self: Sized,
     {
-        Some(self.clone(nav_history, cx))
+        Some(self.clone(cx))
+    }
+
+    fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+        self.nav_history = Some(history);
     }
 
     fn deactivated(&mut self, cx: &mut ViewContext<Self>) {

crates/gpui/src/app.rs 🔗

@@ -595,6 +595,14 @@ impl AsyncAppContext {
         self.update(|cx| cx.add_model(build_model))
     }
 
+    pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> T,
+    {
+        self.update(|cx| cx.add_view(window_id, build_view))
+    }
+
     pub fn platform(&self) -> Arc<dyn Platform> {
         self.0.borrow().platform()
     }
@@ -3459,6 +3467,12 @@ impl<T> PartialEq for ViewHandle<T> {
     }
 }
 
+impl<T> PartialEq<WeakViewHandle<T>> for ViewHandle<T> {
+    fn eq(&self, other: &WeakViewHandle<T>) -> bool {
+        self.window_id == other.window_id && self.view_id == other.view_id
+    }
+}
+
 impl<T> Eq for ViewHandle<T> {}
 
 impl<T> Debug for ViewHandle<T> {

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<ProjectEntry> {
+        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(

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<ProjectEntry> {
+        self.entry_id.map(|entry_id| ProjectEntry {
+            worktree_id: self.worktree_id(cx),
+            entry_id,
+        })
+    }
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]

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::<Editor>()
-                            .unwrap();
+
+                        let editor = Editor::find_or_create(workspace, buffer, cx);
                         editor.update(cx, |editor, cx| {
                             editor.select_ranges(
                                 [position..position],

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<Project>, WeakModelHandle<ProjectSearch>>);
+struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSearchView>>);
 
 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<Self>,
-        _: &Workspace,
-        nav_history: ItemNavHistory,
-        cx: &mut gpui::ViewContext<Self::View>,
-    ) -> Self::View {
-        ProjectSearchView::new(model, Some(nav_history), cx)
-    }
-
-    fn project_path(&self) -> Option<project::ProjectPath> {
-        None
-    }
-}
-
 enum ViewEvent {
     UpdateTab,
 }
@@ -199,11 +182,11 @@ impl View for ProjectSearchView {
     }
 
     fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+        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<dyn ItemHandle> {
-        Box::new(self.model.clone())
-    }
-
     fn tab_content(&self, tab_theme: &theme::Tab, cx: &gpui::AppContext) -> ElementBox {
         let settings = cx.app_state::<Settings>();
         let search_theme = &settings.theme.search;
@@ -271,6 +250,10 @@ impl ItemView for ProjectSearchView {
         None
     }
 
+    fn project_entry(&self, _: &AppContext) -> Option<project::ProjectEntry> {
+        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<Self>,
-    ) -> Option<Self>
+    fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
     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>) {
+        self.results_editor.update(cx, |editor, _| {
+            editor.set_nav_history(Some(nav_history));
+        });
     }
 
     fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) {
@@ -328,11 +313,7 @@ impl ItemView for ProjectSearchView {
 }
 
 impl ProjectSearchView {
-    fn new(
-        model: ModelHandle<ProjectSearch>,
-        nav_history: Option<ItemNavHistory>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
+    fn new(model: ModelHandle<ProjectSearch>, cx: &mut ViewContext<Self>) -> 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::<ProjectSearch>(cx)
+                    .items_of_type::<ProjectSearchView>(cx)
                     .find(|search| search == active_search)
             })
-            .or_else(|| workspace.item_of_type::<ProjectSearch>(cx));
+            .or_else(|| workspace.item_of_type::<ProjectSearchView>(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| {

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<dyn ItemViewHandle>)>,
+    item_views: Vec<(Option<usize>, Box<dyn ItemViewHandle>)>,
     active_item_index: usize,
     nav_history: Rc<RefCell<NavHistory>>,
     toolbars: HashMap<TypeId, Box<dyn ToolbarHandle>>,
@@ -281,27 +281,23 @@ impl Pane {
         }
     }
 
-    pub fn open_item<T>(
+    pub fn open_item(
         &mut self,
-        item_handle: T,
-        workspace: &Workspace,
+        item_view_to_open: Box<dyn ItemViewHandle>,
         cx: &mut ViewContext<Self>,
-    ) -> Box<dyn ItemViewHandle>
-    where
-        T: 'static + ItemHandle,
-    {
-        for (ix, (item_id, item_view)) in self.item_views.iter().enumerate() {
-            if *item_id == item_handle.id() {
+    ) -> Box<dyn ItemViewHandle> {
+        // 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<Item = &Box<dyn ItemViewHandle>> {
         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<Box<dyn ItemViewHandle>> {
+        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<usize> {
         self.item_views
             .iter()
             .position(|(_, i)| i.id() == item_view.id())
     }
 
-    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
-        self.item_views.iter().position(|(id, _)| *id == item.id())
+    pub fn index_for_item(&self, item: &dyn ItemViewHandle) -> Option<usize> {
+        self.item_views
+            .iter()
+            .position(|(_, my_item)| my_item.id() == item.id())
     }
 
     pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext<Self>) {

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<Project>,
-    ) -> Option<Task<Result<Box<dyn ItemHandle>>>>;
-}
-
-pub trait Item: Entity + Sized {
-    type View: ItemView;
-
-    fn build_view(
-        handle: ModelHandle<Self>,
-        workspace: &Workspace,
-        nav_history: ItemNavHistory,
-        cx: &mut ViewContext<Self::View>,
-    ) -> Self::View;
-
-    fn project_path(&self) -> Option<ProjectPath>;
+    ) -> Option<Task<Result<Box<dyn ItemViewHandle>>>>;
 }
 
 pub trait ItemView: View {
     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) {}
-    fn item(&self, cx: &AppContext) -> Box<dyn ItemHandle>;
     fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
-    fn clone_on_split(&self, _: ItemNavHistory, _: &mut ViewContext<Self>) -> Option<Self>
+    fn project_entry(&self, cx: &AppContext) -> Option<ProjectEntry>;
+    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
+    fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
     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<RefCell<NavHistory>>,
-        cx: &mut MutableAppContext,
-    ) -> Box<dyn ItemViewHandle>;
-    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
-    fn downgrade(&self) -> Box<dyn WeakItemHandle>;
-    fn to_any(&self) -> AnyModelHandle;
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
-}
-
-pub trait WeakItemHandle {
-    fn id(&self) -> usize;
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
-}
-
 pub trait ItemViewHandle: 'static {
-    fn item(&self, cx: &AppContext) -> Box<dyn ItemHandle>;
     fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
+    fn project_entry_id(&self, cx: &AppContext) -> Option<usize>;
     fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
-    fn clone_on_split(
-        &self,
-        nav_history: Rc<RefCell<NavHistory>>,
-        cx: &mut MutableAppContext,
-    ) -> Option<Box<dyn ItemViewHandle>>;
+    fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext);
+    fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
     fn added_to_pane(&mut self, cx: &mut ViewContext<Pane>);
     fn deactivated(&self, cx: &mut MutableAppContext);
     fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext);
@@ -256,97 +219,6 @@ pub trait WeakItemViewHandle {
     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemViewHandle>>;
 }
 
-impl<T: Item> ItemHandle for ModelHandle<T> {
-    fn id(&self) -> usize {
-        self.id()
-    }
-
-    fn add_view(
-        &self,
-        window_id: usize,
-        workspace: &Workspace,
-        nav_history: Rc<RefCell<NavHistory>>,
-        cx: &mut MutableAppContext,
-    ) -> Box<dyn ItemViewHandle> {
-        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<dyn ItemHandle> {
-        Box::new(self.clone())
-    }
-
-    fn downgrade(&self) -> Box<dyn WeakItemHandle> {
-        Box::new(self.downgrade())
-    }
-
-    fn to_any(&self) -> AnyModelHandle {
-        self.clone().into()
-    }
-
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-        self.read(cx).project_path()
-    }
-}
-
-impl ItemHandle for Box<dyn ItemHandle> {
-    fn id(&self) -> usize {
-        ItemHandle::id(self.as_ref())
-    }
-
-    fn add_view(
-        &self,
-        window_id: usize,
-        workspace: &Workspace,
-        nav_history: Rc<RefCell<NavHistory>>,
-        cx: &mut MutableAppContext,
-    ) -> Box<dyn ItemViewHandle> {
-        ItemHandle::add_view(self.as_ref(), window_id, workspace, nav_history, cx)
-    }
-
-    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
-        self.as_ref().boxed_clone()
-    }
-
-    fn downgrade(&self) -> Box<dyn WeakItemHandle> {
-        self.as_ref().downgrade()
-    }
-
-    fn to_any(&self) -> AnyModelHandle {
-        self.as_ref().to_any()
-    }
-
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-        self.as_ref().project_path(cx)
-    }
-}
-
-impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
-    fn id(&self) -> usize {
-        WeakModelHandle::id(self)
-    }
-
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-        WeakModelHandle::<T>::upgrade(self, cx).map(|i| Box::new(i) as Box<dyn ItemHandle>)
-    }
-}
-
-impl Hash for Box<dyn WeakItemHandle> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.id().hash(state);
-    }
-}
-
-impl PartialEq for Box<dyn WeakItemHandle> {
-    fn eq(&self, other: &Self) -> bool {
-        self.id() == other.id()
-    }
-}
-
-impl Eq for Box<dyn WeakItemHandle> {}
-
 impl dyn ItemViewHandle {
     pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
         self.to_any().downcast()
@@ -359,10 +231,6 @@ impl dyn ItemViewHandle {
 }
 
 impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
-    fn item(&self, cx: &AppContext) -> Box<dyn ItemHandle> {
-        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<T: ItemView> ItemViewHandle for ViewHandle<T> {
         self.read(cx).project_path(cx)
     }
 
+    fn project_entry_id(&self, cx: &AppContext) -> Option<usize> {
+        Some(self.read(cx).project_entry(cx)?.entry_id)
+    }
+
     fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
         Box::new(self.clone())
     }
 
     fn clone_on_split(
         &self,
-        nav_history: Rc<RefCell<NavHistory>>,
+        // nav_history: Rc<RefCell<NavHistory>>,
         cx: &mut MutableAppContext,
     ) -> Option<Box<dyn ItemViewHandle>> {
         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<dyn ItemViewHandle>)
     }
 
+    fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, 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<Pane>) {
         cx.subscribe(self, |pane, item, event, cx| {
             if T::should_close_item_on_event(event) {
@@ -469,12 +345,6 @@ impl Clone for Box<dyn ItemViewHandle> {
     }
 }
 
-impl Clone for Box<dyn ItemHandle> {
-    fn clone(&self) -> Box<dyn ItemHandle> {
-        self.boxed_clone()
-    }
-}
-
 impl<T: ItemView> WeakItemViewHandle for WeakViewHandle<T> {
     fn id(&self) -> usize {
         self.id()
@@ -563,7 +433,7 @@ pub struct Workspace {
     status_bar: ViewHandle<StatusBar>,
     project: ModelHandle<Project>,
     path_openers: Arc<[Box<dyn PathOpener>]>,
-    items: BTreeMap<Reverse<usize>, Box<dyn WeakItemHandle>>,
+    // items: BTreeMap<Reverse<usize>, Box<dyn WeakItemHandle>>,
     _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<Self>,
-    ) -> Task<Result<Box<dyn ItemHandle>>> {
-        if let Some(existing_item) = self.item_for_path(&path, cx) {
+    ) -> Task<Result<Box<dyn ItemViewHandle>>> {
+        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<Box<dyn ItemHandle>> {
-        self.items
-            .values()
-            .filter_map(|i| i.upgrade(cx))
-            .find(|i| i.project_path(cx).as_ref() == Some(path))
+    pub fn item_of_type<T: ItemView>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+        self.items_of_type(cx).max_by_key(|item| item.id())
     }
 
-    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ModelHandle<T>> {
-        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<Item = ModelHandle<T>> {
-        self.items
-            .values()
-            .filter_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast()))
+    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
+        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<Box<dyn ItemViewHandle>> {
@@ -962,52 +831,46 @@ impl Workspace {
         pane
     }
 
-    pub fn open_item<T>(
+    pub fn open_item(
         &mut self,
-        item_handle: T,
+        item_view: Box<dyn ItemViewHandle>,
         cx: &mut ViewContext<Self>,
-    ) -> Box<dyn ItemViewHandle>
-    where
-        T: 'static + ItemHandle,
-    {
-        self.open_item_in_pane(item_handle, &self.active_pane().clone(), cx)
+    ) -> Box<dyn ItemViewHandle> {
+        self.open_item_in_pane(item_view, &self.active_pane().clone(), cx)
     }
 
-    pub fn open_item_in_pane<T>(
+    pub fn open_item_in_pane(
         &mut self,
-        item_handle: T,
+        item_view: Box<dyn ItemViewHandle>,
         pane: &ViewHandle<Pane>,
         cx: &mut ViewContext<Self>,
-    ) -> Box<dyn ItemViewHandle>
-    where
-        T: 'static + ItemHandle,
-    {
-        self.items
-            .insert(Reverse(item_handle.id()), item_handle.downgrade());
-        pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx))
+    ) -> Box<dyn ItemViewHandle> {
+        pane.update(cx, |pane, cx| pane.open_item(item_view, cx))
     }
 
-    pub fn activate_pane_for_item(
+    pub fn open_item_for_project_entry<T, F>(
         &mut self,
-        item: &dyn ItemHandle,
+        project_entry: ProjectEntry,
         cx: &mut ViewContext<Self>,
-    ) -> 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
+        build_view: F,
+    ) -> Box<dyn ItemViewHandle>
+    where
+        T: ItemView,
+        F: FnOnce(&mut ViewContext<T>) -> T,
+    {
+        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<Self>) -> bool {
+    pub fn activate_item(&mut self, item: &dyn ItemViewHandle, cx: &mut ViewContext<Self>) -> 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));
             }
         }