WIP

Antonio Scandurra created

Change summary

crates/workspace2/src/item.rs       |  546 +-
crates/workspace2/src/pane.rs       | 5016 +++++++++++-----------
crates/workspace2/src/pane_group.rs |   82 
crates/workspace2/src/searchable.rs |   56 
crates/workspace2/src/workspace2.rs | 6700 +++++++++++++++---------------
5 files changed, 6,215 insertions(+), 6,185 deletions(-)

Detailed changes

crates/workspace2/src/item.rs 🔗

@@ -3,7 +3,12 @@
 //     ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
 // };
 // use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
-// use anyhow::Result;
+use anyhow::Result;
+use client2::{
+    proto::{self, PeerId, ViewId},
+    Client,
+};
+use theme2::Theme;
 // use client2::{
 //     proto::{self, PeerId},
 //     Client,
@@ -78,218 +83,227 @@
 //     }
 // }
 
-// #[derive(Eq, PartialEq, Hash, Debug)]
-// pub enum ItemEvent {
-//     CloseItem,
-//     UpdateTab,
-//     UpdateBreadcrumbs,
-//     Edit,
-// }
-
-// // TODO: Combine this with existing HighlightedText struct?
-// pub struct BreadcrumbText {
-//     pub text: String,
-//     pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
-// }
-
-// pub trait Item: View {
-//     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
-//     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
-//     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-//         false
-//     }
-//     fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
-//         None
-//     }
-//     fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
-//         None
-//     }
-//     fn tab_content<V: 'static>(
-//         &self,
-//         detail: Option<usize>,
-//         style: &theme2::Tab,
-//         cx: &AppContext,
-//     ) -> AnyElement<V>;
-//     fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) {
-//     } // (model id, Item)
-//     fn is_singleton(&self, _cx: &AppContext) -> bool {
-//         false
-//     }
-//     fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
-//     fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
-//     where
-//         Self: Sized,
-//     {
-//         None
-//     }
-//     fn is_dirty(&self, _: &AppContext) -> bool {
-//         false
-//     }
-//     fn has_conflict(&self, _: &AppContext) -> bool {
-//         false
-//     }
-//     fn can_save(&self, _cx: &AppContext) -> bool {
-//         false
-//     }
-//     fn save(
-//         &mut self,
-//         _project: ModelHandle<Project>,
-//         _cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<()>> {
-//         unimplemented!("save() must be implemented if can_save() returns true")
-//     }
-//     fn save_as(
-//         &mut self,
-//         _project: ModelHandle<Project>,
-//         _abs_path: PathBuf,
-//         _cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<()>> {
-//         unimplemented!("save_as() must be implemented if can_save() returns true")
-//     }
-//     fn reload(
-//         &mut self,
-//         _project: ModelHandle<Project>,
-//         _cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<()>> {
-//         unimplemented!("reload() must be implemented if can_save() returns true")
-//     }
-//     fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-//         SmallVec::new()
-//     }
-//     fn should_close_item_on_event(_: &Self::Event) -> bool {
-//         false
-//     }
-//     fn should_update_tab_on_event(_: &Self::Event) -> bool {
-//         false
-//     }
-
-//     fn act_as_type<'a>(
-//         &'a self,
-//         type_id: TypeId,
-//         self_handle: &'a ViewHandle<Self>,
-//         _: &'a AppContext,
-//     ) -> Option<&AnyViewHandle> {
-//         if TypeId::of::<Self>() == type_id {
-//             Some(self_handle)
-//         } else {
-//             None
-//         }
-//     }
-
-//     fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-//         None
-//     }
-
-//     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-//         ToolbarItemLocation::Hidden
-//     }
-
-//     fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-//         None
-//     }
-
-//     fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
-
-//     fn serialized_item_kind() -> Option<&'static str> {
-//         None
-//     }
-
-//     fn deserialize(
-//         _project: ModelHandle<Project>,
-//         _workspace: WeakViewHandle<Workspace>,
-//         _workspace_id: WorkspaceId,
-//         _item_id: ItemId,
-//         _cx: &mut ViewContext<Pane>,
-//     ) -> Task<Result<ViewHandle<Self>>> {
-//         unimplemented!(
-//             "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
-//         )
-//     }
-//     fn show_toolbar(&self) -> bool {
-//         true
-//     }
-//     fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
-//         None
-//     }
-// }
+#[derive(Eq, PartialEq, Hash, Debug)]
+pub enum ItemEvent {
+    CloseItem,
+    UpdateTab,
+    UpdateBreadcrumbs,
+    Edit,
+}
 
-use core::fmt;
+// TODO: Combine this with existing HighlightedText struct?
+pub struct BreadcrumbText {
+    pub text: String,
+    pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
+}
 
-pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
-    //     fn subscribe_to_item_events(
-    //         &self,
-    //         cx: &mut WindowContext,
-    //         handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
-    //     ) -> gpui2::Subscription;
-    //     fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
-    //     fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
-    //     fn tab_content(
-    //         &self,
-    //         detail: Option<usize>,
-    //         style: &theme2::Tab,
-    //         cx: &AppContext,
-    //     ) -> AnyElement<Pane>;
-    //     fn dragged_tab_content(
+pub trait Item: EventEmitter {
+    //     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
+    //     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
+    //     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+    //         false
+    //     }
+    //     fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+    //         None
+    //     }
+    //     fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
+    //         None
+    //     }
+    //     fn tab_content<V: 'static>(
     //         &self,
     //         detail: Option<usize>,
     //         style: &theme2::Tab,
     //         cx: &AppContext,
-    //     ) -> AnyElement<Workspace>;
-    //     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
-    //     fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
-    //     fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
-    //     fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item));
-    //     fn is_singleton(&self, cx: &AppContext) -> bool;
-    //     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
-    //     fn clone_on_split(
-    //         &self,
-    //         workspace_id: WorkspaceId,
-    //         cx: &mut WindowContext,
-    //     ) -> Option<Box<dyn ItemHandle>>;
-    //     fn added_to_pane(
-    //         &self,
-    //         workspace: &mut Workspace,
-    //         pane: ViewHandle<Pane>,
-    //         cx: &mut ViewContext<Workspace>,
-    //     );
-    //     fn deactivated(&self, cx: &mut WindowContext);
-    //     fn workspace_deactivated(&self, cx: &mut WindowContext);
-    //     fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
-    //     fn id(&self) -> usize;
-    //     fn window(&self) -> AnyWindowHandle;
-    //     fn as_any(&self) -> &AnyViewHandle;
-    //     fn is_dirty(&self, cx: &AppContext) -> bool;
-    //     fn has_conflict(&self, cx: &AppContext) -> bool;
-    //     fn can_save(&self, cx: &AppContext) -> bool;
-    //     fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    //     ) -> AnyElement<V>;
+    //     fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) {
+    //     } // (model id, Item)
+    fn is_singleton(&self, _cx: &AppContext) -> bool {
+        false
+    }
+    //     fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
+    //     fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
+    //     where
+    //         Self: Sized,
+    //     {
+    //         None
+    //     }
+    //     fn is_dirty(&self, _: &AppContext) -> bool {
+    //         false
+    //     }
+    //     fn has_conflict(&self, _: &AppContext) -> bool {
+    //         false
+    //     }
+    //     fn can_save(&self, _cx: &AppContext) -> bool {
+    //         false
+    //     }
+    //     fn save(
+    //         &mut self,
+    //         _project: ModelHandle<Project>,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         unimplemented!("save() must be implemented if can_save() returns true")
+    //     }
     //     fn save_as(
-    //         &self,
-    //         project: ModelHandle<Project>,
-    //         abs_path: PathBuf,
-    //         cx: &mut WindowContext,
-    //     ) -> Task<Result<()>>;
-    //     fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
-    //     fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
-    //     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
-    //     fn on_release(
-    //         &self,
-    //         cx: &mut AppContext,
-    //         callback: Box<dyn FnOnce(&mut AppContext)>,
-    //     ) -> gpui2::Subscription;
-    //     fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
-    //     fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
-    //     fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
-    //     fn serialized_item_kind(&self) -> Option<&'static str>;
-    //     fn show_toolbar(&self, cx: &AppContext) -> bool;
-    //     fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F>;
+    //         &mut self,
+    //         _project: ModelHandle<Project>,
+    //         _abs_path: PathBuf,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         unimplemented!("save_as() must be implemented if can_save() returns true")
+    //     }
+    //     fn reload(
+    //         &mut self,
+    //         _project: ModelHandle<Project>,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         unimplemented!("reload() must be implemented if can_save() returns true")
+    //     }
+    //     fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+    //         SmallVec::new()
+    //     }
+    //     fn should_close_item_on_event(_: &Self::Event) -> bool {
+    //         false
+    //     }
+    //     fn should_update_tab_on_event(_: &Self::Event) -> bool {
+    //         false
+    //     }
+
+    //     fn act_as_type<'a>(
+    //         &'a self,
+    //         type_id: TypeId,
+    //         self_handle: &'a ViewHandle<Self>,
+    //         _: &'a AppContext,
+    //     ) -> Option<&AnyViewHandle> {
+    //         if TypeId::of::<Self>() == type_id {
+    //             Some(self_handle)
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    //         None
+    //     }
+
+    //     fn breadcrumb_location(&self) -> ToolbarItemLocation {
+    //         ToolbarItemLocation::Hidden
+    //     }
+
+    //     fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+    //         None
+    //     }
+
+    //     fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
+
+    //     fn serialized_item_kind() -> Option<&'static str> {
+    //         None
+    //     }
+
+    //     fn deserialize(
+    //         _project: ModelHandle<Project>,
+    //         _workspace: WeakViewHandle<Workspace>,
+    //         _workspace_id: WorkspaceId,
+    //         _item_id: ItemId,
+    //         _cx: &mut ViewContext<Pane>,
+    //     ) -> Task<Result<ViewHandle<Self>>> {
+    //         unimplemented!(
+    //             "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
+    //         )
+    //     }
+    //     fn show_toolbar(&self) -> bool {
+    //         true
+    //     }
+    //     fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
+    //         None
+    //     }
 }
 
-// pub trait WeakItemHandle {
-//     fn id(&self) -> usize;
-//     fn window(&self) -> AnyWindowHandle;
-//     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
-// }
+use core::fmt;
+use std::{
+    any::{Any, TypeId},
+    borrow::Cow,
+    ops::Range,
+    path::PathBuf,
+    sync::Arc,
+};
+
+use gpui2::{
+    AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels,
+    Point, Task, View, ViewContext, WindowContext,
+};
+use project2::{Project, ProjectEntryId, ProjectPath};
+use smallvec::SmallVec;
+
+use crate::{
+    pane::Pane, searchable::SearchableItemHandle, ToolbarItemLocation, Workspace, WorkspaceId,
+};
+
+pub trait ItemHandle: 'static + fmt::Debug + Send {
+    fn subscribe_to_item_events(
+        &self,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
+    ) -> gpui2::Subscription;
+    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
+    fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
+    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane>;
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace>;
+    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
+    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
+    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
+    fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item));
+    fn is_singleton(&self, cx: &AppContext) -> bool;
+    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
+    fn clone_on_split(
+        &self,
+        workspace_id: WorkspaceId,
+        cx: &mut WindowContext,
+    ) -> Option<Box<dyn ItemHandle>>;
+    fn added_to_pane(
+        &self,
+        workspace: &mut Workspace,
+        pane: View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    );
+    fn deactivated(&self, cx: &mut WindowContext);
+    fn workspace_deactivated(&self, cx: &mut WindowContext);
+    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
+    fn id(&self) -> usize;
+    fn window(&self) -> AnyWindowHandle;
+    // fn as_any(&self) -> &AnyView; todo!()
+    fn is_dirty(&self, cx: &AppContext) -> bool;
+    fn has_conflict(&self, cx: &AppContext) -> bool;
+    fn can_save(&self, cx: &AppContext) -> bool;
+    fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    fn save_as(
+        &self,
+        project: Handle<Project>,
+        abs_path: PathBuf,
+        cx: &mut WindowContext,
+    ) -> Task<Result<()>>;
+    fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!()
+    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
+    fn on_release(
+        &self,
+        cx: &mut AppContext,
+        callback: Box<dyn FnOnce(&mut AppContext)>,
+    ) -> gpui2::Subscription;
+    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
+    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
+    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
+    fn serialized_item_kind(&self) -> Option<&'static str>;
+    fn show_toolbar(&self, cx: &AppContext) -> bool;
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
+}
+
+pub trait WeakItemHandle {
+    fn id(&self) -> usize;
+    fn window(&self) -> AnyWindowHandle;
+    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+}
 
+// todo!()
 // impl dyn ItemHandle {
 //     pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
 //         self.as_any().clone().downcast()
@@ -653,11 +667,11 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
 //     }
 // }
 
-// impl Clone for Box<dyn ItemHandle> {
-//     fn clone(&self) -> Box<dyn ItemHandle> {
-//         self.boxed_clone()
-//     }
-// }
+impl Clone for Box<dyn ItemHandle> {
+    fn clone(&self) -> Box<dyn ItemHandle> {
+        self.boxed_clone()
+    }
+}
 
 // impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
 //     fn id(&self) -> usize {
@@ -673,63 +687,65 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
 //     }
 // }
 
-// pub trait ProjectItem: Item {
-//     type Item: project2::Item + gpui2::Entity;
+pub trait ProjectItem: Item {
+    type Item: project2::Item;
 
-//     fn for_project_item(
-//         project: ModelHandle<Project>,
-//         item: ModelHandle<Self::Item>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Self;
-// }
-
-// pub trait FollowableItem: Item {
-//     fn remote_id(&self) -> Option<ViewId>;
-//     fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
-//     fn from_state_proto(
-//         pane: ViewHandle<Pane>,
-//         project: ViewHandle<Workspace>,
-//         id: ViewId,
-//         state: &mut Option<proto::view::Variant>,
-//         cx: &mut AppContext,
-//     ) -> Option<Task<Result<ViewHandle<Self>>>>;
-//     fn add_event_to_update_proto(
-//         &self,
-//         event: &Self::Event,
-//         update: &mut Option<proto::update_view::Variant>,
-//         cx: &AppContext,
-//     ) -> bool;
-//     fn apply_update_proto(
-//         &mut self,
-//         project: &ModelHandle<Project>,
-//         message: proto::update_view::Variant,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<()>>;
-//     fn is_project_item(&self, cx: &AppContext) -> bool;
+    fn for_project_item(
+        project: Handle<Project>,
+        item: Handle<Self::Item>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self
+    where
+        Self: Sized;
+}
 
-//     fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
-//     fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
-// }
+pub trait FollowableItem: Item {
+    fn remote_id(&self) -> Option<ViewId>;
+    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn from_state_proto(
+        pane: View<Pane>,
+        project: View<Workspace>,
+        id: ViewId,
+        state: &mut Option<proto::view::Variant>,
+        cx: &mut AppContext,
+    ) -> Option<Task<Result<View<Self>>>>;
+    fn add_event_to_update_proto(
+        &self,
+        event: &Self::Event,
+        update: &mut Option<proto::update_view::Variant>,
+        cx: &AppContext,
+    ) -> bool;
+    fn apply_update_proto(
+        &mut self,
+        project: &Handle<Project>,
+        message: proto::update_view::Variant,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<()>>;
+    fn is_project_item(&self, cx: &AppContext) -> bool;
+
+    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
+    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
+}
 
-// pub trait FollowableItemHandle: ItemHandle {
-//     fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
-//     fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
-//     fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
-//     fn add_event_to_update_proto(
-//         &self,
-//         event: &dyn Any,
-//         update: &mut Option<proto::update_view::Variant>,
-//         cx: &AppContext,
-//     ) -> bool;
-//     fn apply_update_proto(
-//         &self,
-//         project: &ModelHandle<Project>,
-//         message: proto::update_view::Variant,
-//         cx: &mut WindowContext,
-//     ) -> Task<Result<()>>;
-//     fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
-//     fn is_project_item(&self, cx: &AppContext) -> bool;
-// }
+pub trait FollowableItemHandle: ItemHandle {
+    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
+    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn add_event_to_update_proto(
+        &self,
+        event: &dyn Any,
+        update: &mut Option<proto::update_view::Variant>,
+        cx: &AppContext,
+    ) -> bool;
+    fn apply_update_proto(
+        &self,
+        project: &Handle<Project>,
+        message: proto::update_view::Variant,
+        cx: &mut WindowContext,
+    ) -> Task<Result<()>>;
+    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &AppContext) -> bool;
+}
 
 // impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
 //     fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
@@ -981,9 +997,9 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
 //                 .for_each(|item| f(item.id(), item.read(cx)))
 //         }
 
-//         fn is_singleton(&self, _: &AppContext) -> bool {
-//             self.is_singleton
-//         }
+// fn is_singleton(&self, _: &AppContext) -> bool {
+//     self.is_singleton
+// }
 
 //         fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
 //             self.nav_history = Some(history);

crates/workspace2/src/pane.rs 🔗

@@ -1,150 +1,150 @@
-mod dragged_item_receiver;
-
-use super::{ItemHandle, SplitDirection};
-pub use crate::toolbar::Toolbar;
-use crate::{
-    item::{ItemSettings, WeakItemHandle},
-    notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
-    Workspace, WorkspaceSettings,
-};
-use anyhow::Result;
-use collections::{HashMap, HashSet, VecDeque};
-// use context_menu::{ContextMenu, ContextMenuItem};
-
-use dragged_item_receiver::dragged_item_receiver;
-use fs2::repository::GitFileStatus;
-use futures::StreamExt;
-use gpui2::{
-    actions,
-    elements::*,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    impl_actions,
-    keymap_matcher::KeymapContext,
-    platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
-    Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-    ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
-    WindowContext,
-};
-use project2::{Project, ProjectEntryId, ProjectPath};
-use serde::Deserialize;
-use std::{
-    any::Any,
-    cell::RefCell,
-    cmp, mem,
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::{
-        atomic::{AtomicUsize, Ordering},
-        Arc,
-    },
-};
-use theme2::{Theme, ThemeSettings};
-use util::truncate_and_remove_front;
-
-#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub enum SaveIntent {
-    /// write all files (even if unchanged)
-    /// prompt before overwriting on-disk changes
-    Save,
-    /// write any files that have local changes
-    /// prompt before overwriting on-disk changes
-    SaveAll,
-    /// always prompt for a new path
-    SaveAs,
-    /// prompt "you have unsaved changes" before writing
-    Close,
-    /// write all dirty files, don't prompt on conflict
-    Overwrite,
-    /// skip all save-related behavior
-    Skip,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct ActivateItem(pub usize);
-
-#[derive(Clone, PartialEq)]
-pub struct CloseItemById {
-    pub item_id: usize,
-    pub pane: WeakViewHandle<Pane>,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct CloseItemsToTheLeftById {
-    pub item_id: usize,
-    pub pane: WeakViewHandle<Pane>,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct CloseItemsToTheRightById {
-    pub item_id: usize,
-    pub pane: WeakViewHandle<Pane>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseActiveItem {
-    pub save_intent: Option<SaveIntent>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseAllItems {
-    pub save_intent: Option<SaveIntent>,
-}
-
-actions!(
-    pane,
-    [
-        ActivatePrevItem,
-        ActivateNextItem,
-        ActivateLastItem,
-        CloseInactiveItems,
-        CloseCleanItems,
-        CloseItemsToTheLeft,
-        CloseItemsToTheRight,
-        GoBack,
-        GoForward,
-        ReopenClosedItem,
-        SplitLeft,
-        SplitUp,
-        SplitRight,
-        SplitDown,
-    ]
-);
-
-impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
-
-const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(Pane::toggle_zoom);
-    cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
-        pane.activate_item(action.0, true, true, cx);
-    });
-    cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
-        pane.activate_item(pane.items.len() - 1, true, true, cx);
-    });
-    cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
-        pane.activate_prev_item(true, cx);
-    });
-    cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
-        pane.activate_next_item(true, cx);
-    });
-    cx.add_async_action(Pane::close_active_item);
-    cx.add_async_action(Pane::close_inactive_items);
-    cx.add_async_action(Pane::close_clean_items);
-    cx.add_async_action(Pane::close_items_to_the_left);
-    cx.add_async_action(Pane::close_items_to_the_right);
-    cx.add_async_action(Pane::close_all_items);
-    cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
-    cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
-    cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
-    cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
-}
+// mod dragged_item_receiver;
+
+// use super::{ItemHandle, SplitDirection};
+// pub use crate::toolbar::Toolbar;
+// use crate::{
+//     item::{ItemSettings, WeakItemHandle},
+//     notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
+//     Workspace, WorkspaceSettings,
+// };
+// use anyhow::Result;
+// use collections::{HashMap, HashSet, VecDeque};
+// // use context_menu::{ContextMenu, ContextMenuItem};
+
+// use dragged_item_receiver::dragged_item_receiver;
+// use fs2::repository::GitFileStatus;
+// use futures::StreamExt;
+// use gpui2::{
+//     actions,
+//     elements::*,
+//     geometry::{
+//         rect::RectF,
+//         vector::{vec2f, Vector2F},
+//     },
+//     impl_actions,
+//     keymap_matcher::KeymapContext,
+//     platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
+//     Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
+//     ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+//     WindowContext,
+// };
+// use project2::{Project, ProjectEntryId, ProjectPath};
+// use serde::Deserialize;
+// use std::{
+//     any::Any,
+//     cell::RefCell,
+//     cmp, mem,
+//     path::{Path, PathBuf},
+//     rc::Rc,
+//     sync::{
+//         atomic::{AtomicUsize, Ordering},
+//         Arc,
+//     },
+// };
+// use theme2::{Theme, ThemeSettings};
+// use util::truncate_and_remove_front;
+
+// #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
+// #[serde(rename_all = "camelCase")]
+// pub enum SaveIntent {
+//     /// write all files (even if unchanged)
+//     /// prompt before overwriting on-disk changes
+//     Save,
+//     /// write any files that have local changes
+//     /// prompt before overwriting on-disk changes
+//     SaveAll,
+//     /// always prompt for a new path
+//     SaveAs,
+//     /// prompt "you have unsaved changes" before writing
+//     Close,
+//     /// write all dirty files, don't prompt on conflict
+//     Overwrite,
+//     /// skip all save-related behavior
+//     Skip,
+// }
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct ActivateItem(pub usize);
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemById {
+//     pub item_id: usize,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheLeftById {
+//     pub item_id: usize,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheRightById {
+//     pub item_id: usize,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseActiveItem {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseAllItems {
+//     pub save_intent: Option<SaveIntent>,
+// }
+
+// actions!(
+//     pane,
+//     [
+//         ActivatePrevItem,
+//         ActivateNextItem,
+//         ActivateLastItem,
+//         CloseInactiveItems,
+//         CloseCleanItems,
+//         CloseItemsToTheLeft,
+//         CloseItemsToTheRight,
+//         GoBack,
+//         GoForward,
+//         ReopenClosedItem,
+//         SplitLeft,
+//         SplitUp,
+//         SplitRight,
+//         SplitDown,
+//     ]
+// );
+
+// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
+
+// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
+
+// pub fn init(cx: &mut AppContext) {
+//     cx.add_action(Pane::toggle_zoom);
+//     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
+//         pane.activate_item(action.0, true, true, cx);
+//     });
+//     cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
+//         pane.activate_item(pane.items.len() - 1, true, true, cx);
+//     });
+//     cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
+//         pane.activate_prev_item(true, cx);
+//     });
+//     cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
+//         pane.activate_next_item(true, cx);
+//     });
+//     cx.add_async_action(Pane::close_active_item);
+//     cx.add_async_action(Pane::close_inactive_items);
+//     cx.add_async_action(Pane::close_clean_items);
+//     cx.add_async_action(Pane::close_items_to_the_left);
+//     cx.add_async_action(Pane::close_items_to_the_right);
+//     cx.add_async_action(Pane::close_all_items);
+//     cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
+//     cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
+//     cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
+//     cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
+// }
 
 #[derive(Debug)]
 pub enum Event {
@@ -159,23 +159,36 @@ pub enum Event {
     ZoomOut,
 }
 
+use crate::item::{ItemHandle, WeakItemHandle};
+use collections::{HashMap, VecDeque};
+use gpui2::{Handle, ViewContext, WeakView};
+use project2::{Project, ProjectEntryId, ProjectPath};
+use std::{
+    any::Any,
+    cell::RefCell,
+    cmp, mem,
+    path::PathBuf,
+    rc::Rc,
+    sync::{atomic::AtomicUsize, Arc},
+};
+
 pub struct Pane {
     items: Vec<Box<dyn ItemHandle>>,
-    activation_history: Vec<usize>,
-    zoomed: bool,
-    active_item_index: usize,
-    last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
-    autoscroll: bool,
+    //     activation_history: Vec<usize>,
+    //     zoomed: bool,
+    //     active_item_index: usize,
+    //     last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
+    //     autoscroll: bool,
     nav_history: NavHistory,
-    toolbar: ViewHandle<Toolbar>,
-    tab_bar_context_menu: TabBarContextMenu,
-    tab_context_menu: ViewHandle<ContextMenu>,
-    workspace: WeakViewHandle<Workspace>,
-    project: ModelHandle<Project>,
-    has_focus: bool,
-    can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
-    can_split: bool,
-    render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
+    //     toolbar: ViewHandle<Toolbar>,
+    //     tab_bar_context_menu: TabBarContextMenu,
+    //     tab_context_menu: ViewHandle<ContextMenu>,
+    //     workspace: WeakViewHandle<Workspace>,
+    project: Handle<Project>,
+    //     has_focus: bool,
+    //     can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
+    //     can_split: bool,
+    //     render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
 }
 
 pub struct ItemNavHistory {
@@ -192,7 +205,7 @@ struct NavHistoryState {
     forward_stack: VecDeque<NavigationEntry>,
     closed_stack: VecDeque<NavigationEntry>,
     paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
-    pane: WeakViewHandle<Pane>,
+    pane: WeakView<Pane>,
     next_timestamp: Arc<AtomicUsize>,
 }
 
@@ -218,261 +231,261 @@ pub struct NavigationEntry {
     pub timestamp: usize,
 }
 
-pub struct DraggedItem {
-    pub handle: Box<dyn ItemHandle>,
-    pub pane: WeakViewHandle<Pane>,
-}
-
-pub enum ReorderBehavior {
-    None,
-    MoveAfterActive,
-    MoveToIndex(usize),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum TabBarContextMenuKind {
-    New,
-    Split,
-}
-
-struct TabBarContextMenu {
-    kind: TabBarContextMenuKind,
-    handle: ViewHandle<ContextMenu>,
-}
-
-impl TabBarContextMenu {
-    fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
-        if self.kind == kind {
-            return Some(self.handle.clone());
-        }
-        None
-    }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
-    svg_path: &'static str,
-    style: theme2::Interactive<theme2::IconButton>,
-    nav_button_height: f32,
-    tooltip_style: TooltipStyle,
-    enabled: bool,
-    on_click: F,
-    tooltip_action: A,
-    action_name: &str,
-    cx: &mut ViewContext<Pane>,
-) -> AnyElement<Pane> {
-    MouseEventHandler::new::<A, _>(0, cx, |state, _| {
-        let style = if enabled {
-            style.style_for(state)
-        } else {
-            style.disabled_style()
-        };
-        Svg::new(svg_path)
-            .with_color(style.color)
-            .constrained()
-            .with_width(style.icon_width)
-            .aligned()
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_width(style.button_width)
-            .with_height(nav_button_height)
-            .aligned()
-            .top()
-    })
-    .with_cursor_style(if enabled {
-        CursorStyle::PointingHand
-    } else {
-        CursorStyle::default()
-    })
-    .on_click(MouseButton::Left, move |_, toolbar, cx| {
-        on_click(toolbar, cx)
-    })
-    .with_tooltip::<A>(
-        0,
-        action_name.to_string(),
-        Some(Box::new(tooltip_action)),
-        tooltip_style,
-        cx,
-    )
-    .contained()
-    .into_any_named("nav button")
-}
+// pub struct DraggedItem {
+//     pub handle: Box<dyn ItemHandle>,
+//     pub pane: WeakViewHandle<Pane>,
+// }
+
+// pub enum ReorderBehavior {
+//     None,
+//     MoveAfterActive,
+//     MoveToIndex(usize),
+// }
+
+// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// enum TabBarContextMenuKind {
+//     New,
+//     Split,
+// }
+
+// struct TabBarContextMenu {
+//     kind: TabBarContextMenuKind,
+//     handle: ViewHandle<ContextMenu>,
+// }
+
+// impl TabBarContextMenu {
+//     fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
+//         if self.kind == kind {
+//             return Some(self.handle.clone());
+//         }
+//         None
+//     }
+// }
+
+// #[allow(clippy::too_many_arguments)]
+// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
+//     svg_path: &'static str,
+//     style: theme2::Interactive<theme2::IconButton>,
+//     nav_button_height: f32,
+//     tooltip_style: TooltipStyle,
+//     enabled: bool,
+//     on_click: F,
+//     tooltip_action: A,
+//     action_name: &str,
+//     cx: &mut ViewContext<Pane>,
+// ) -> AnyElement<Pane> {
+//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
+//         let style = if enabled {
+//             style.style_for(state)
+//         } else {
+//             style.disabled_style()
+//         };
+//         Svg::new(svg_path)
+//             .with_color(style.color)
+//             .constrained()
+//             .with_width(style.icon_width)
+//             .aligned()
+//             .contained()
+//             .with_style(style.container)
+//             .constrained()
+//             .with_width(style.button_width)
+//             .with_height(nav_button_height)
+//             .aligned()
+//             .top()
+//     })
+//     .with_cursor_style(if enabled {
+//         CursorStyle::PointingHand
+//     } else {
+//         CursorStyle::default()
+//     })
+//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
+//         on_click(toolbar, cx)
+//     })
+//     .with_tooltip::<A>(
+//         0,
+//         action_name.to_string(),
+//         Some(Box::new(tooltip_action)),
+//         tooltip_style,
+//         cx,
+//     )
+//     .contained()
+//     .into_any_named("nav button")
+// }
 
 impl Pane {
-    pub fn new(
-        workspace: WeakViewHandle<Workspace>,
-        project: ModelHandle<Project>,
-        next_timestamp: Arc<AtomicUsize>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let pane_view_id = cx.view_id();
-        let handle = cx.weak_handle();
-        let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
-        context_menu.update(cx, |menu, _| {
-            menu.set_position_mode(OverlayPositionMode::Local)
-        });
-
-        Self {
-            items: Vec::new(),
-            activation_history: Vec::new(),
-            zoomed: false,
-            active_item_index: 0,
-            last_focused_view_by_item: Default::default(),
-            autoscroll: false,
-            nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
-                mode: NavigationMode::Normal,
-                backward_stack: Default::default(),
-                forward_stack: Default::default(),
-                closed_stack: Default::default(),
-                paths_by_item: Default::default(),
-                pane: handle.clone(),
-                next_timestamp,
-            }))),
-            toolbar: cx.add_view(|_| Toolbar::new()),
-            tab_bar_context_menu: TabBarContextMenu {
-                kind: TabBarContextMenuKind::New,
-                handle: context_menu,
-            },
-            tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
-            workspace,
-            project,
-            has_focus: false,
-            can_drop: Rc::new(|_, _| true),
-            can_split: true,
-            render_tab_bar_buttons: Rc::new(move |pane, cx| {
-                Flex::row()
-                    // New menu
-                    .with_child(Self::render_tab_bar_button(
-                        0,
-                        "icons/plus.svg",
-                        false,
-                        Some(("New...".into(), None)),
-                        cx,
-                        |pane, cx| pane.deploy_new_menu(cx),
-                        |pane, cx| {
-                            pane.tab_bar_context_menu
-                                .handle
-                                .update(cx, |menu, _| menu.delay_cancel())
-                        },
-                        pane.tab_bar_context_menu
-                            .handle_if_kind(TabBarContextMenuKind::New),
-                    ))
-                    .with_child(Self::render_tab_bar_button(
-                        1,
-                        "icons/split.svg",
-                        false,
-                        Some(("Split Pane".into(), None)),
-                        cx,
-                        |pane, cx| pane.deploy_split_menu(cx),
-                        |pane, cx| {
-                            pane.tab_bar_context_menu
-                                .handle
-                                .update(cx, |menu, _| menu.delay_cancel())
-                        },
-                        pane.tab_bar_context_menu
-                            .handle_if_kind(TabBarContextMenuKind::Split),
-                    ))
-                    .with_child({
-                        let icon_path;
-                        let tooltip_label;
-                        if pane.is_zoomed() {
-                            icon_path = "icons/minimize.svg";
-                            tooltip_label = "Zoom In";
-                        } else {
-                            icon_path = "icons/maximize.svg";
-                            tooltip_label = "Zoom In";
-                        }
-
-                        Pane::render_tab_bar_button(
-                            2,
-                            icon_path,
-                            pane.is_zoomed(),
-                            Some((tooltip_label, Some(Box::new(ToggleZoom)))),
-                            cx,
-                            move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
-                            move |_, _| {},
-                            None,
-                        )
-                    })
-                    .into_any()
-            }),
-        }
-    }
-
-    pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
-        &self.workspace
-    }
-
-    pub fn has_focus(&self) -> bool {
-        self.has_focus
-    }
-
-    pub fn active_item_index(&self) -> usize {
-        self.active_item_index
-    }
-
-    pub fn on_can_drop<F>(&mut self, can_drop: F)
-    where
-        F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
-    {
-        self.can_drop = Rc::new(can_drop);
-    }
-
-    pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
-        self.can_split = can_split;
-        cx.notify();
-    }
-
-    pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.set_can_navigate(can_navigate, cx);
-        });
-        cx.notify();
-    }
-
-    pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
-    where
-        F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
-    {
-        self.render_tab_bar_buttons = Rc::new(render);
-        cx.notify();
-    }
-
-    pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
-        ItemNavHistory {
-            history: self.nav_history.clone(),
-            item: Rc::new(item.downgrade()),
-        }
-    }
-
-    pub fn nav_history(&self) -> &NavHistory {
-        &self.nav_history
-    }
-
-    pub fn nav_history_mut(&mut self) -> &mut NavHistory {
-        &mut self.nav_history
-    }
-
-    pub fn disable_history(&mut self) {
-        self.nav_history.disable();
-    }
-
-    pub fn enable_history(&mut self) {
-        self.nav_history.enable();
-    }
-
-    pub fn can_navigate_backward(&self) -> bool {
-        !self.nav_history.0.borrow().backward_stack.is_empty()
-    }
-
-    pub fn can_navigate_forward(&self) -> bool {
-        !self.nav_history.0.borrow().forward_stack.is_empty()
-    }
-
-    fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
-        self.toolbar.update(cx, |_, cx| cx.notify());
-    }
+    //     pub fn new(
+    //         workspace: WeakViewHandle<Workspace>,
+    //         project: ModelHandle<Project>,
+    //         next_timestamp: Arc<AtomicUsize>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Self {
+    //         let pane_view_id = cx.view_id();
+    //         let handle = cx.weak_handle();
+    //         let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
+    //         context_menu.update(cx, |menu, _| {
+    //             menu.set_position_mode(OverlayPositionMode::Local)
+    //         });
+
+    //         Self {
+    //             items: Vec::new(),
+    //             activation_history: Vec::new(),
+    //             zoomed: false,
+    //             active_item_index: 0,
+    //             last_focused_view_by_item: Default::default(),
+    //             autoscroll: false,
+    //             nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
+    //                 mode: NavigationMode::Normal,
+    //                 backward_stack: Default::default(),
+    //                 forward_stack: Default::default(),
+    //                 closed_stack: Default::default(),
+    //                 paths_by_item: Default::default(),
+    //                 pane: handle.clone(),
+    //                 next_timestamp,
+    //             }))),
+    //             toolbar: cx.add_view(|_| Toolbar::new()),
+    //             tab_bar_context_menu: TabBarContextMenu {
+    //                 kind: TabBarContextMenuKind::New,
+    //                 handle: context_menu,
+    //             },
+    //             tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
+    //             workspace,
+    //             project,
+    //             has_focus: false,
+    //             can_drop: Rc::new(|_, _| true),
+    //             can_split: true,
+    //             render_tab_bar_buttons: Rc::new(move |pane, cx| {
+    //                 Flex::row()
+    //                     // New menu
+    //                     .with_child(Self::render_tab_bar_button(
+    //                         0,
+    //                         "icons/plus.svg",
+    //                         false,
+    //                         Some(("New...".into(), None)),
+    //                         cx,
+    //                         |pane, cx| pane.deploy_new_menu(cx),
+    //                         |pane, cx| {
+    //                             pane.tab_bar_context_menu
+    //                                 .handle
+    //                                 .update(cx, |menu, _| menu.delay_cancel())
+    //                         },
+    //                         pane.tab_bar_context_menu
+    //                             .handle_if_kind(TabBarContextMenuKind::New),
+    //                     ))
+    //                     .with_child(Self::render_tab_bar_button(
+    //                         1,
+    //                         "icons/split.svg",
+    //                         false,
+    //                         Some(("Split Pane".into(), None)),
+    //                         cx,
+    //                         |pane, cx| pane.deploy_split_menu(cx),
+    //                         |pane, cx| {
+    //                             pane.tab_bar_context_menu
+    //                                 .handle
+    //                                 .update(cx, |menu, _| menu.delay_cancel())
+    //                         },
+    //                         pane.tab_bar_context_menu
+    //                             .handle_if_kind(TabBarContextMenuKind::Split),
+    //                     ))
+    //                     .with_child({
+    //                         let icon_path;
+    //                         let tooltip_label;
+    //                         if pane.is_zoomed() {
+    //                             icon_path = "icons/minimize.svg";
+    //                             tooltip_label = "Zoom In";
+    //                         } else {
+    //                             icon_path = "icons/maximize.svg";
+    //                             tooltip_label = "Zoom In";
+    //                         }
+
+    //                         Pane::render_tab_bar_button(
+    //                             2,
+    //                             icon_path,
+    //                             pane.is_zoomed(),
+    //                             Some((tooltip_label, Some(Box::new(ToggleZoom)))),
+    //                             cx,
+    //                             move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
+    //                             move |_, _| {},
+    //                             None,
+    //                         )
+    //                     })
+    //                     .into_any()
+    //             }),
+    //         }
+    //     }
+
+    //     pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
+    //         &self.workspace
+    //     }
+
+    //     pub fn has_focus(&self) -> bool {
+    //         self.has_focus
+    //     }
+
+    //     pub fn active_item_index(&self) -> usize {
+    //         self.active_item_index
+    //     }
+
+    //     pub fn on_can_drop<F>(&mut self, can_drop: F)
+    //     where
+    //         F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
+    //     {
+    //         self.can_drop = Rc::new(can_drop);
+    //     }
+
+    //     pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
+    //         self.can_split = can_split;
+    //         cx.notify();
+    //     }
+
+    //     pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.set_can_navigate(can_navigate, cx);
+    //         });
+    //         cx.notify();
+    //     }
+
+    //     pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
+    //     where
+    //         F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
+    //     {
+    //         self.render_tab_bar_buttons = Rc::new(render);
+    //         cx.notify();
+    //     }
+
+    //     pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
+    //         ItemNavHistory {
+    //             history: self.nav_history.clone(),
+    //             item: Rc::new(item.downgrade()),
+    //         }
+    //     }
+
+    //     pub fn nav_history(&self) -> &NavHistory {
+    //         &self.nav_history
+    //     }
+
+    //     pub fn nav_history_mut(&mut self) -> &mut NavHistory {
+    //         &mut self.nav_history
+    //     }
+
+    //     pub fn disable_history(&mut self) {
+    //         self.nav_history.disable();
+    //     }
+
+    //     pub fn enable_history(&mut self) {
+    //         self.nav_history.enable();
+    //     }
+
+    //     pub fn can_navigate_backward(&self) -> bool {
+    //         !self.nav_history.0.borrow().backward_stack.is_empty()
+    //     }
+
+    //     pub fn can_navigate_forward(&self) -> bool {
+    //         !self.nav_history.0.borrow().forward_stack.is_empty()
+    //     }
+
+    //     fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.toolbar.update(cx, |_, cx| cx.notify());
+    //     }
 
     pub(crate) fn open_item(
         &mut self,
@@ -599,63 +612,63 @@ impl Pane {
         cx.emit(Event::AddItem { item });
     }
 
-    pub fn items_len(&self) -> usize {
-        self.items.len()
-    }
-
-    pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
-        self.items.iter()
-    }
-
-    pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
-        self.items
-            .iter()
-            .filter_map(|item| item.as_any().clone().downcast())
-    }
-
-    pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
-        self.items.get(self.active_item_index).cloned()
-    }
-
-    pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
-        self.items
-            .get(self.active_item_index)?
-            .pixel_position_of_cursor(cx)
-    }
-
-    pub fn item_for_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &AppContext,
-    ) -> Option<Box<dyn ItemHandle>> {
-        self.items.iter().find_map(|item| {
-            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
-                Some(item.boxed_clone())
-            } else {
-                None
-            }
-        })
-    }
-
-    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
-        self.items.iter().position(|i| i.id() == item.id())
-    }
-
-    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-        // Potentially warn the user of the new keybinding
-        let workspace_handle = self.workspace().clone();
-        cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
-            .detach();
-
-        if self.zoomed {
-            cx.emit(Event::ZoomOut);
-        } else if !self.items.is_empty() {
-            if !self.has_focus {
-                cx.focus_self();
-            }
-            cx.emit(Event::ZoomIn);
-        }
-    }
+    //     pub fn items_len(&self) -> usize {
+    //         self.items.len()
+    //     }
+
+    //     pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
+    //         self.items.iter()
+    //     }
+
+    //     pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
+    //         self.items
+    //             .iter()
+    //             .filter_map(|item| item.as_any().clone().downcast())
+    //     }
+
+    //     pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
+    //         self.items.get(self.active_item_index).cloned()
+    //     }
+
+    //     pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
+    //         self.items
+    //             .get(self.active_item_index)?
+    //             .pixel_position_of_cursor(cx)
+    //     }
+
+    //     pub fn item_for_entry(
+    //         &self,
+    //         entry_id: ProjectEntryId,
+    //         cx: &AppContext,
+    //     ) -> Option<Box<dyn ItemHandle>> {
+    //         self.items.iter().find_map(|item| {
+    //             if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+    //                 Some(item.boxed_clone())
+    //             } else {
+    //                 None
+    //             }
+    //         })
+    //     }
+
+    //     pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
+    //         self.items.iter().position(|i| i.id() == item.id())
+    //     }
+
+    //     pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
+    //         // Potentially warn the user of the new keybinding
+    //         let workspace_handle = self.workspace().clone();
+    //         cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
+    //             .detach();
+
+    //         if self.zoomed {
+    //             cx.emit(Event::ZoomOut);
+    //         } else if !self.items.is_empty() {
+    //             if !self.has_focus {
+    //                 cx.focus_self();
+    //             }
+    //             cx.emit(Event::ZoomIn);
+    //         }
+    //     }
 
     pub fn activate_item(
         &mut self,
@@ -699,2039 +712,2040 @@ impl Pane {
         }
     }
 
-    pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
-        let mut index = self.active_item_index;
-        if index > 0 {
-            index -= 1;
-        } else if !self.items.is_empty() {
-            index = self.items.len() - 1;
-        }
-        self.activate_item(index, activate_pane, activate_pane, cx);
-    }
-
-    pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
-        let mut index = self.active_item_index;
-        if index + 1 < self.items.len() {
-            index += 1;
-        } else {
-            index = 0;
-        }
-        self.activate_item(index, activate_pane, activate_pane, cx);
-    }
-
-    pub fn close_active_item(
-        &mut self,
-        action: &CloseActiveItem,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-        let active_item_id = self.items[self.active_item_index].id();
-        Some(self.close_item_by_id(
-            active_item_id,
-            action.save_intent.unwrap_or(SaveIntent::Close),
-            cx,
-        ))
-    }
-
-    pub fn close_item_by_id(
-        &mut self,
-        item_id_to_close: usize,
-        save_intent: SaveIntent,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
-    }
-
-    pub fn close_inactive_items(
-        &mut self,
-        _: &CloseInactiveItems,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-
-        let active_item_id = self.items[self.active_item_index].id();
-        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_id != active_item_id
-        }))
-    }
-
-    pub fn close_clean_items(
-        &mut self,
-        _: &CloseCleanItems,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let item_ids: Vec<_> = self
-            .items()
-            .filter(|item| !item.is_dirty(cx))
-            .map(|item| item.id())
-            .collect();
-        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_ids.contains(&item_id)
-        }))
-    }
-
-    pub fn close_items_to_the_left(
-        &mut self,
-        _: &CloseItemsToTheLeft,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-        let active_item_id = self.items[self.active_item_index].id();
-        Some(self.close_items_to_the_left_by_id(active_item_id, cx))
-    }
-
-    pub fn close_items_to_the_left_by_id(
-        &mut self,
-        item_id: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let item_ids: Vec<_> = self
-            .items()
-            .take_while(|item| item.id() != item_id)
-            .map(|item| item.id())
-            .collect();
-        self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_ids.contains(&item_id)
-        })
-    }
-
-    pub fn close_items_to_the_right(
-        &mut self,
-        _: &CloseItemsToTheRight,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-        let active_item_id = self.items[self.active_item_index].id();
-        Some(self.close_items_to_the_right_by_id(active_item_id, cx))
-    }
-
-    pub fn close_items_to_the_right_by_id(
-        &mut self,
-        item_id: usize,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let item_ids: Vec<_> = self
-            .items()
-            .rev()
-            .take_while(|item| item.id() != item_id)
-            .map(|item| item.id())
-            .collect();
-        self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_ids.contains(&item_id)
-        })
-    }
-
-    pub fn close_all_items(
-        &mut self,
-        action: &CloseAllItems,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-
-        Some(
-            self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
-                true
-            }),
-        )
-    }
-
-    pub(super) fn file_names_for_prompt(
-        items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
-        all_dirty_items: usize,
-        cx: &AppContext,
-    ) -> String {
-        /// Quantity of item paths displayed in prompt prior to cutoff..
-        const FILE_NAMES_CUTOFF_POINT: usize = 10;
-        let mut file_names: Vec<_> = items
-            .filter_map(|item| {
-                item.project_path(cx).and_then(|project_path| {
-                    project_path
-                        .path
-                        .file_name()
-                        .and_then(|name| name.to_str().map(ToOwned::to_owned))
-                })
-            })
-            .take(FILE_NAMES_CUTOFF_POINT)
-            .collect();
-        let should_display_followup_text =
-            all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
-        if should_display_followup_text {
-            let not_shown_files = all_dirty_items - file_names.len();
-            if not_shown_files == 1 {
-                file_names.push(".. 1 file not shown".into());
-            } else {
-                file_names.push(format!(".. {} files not shown", not_shown_files).into());
-            }
-        }
-        let file_names = file_names.join("\n");
-        format!(
-            "Do you want to save changes to the following {} files?\n{file_names}",
-            all_dirty_items
-        )
-    }
-
-    pub fn close_items(
-        &mut self,
-        cx: &mut ViewContext<Pane>,
-        mut save_intent: SaveIntent,
-        should_close: impl 'static + Fn(usize) -> bool,
-    ) -> Task<Result<()>> {
-        // Find the items to close.
-        let mut items_to_close = Vec::new();
-        let mut dirty_items = Vec::new();
-        for item in &self.items {
-            if should_close(item.id()) {
-                items_to_close.push(item.boxed_clone());
-                if item.is_dirty(cx) {
-                    dirty_items.push(item.boxed_clone());
-                }
-            }
-        }
-
-        // If a buffer is open both in a singleton editor and in a multibuffer, make sure
-        // to focus the singleton buffer when prompting to save that buffer, as opposed
-        // to focusing the multibuffer, because this gives the user a more clear idea
-        // of what content they would be saving.
-        items_to_close.sort_by_key(|item| !item.is_singleton(cx));
-
-        let workspace = self.workspace.clone();
-        cx.spawn(|pane, mut cx| async move {
-            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-                let mut answer = pane.update(&mut cx, |_, cx| {
-                    let prompt =
-                        Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
-                    cx.prompt(
-                        PromptLevel::Warning,
-                        &prompt,
-                        &["Save all", "Discard all", "Cancel"],
-                    )
-                })?;
-                match answer.next().await {
-                    Some(0) => save_intent = SaveIntent::SaveAll,
-                    Some(1) => save_intent = SaveIntent::Skip,
-                    _ => {}
-                }
-            }
-            let mut saved_project_items_ids = HashSet::default();
-            for item in items_to_close.clone() {
-                // Find the item's current index and its set of project item models. Avoid
-                // storing these in advance, in case they have changed since this task
-                // was started.
-                let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
-                    (pane.index_for_item(&*item), item.project_item_model_ids(cx))
-                })?;
-                let item_ix = if let Some(ix) = item_ix {
-                    ix
-                } else {
-                    continue;
-                };
-
-                // Check if this view has any project items that are not open anywhere else
-                // in the workspace, AND that the user has not already been prompted to save.
-                // If there are any such project entries, prompt the user to save this item.
-                let project = workspace.read_with(&cx, |workspace, cx| {
-                    for item in workspace.items(cx) {
-                        if !items_to_close
-                            .iter()
-                            .any(|item_to_close| item_to_close.id() == item.id())
-                        {
-                            let other_project_item_ids = item.project_item_model_ids(cx);
-                            project_item_ids.retain(|id| !other_project_item_ids.contains(id));
-                        }
-                    }
-                    workspace.project().clone()
-                })?;
-                let should_save = project_item_ids
-                    .iter()
-                    .any(|id| saved_project_items_ids.insert(*id));
-
-                if should_save
-                    && !Self::save_item(
-                        project.clone(),
-                        &pane,
-                        item_ix,
-                        &*item,
-                        save_intent,
-                        &mut cx,
-                    )
-                    .await?
-                {
-                    break;
-                }
-
-                // Remove the item from the pane.
-                pane.update(&mut cx, |pane, cx| {
-                    if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
-                        pane.remove_item(item_ix, false, cx);
-                    }
-                })?;
-            }
-
-            pane.update(&mut cx, |_, cx| cx.notify())?;
-            Ok(())
-        })
-    }
-
-    pub fn remove_item(
-        &mut self,
-        item_index: usize,
-        activate_pane: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.activation_history
-            .retain(|&history_entry| history_entry != self.items[item_index].id());
-
-        if item_index == self.active_item_index {
-            let index_to_activate = self
-                .activation_history
-                .pop()
-                .and_then(|last_activated_item| {
-                    self.items.iter().enumerate().find_map(|(index, item)| {
-                        (item.id() == last_activated_item).then_some(index)
-                    })
-                })
-                // We didn't have a valid activation history entry, so fallback
-                // to activating the item to the left
-                .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
-
-            let should_activate = activate_pane || self.has_focus;
-            self.activate_item(index_to_activate, should_activate, should_activate, cx);
-        }
-
-        let item = self.items.remove(item_index);
-
-        cx.emit(Event::RemoveItem { item_id: item.id() });
-        if self.items.is_empty() {
-            item.deactivated(cx);
-            self.update_toolbar(cx);
-            cx.emit(Event::Remove);
-        }
-
-        if item_index < self.active_item_index {
-            self.active_item_index -= 1;
-        }
-
-        self.nav_history.set_mode(NavigationMode::ClosingItem);
-        item.deactivated(cx);
-        self.nav_history.set_mode(NavigationMode::Normal);
-
-        if let Some(path) = item.project_path(cx) {
-            let abs_path = self
-                .nav_history
-                .0
-                .borrow()
-                .paths_by_item
-                .get(&item.id())
-                .and_then(|(_, abs_path)| abs_path.clone());
-
-            self.nav_history
-                .0
-                .borrow_mut()
-                .paths_by_item
-                .insert(item.id(), (path, abs_path));
-        } else {
-            self.nav_history
-                .0
-                .borrow_mut()
-                .paths_by_item
-                .remove(&item.id());
-        }
-
-        if self.items.is_empty() && self.zoomed {
-            cx.emit(Event::ZoomOut);
-        }
-
-        cx.notify();
-    }
-
-    pub async fn save_item(
-        project: ModelHandle<Project>,
-        pane: &WeakViewHandle<Pane>,
-        item_ix: usize,
-        item: &dyn ItemHandle,
-        save_intent: SaveIntent,
-        cx: &mut AsyncAppContext,
-    ) -> Result<bool> {
-        const CONFLICT_MESSAGE: &str =
-            "This file has changed on disk since you started editing it. Do you want to overwrite it?";
-
-        if save_intent == SaveIntent::Skip {
-            return Ok(true);
-        }
-
-        let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
-            (
-                item.has_conflict(cx),
-                item.is_dirty(cx),
-                item.can_save(cx),
-                item.is_singleton(cx),
-            )
-        });
-
-        // when saving a single buffer, we ignore whether or not it's dirty.
-        if save_intent == SaveIntent::Save {
-            is_dirty = true;
-        }
-
-        if save_intent == SaveIntent::SaveAs {
-            is_dirty = true;
-            has_conflict = false;
-            can_save = false;
-        }
-
-        if save_intent == SaveIntent::Overwrite {
-            has_conflict = false;
-        }
-
-        if has_conflict && can_save {
-            let mut answer = pane.update(cx, |pane, cx| {
-                pane.activate_item(item_ix, true, true, cx);
-                cx.prompt(
-                    PromptLevel::Warning,
-                    CONFLICT_MESSAGE,
-                    &["Overwrite", "Discard", "Cancel"],
-                )
-            })?;
-            match answer.next().await {
-                Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
-                Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
-                _ => return Ok(false),
-            }
-        } else if is_dirty && (can_save || can_save_as) {
-            if save_intent == SaveIntent::Close {
-                let will_autosave = cx.read(|cx| {
-                    matches!(
-                        settings::get::<WorkspaceSettings>(cx).autosave,
-                        AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
-                    ) && Self::can_autosave_item(&*item, cx)
-                });
-                if !will_autosave {
-                    let mut answer = pane.update(cx, |pane, cx| {
-                        pane.activate_item(item_ix, true, true, cx);
-                        let prompt = dirty_message_for(item.project_path(cx));
-                        cx.prompt(
-                            PromptLevel::Warning,
-                            &prompt,
-                            &["Save", "Don't Save", "Cancel"],
-                        )
-                    })?;
-                    match answer.next().await {
-                        Some(0) => {}
-                        Some(1) => return Ok(true), // Don't save his file
-                        _ => return Ok(false),      // Cancel
-                    }
-                }
-            }
-
-            if can_save {
-                pane.update(cx, |_, cx| item.save(project, cx))?.await?;
-            } else if can_save_as {
-                let start_abs_path = project
-                    .read_with(cx, |project, cx| {
-                        let worktree = project.visible_worktrees(cx).next()?;
-                        Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
-                    })
-                    .unwrap_or_else(|| Path::new("").into());
-
-                let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
-                if let Some(abs_path) = abs_path.next().await.flatten() {
-                    pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
-                        .await?;
-                } else {
-                    return Ok(false);
-                }
-            }
-        }
-        Ok(true)
-    }
-
-    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
-        let is_deleted = item.project_entry_ids(cx).is_empty();
-        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
-    }
-
-    pub fn autosave_item(
-        item: &dyn ItemHandle,
-        project: ModelHandle<Project>,
-        cx: &mut WindowContext,
-    ) -> Task<Result<()>> {
-        if Self::can_autosave_item(item, cx) {
-            item.save(project, cx)
-        } else {
-            Task::ready(Ok(()))
-        }
-    }
-
-    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(active_item) = self.active_item() {
-            cx.focus(active_item.as_any());
-        }
-    }
-
-    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Split(direction));
-    }
-
-    fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
-        self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
-            menu.toggle(
-                Default::default(),
-                AnchorCorner::TopRight,
-                vec![
-                    ContextMenuItem::action("Split Right", SplitRight),
-                    ContextMenuItem::action("Split Left", SplitLeft),
-                    ContextMenuItem::action("Split Up", SplitUp),
-                    ContextMenuItem::action("Split Down", SplitDown),
-                ],
-                cx,
-            );
-        });
-
-        self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
-    }
-
-    fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
-        self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
-            menu.toggle(
-                Default::default(),
-                AnchorCorner::TopRight,
-                vec![
-                    ContextMenuItem::action("New File", NewFile),
-                    ContextMenuItem::action("New Terminal", NewCenterTerminal),
-                    ContextMenuItem::action("New Search", NewSearch),
-                ],
-                cx,
-            );
-        });
-
-        self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
-    }
-
-    fn deploy_tab_context_menu(
-        &mut self,
-        position: Vector2F,
-        target_item_id: usize,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let active_item_id = self.items[self.active_item_index].id();
-        let is_active_item = target_item_id == active_item_id;
-        let target_pane = cx.weak_handle();
-
-        // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on.  Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
-
-        self.tab_context_menu.update(cx, |menu, cx| {
-            menu.show(
-                position,
-                AnchorCorner::TopLeft,
-                if is_active_item {
-                    vec![
-                        ContextMenuItem::action(
-                            "Close Active Item",
-                            CloseActiveItem { save_intent: None },
-                        ),
-                        ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
-                        ContextMenuItem::action("Close Clean Items", CloseCleanItems),
-                        ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
-                        ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
-                        ContextMenuItem::action(
-                            "Close All Items",
-                            CloseAllItems { save_intent: None },
-                        ),
-                    ]
-                } else {
-                    // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
-                    vec![
-                        ContextMenuItem::handler("Close Inactive Item", {
-                            let pane = target_pane.clone();
-                            move |cx| {
-                                if let Some(pane) = pane.upgrade(cx) {
-                                    pane.update(cx, |pane, cx| {
-                                        pane.close_item_by_id(
-                                            target_item_id,
-                                            SaveIntent::Close,
-                                            cx,
-                                        )
-                                        .detach_and_log_err(cx);
-                                    })
-                                }
-                            }
-                        }),
-                        ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
-                        ContextMenuItem::action("Close Clean Items", CloseCleanItems),
-                        ContextMenuItem::handler("Close Items To The Left", {
-                            let pane = target_pane.clone();
-                            move |cx| {
-                                if let Some(pane) = pane.upgrade(cx) {
-                                    pane.update(cx, |pane, cx| {
-                                        pane.close_items_to_the_left_by_id(target_item_id, cx)
-                                            .detach_and_log_err(cx);
-                                    })
-                                }
-                            }
-                        }),
-                        ContextMenuItem::handler("Close Items To The Right", {
-                            let pane = target_pane.clone();
-                            move |cx| {
-                                if let Some(pane) = pane.upgrade(cx) {
-                                    pane.update(cx, |pane, cx| {
-                                        pane.close_items_to_the_right_by_id(target_item_id, cx)
-                                            .detach_and_log_err(cx);
-                                    })
-                                }
-                            }
-                        }),
-                        ContextMenuItem::action(
-                            "Close All Items",
-                            CloseAllItems { save_intent: None },
-                        ),
-                    ]
-                },
-                cx,
-            );
-        });
-    }
-
-    pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
-        &self.toolbar
-    }
-
-    pub fn handle_deleted_project_item(
-        &mut self,
-        entry_id: ProjectEntryId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Option<()> {
-        let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
-            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
-                Some((i, item.id()))
-            } else {
-                None
-            }
-        })?;
-
-        self.remove_item(item_index_to_delete, false, cx);
-        self.nav_history.remove_item(item_id);
-
-        Some(())
-    }
-
-    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
-        let active_item = self
-            .items
-            .get(self.active_item_index)
-            .map(|item| item.as_ref());
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.set_active_item(active_item, cx);
-        });
-    }
-
-    fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        let theme = theme::current(cx).clone();
-
-        let pane = cx.handle().downgrade();
-        let autoscroll = if mem::take(&mut self.autoscroll) {
-            Some(self.active_item_index)
-        } else {
-            None
-        };
-
-        let pane_active = self.has_focus;
-
-        enum Tabs {}
-        let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
-        for (ix, (item, detail)) in self
-            .items
-            .iter()
-            .cloned()
-            .zip(self.tab_details(cx))
-            .enumerate()
-        {
-            let git_status = item
-                .project_path(cx)
-                .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
-                .and_then(|entry| entry.git_status());
-
-            let detail = if detail == 0 { None } else { Some(detail) };
-            let tab_active = ix == self.active_item_index;
-
-            row.add_child({
-                enum TabDragReceiver {}
-                let mut receiver =
-                    dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
-                        let item = item.clone();
-                        let pane = pane.clone();
-                        let detail = detail.clone();
-
-                        let theme = theme::current(cx).clone();
-                        let mut tooltip_theme = theme.tooltip.clone();
-                        tooltip_theme.max_text_width = None;
-                        let tab_tooltip_text =
-                            item.tab_tooltip_text(cx).map(|text| text.into_owned());
-
-                        let mut tab_style = theme
-                            .workspace
-                            .tab_bar
-                            .tab_style(pane_active, tab_active)
-                            .clone();
-                        let should_show_status = settings::get::<ItemSettings>(cx).git_status;
-                        if should_show_status && git_status != None {
-                            tab_style.label.text.color = match git_status.unwrap() {
-                                GitFileStatus::Added => tab_style.git.inserted,
-                                GitFileStatus::Modified => tab_style.git.modified,
-                                GitFileStatus::Conflict => tab_style.git.conflict,
-                            };
-                        }
-
-                        move |mouse_state, cx| {
-                            let hovered = mouse_state.hovered();
-
-                            enum Tab {}
-                            let mouse_event_handler =
-                                MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
-                                    Self::render_tab(
-                                        &item,
-                                        pane.clone(),
-                                        ix == 0,
-                                        detail,
-                                        hovered,
-                                        &tab_style,
-                                        cx,
-                                    )
-                                })
-                                .on_down(MouseButton::Left, move |_, this, cx| {
-                                    this.activate_item(ix, true, true, cx);
-                                })
-                                .on_click(MouseButton::Middle, {
-                                    let item_id = item.id();
-                                    move |_, pane, cx| {
-                                        pane.close_item_by_id(item_id, SaveIntent::Close, cx)
-                                            .detach_and_log_err(cx);
-                                    }
-                                })
-                                .on_down(
-                                    MouseButton::Right,
-                                    move |event, pane, cx| {
-                                        pane.deploy_tab_context_menu(event.position, item.id(), cx);
-                                    },
-                                );
-
-                            if let Some(tab_tooltip_text) = tab_tooltip_text {
-                                mouse_event_handler
-                                    .with_tooltip::<Self>(
-                                        ix,
-                                        tab_tooltip_text,
-                                        None,
-                                        tooltip_theme,
-                                        cx,
-                                    )
-                                    .into_any()
-                            } else {
-                                mouse_event_handler.into_any()
-                            }
-                        }
-                    });
-
-                if !pane_active || !tab_active {
-                    receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
-                }
-
-                receiver.as_draggable(
-                    DraggedItem {
-                        handle: item,
-                        pane: pane.clone(),
-                    },
-                    {
-                        let theme = theme::current(cx).clone();
-
-                        let detail = detail.clone();
-                        move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
-                            let tab_style = &theme.workspace.tab_bar.dragged_tab;
-                            Self::render_dragged_tab(
-                                &dragged_item.handle,
-                                dragged_item.pane.clone(),
-                                false,
-                                detail,
-                                false,
-                                &tab_style,
-                                cx,
-                            )
-                        }
-                    },
-                )
-            })
-        }
-
-        // Use the inactive tab style along with the current pane's active status to decide how to render
-        // the filler
-        let filler_index = self.items.len();
-        let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
-        enum Filler {}
-        row.add_child(
-            dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
-                Empty::new()
-                    .contained()
-                    .with_style(filler_style.container)
-                    .with_border(filler_style.container.border)
-            })
-            .flex(1., true)
-            .into_any_named("filler"),
-        );
-
-        row
-    }
-
-    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
-        let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
-
-        let mut tab_descriptions = HashMap::default();
-        let mut done = false;
-        while !done {
-            done = true;
-
-            // Store item indices by their tab description.
-            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
-                if let Some(description) = item.tab_description(*detail, cx) {
-                    if *detail == 0
-                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
-                    {
-                        tab_descriptions
-                            .entry(description)
-                            .or_insert(Vec::new())
-                            .push(ix);
-                    }
-                }
-            }
-
-            // If two or more items have the same tab description, increase their level
-            // of detail and try again.
-            for (_, item_ixs) in tab_descriptions.drain() {
-                if item_ixs.len() > 1 {
-                    done = false;
-                    for ix in item_ixs {
-                        tab_details[ix] += 1;
-                    }
-                }
-            }
-        }
-
-        tab_details
-    }
-
-    fn render_tab(
-        item: &Box<dyn ItemHandle>,
-        pane: WeakViewHandle<Pane>,
-        first: bool,
-        detail: Option<usize>,
-        hovered: bool,
-        tab_style: &theme::Tab,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        let title = item.tab_content(detail, &tab_style, cx);
-        Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
-    }
-
-    fn render_dragged_tab(
-        item: &Box<dyn ItemHandle>,
-        pane: WeakViewHandle<Pane>,
-        first: bool,
-        detail: Option<usize>,
-        hovered: bool,
-        tab_style: &theme::Tab,
-        cx: &mut ViewContext<Workspace>,
-    ) -> AnyElement<Workspace> {
-        let title = item.dragged_tab_content(detail, &tab_style, cx);
-        Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
-    }
-
-    fn render_tab_with_title<T: View>(
-        title: AnyElement<T>,
-        item: &Box<dyn ItemHandle>,
-        pane: WeakViewHandle<Pane>,
-        first: bool,
-        hovered: bool,
-        tab_style: &theme::Tab,
-        cx: &mut ViewContext<T>,
-    ) -> AnyElement<T> {
-        let mut container = tab_style.container.clone();
-        if first {
-            container.border.left = false;
-        }
-
-        let buffer_jewel_element = {
-            let diameter = 7.0;
-            let icon_color = if item.has_conflict(cx) {
-                Some(tab_style.icon_conflict)
-            } else if item.is_dirty(cx) {
-                Some(tab_style.icon_dirty)
-            } else {
-                None
-            };
-
-            Canvas::new(move |bounds, _, _, cx| {
-                if let Some(color) = icon_color {
-                    let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
-                    cx.scene().push_quad(Quad {
-                        bounds: square,
-                        background: Some(color),
-                        border: Default::default(),
-                        corner_radii: (diameter / 2.).into(),
-                    });
-                }
-            })
-            .constrained()
-            .with_width(diameter)
-            .with_height(diameter)
-            .aligned()
-        };
-
-        let title_element = title.aligned().contained().with_style(ContainerStyle {
-            margin: Margin {
-                left: tab_style.spacing,
-                right: tab_style.spacing,
-                ..Default::default()
-            },
-            ..Default::default()
-        });
-
-        let close_element = if hovered {
-            let item_id = item.id();
-            enum TabCloseButton {}
-            let icon = Svg::new("icons/x.svg");
-            MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
-                if mouse_state.hovered() {
-                    icon.with_color(tab_style.icon_close_active)
-                } else {
-                    icon.with_color(tab_style.icon_close)
-                }
-            })
-            .with_padding(Padding::uniform(4.))
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, {
-                let pane = pane.clone();
-                move |_, _, cx| {
-                    let pane = pane.clone();
-                    cx.window_context().defer(move |cx| {
-                        if let Some(pane) = pane.upgrade(cx) {
-                            pane.update(cx, |pane, cx| {
-                                pane.close_item_by_id(item_id, SaveIntent::Close, cx)
-                                    .detach_and_log_err(cx);
-                            });
-                        }
-                    });
-                }
-            })
-            .into_any_named("close-tab-icon")
-            .constrained()
-        } else {
-            Empty::new().constrained()
-        }
-        .with_width(tab_style.close_icon_width)
-        .aligned();
-
-        let close_right = settings::get::<ItemSettings>(cx).close_position.right();
-
-        if close_right {
-            Flex::row()
-                .with_child(buffer_jewel_element)
-                .with_child(title_element)
-                .with_child(close_element)
-        } else {
-            Flex::row()
-                .with_child(close_element)
-                .with_child(title_element)
-                .with_child(buffer_jewel_element)
-        }
-        .contained()
-        .with_style(container)
-        .constrained()
-        .with_height(tab_style.height)
-        .into_any()
-    }
-
-    pub fn render_tab_bar_button<
-        F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
-        F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
-    >(
-        index: usize,
-        icon: &'static str,
-        is_active: bool,
-        tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
-        cx: &mut ViewContext<Pane>,
-        on_click: F1,
-        on_down: F2,
-        context_menu: Option<ViewHandle<ContextMenu>>,
-    ) -> AnyElement<Pane> {
-        enum TabBarButton {}
-
-        let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
-            let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
-            let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
-            Svg::new(icon)
-                .with_color(style.color)
-                .constrained()
-                .with_width(style.icon_width)
-                .aligned()
-                .constrained()
-                .with_width(style.button_width)
-                .with_height(style.button_width)
-        })
-        .with_cursor_style(CursorStyle::PointingHand)
-        .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
-        .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
-        .into_any();
-        if let Some((tooltip, action)) = tooltip {
-            let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
-            button = button
-                .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
-                .into_any();
-        }
-
-        Stack::new()
-            .with_child(button)
-            .with_children(
-                context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
-            )
-            .flex(1., false)
-            .into_any_named("tab bar button")
-    }
-
-    fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let background = theme.workspace.background;
-        Empty::new()
-            .contained()
-            .with_background_color(background)
-            .into_any()
-    }
-
-    pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
-        self.zoomed = zoomed;
-        cx.notify();
-    }
-
-    pub fn is_zoomed(&self) -> bool {
-        self.zoomed
-    }
-}
-
-impl Entity for Pane {
-    type Event = Event;
-}
-
-impl View for Pane {
-    fn ui_name() -> &'static str {
-        "Pane"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum MouseNavigationHandler {}
-
-        MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
-            let active_item_index = self.active_item_index;
-
-            if let Some(active_item) = self.active_item() {
-                Flex::column()
-                    .with_child({
-                        let theme = theme::current(cx).clone();
-
-                        let mut stack = Stack::new();
-
-                        enum TabBarEventHandler {}
-                        stack.add_child(
-                            MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
-                                Empty::new()
-                                    .contained()
-                                    .with_style(theme.workspace.tab_bar.container)
-                            })
-                            .on_down(
-                                MouseButton::Left,
-                                move |_, this, cx| {
-                                    this.activate_item(active_item_index, true, true, cx);
-                                },
-                            ),
-                        );
-                        let tooltip_style = theme.tooltip.clone();
-                        let tab_bar_theme = theme.workspace.tab_bar.clone();
-
-                        let nav_button_height = tab_bar_theme.height;
-                        let button_style = tab_bar_theme.nav_button;
-                        let border_for_nav_buttons = tab_bar_theme
-                            .tab_style(false, false)
-                            .container
-                            .border
-                            .clone();
-
-                        let mut tab_row = Flex::row()
-                            .with_child(nav_button(
-                                "icons/arrow_left.svg",
-                                button_style.clone(),
-                                nav_button_height,
-                                tooltip_style.clone(),
-                                self.can_navigate_backward(),
-                                {
-                                    move |pane, cx| {
-                                        if let Some(workspace) = pane.workspace.upgrade(cx) {
-                                            let pane = cx.weak_handle();
-                                            cx.window_context().defer(move |cx| {
-                                                workspace.update(cx, |workspace, cx| {
-                                                    workspace
-                                                        .go_back(pane, cx)
-                                                        .detach_and_log_err(cx)
-                                                })
-                                            })
-                                        }
-                                    }
-                                },
-                                super::GoBack,
-                                "Go Back",
-                                cx,
-                            ))
-                            .with_child(
-                                nav_button(
-                                    "icons/arrow_right.svg",
-                                    button_style.clone(),
-                                    nav_button_height,
-                                    tooltip_style,
-                                    self.can_navigate_forward(),
-                                    {
-                                        move |pane, cx| {
-                                            if let Some(workspace) = pane.workspace.upgrade(cx) {
-                                                let pane = cx.weak_handle();
-                                                cx.window_context().defer(move |cx| {
-                                                    workspace.update(cx, |workspace, cx| {
-                                                        workspace
-                                                            .go_forward(pane, cx)
-                                                            .detach_and_log_err(cx)
-                                                    })
-                                                })
-                                            }
-                                        }
-                                    },
-                                    super::GoForward,
-                                    "Go Forward",
-                                    cx,
-                                )
-                                .contained()
-                                .with_border(border_for_nav_buttons),
-                            )
-                            .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
-
-                        if self.has_focus {
-                            let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
-                            tab_row.add_child(
-                                (render_tab_bar_buttons)(self, cx)
-                                    .contained()
-                                    .with_style(theme.workspace.tab_bar.pane_button_container)
-                                    .flex(1., false)
-                                    .into_any(),
-                            )
-                        }
-
-                        stack.add_child(tab_row);
-                        stack
-                            .constrained()
-                            .with_height(theme.workspace.tab_bar.height)
-                            .flex(1., false)
-                            .into_any_named("tab bar")
-                    })
-                    .with_child({
-                        enum PaneContentTabDropTarget {}
-                        dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
-                            self,
-                            0,
-                            self.active_item_index + 1,
-                            !self.can_split,
-                            if self.can_split { Some(100.) } else { None },
-                            cx,
-                            {
-                                let toolbar = self.toolbar.clone();
-                                let toolbar_hidden = toolbar.read(cx).hidden();
-                                move |_, cx| {
-                                    Flex::column()
-                                        .with_children(
-                                            (!toolbar_hidden)
-                                                .then(|| ChildView::new(&toolbar, cx).expanded()),
-                                        )
-                                        .with_child(
-                                            ChildView::new(active_item.as_any(), cx).flex(1., true),
-                                        )
-                                }
-                            },
-                        )
-                        .flex(1., true)
-                    })
-                    .with_child(ChildView::new(&self.tab_context_menu, cx))
-                    .into_any()
-            } else {
-                enum EmptyPane {}
-                let theme = theme::current(cx).clone();
-
-                dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
-                    self.render_blank_pane(&theme, cx)
-                })
-                .on_down(MouseButton::Left, |_, _, cx| {
-                    cx.focus_parent();
-                })
-                .into_any()
-            }
-        })
-        .on_down(
-            MouseButton::Navigate(NavigationDirection::Back),
-            move |_, pane, cx| {
-                if let Some(workspace) = pane.workspace.upgrade(cx) {
-                    let pane = cx.weak_handle();
-                    cx.window_context().defer(move |cx| {
-                        workspace.update(cx, |workspace, cx| {
-                            workspace.go_back(pane, cx).detach_and_log_err(cx)
-                        })
-                    })
-                }
-            },
-        )
-        .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
-            move |_, pane, cx| {
-                if let Some(workspace) = pane.workspace.upgrade(cx) {
-                    let pane = cx.weak_handle();
-                    cx.window_context().defer(move |cx| {
-                        workspace.update(cx, |workspace, cx| {
-                            workspace.go_forward(pane, cx).detach_and_log_err(cx)
-                        })
-                    })
-                }
-            }
-        })
-        .into_any_named("pane")
-    }
-
-    fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if !self.has_focus {
-            self.has_focus = true;
-            cx.emit(Event::Focus);
-            cx.notify();
-        }
-
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.focus_changed(true, cx);
-        });
-
-        if let Some(active_item) = self.active_item() {
-            if cx.is_self_focused() {
-                // Pane was focused directly. We need to either focus a view inside the active item,
-                // or focus the active item itself
-                if let Some(weak_last_focused_view) =
-                    self.last_focused_view_by_item.get(&active_item.id())
-                {
-                    if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
-                        cx.focus(&last_focused_view);
-                        return;
-                    } else {
-                        self.last_focused_view_by_item.remove(&active_item.id());
-                    }
-                }
-
-                cx.focus(active_item.as_any());
-            } else if focused != self.tab_bar_context_menu.handle {
-                self.last_focused_view_by_item
-                    .insert(active_item.id(), focused.downgrade());
-            }
-        }
-    }
-
-    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        self.has_focus = false;
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.focus_changed(false, cx);
-        });
-        cx.notify();
-    }
-
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
-        Self::reset_to_default_keymap_context(keymap);
-    }
-}
-
-impl ItemNavHistory {
-    pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
-        self.history.push(data, self.item.clone(), cx);
-    }
-
-    pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        self.history.pop(NavigationMode::GoingBack, cx)
-    }
-
-    pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        self.history.pop(NavigationMode::GoingForward, cx)
-    }
-}
-
-impl NavHistory {
-    pub fn for_each_entry(
-        &self,
-        cx: &AppContext,
-        mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
-    ) {
-        let borrowed_history = self.0.borrow();
-        borrowed_history
-            .forward_stack
-            .iter()
-            .chain(borrowed_history.backward_stack.iter())
-            .chain(borrowed_history.closed_stack.iter())
-            .for_each(|entry| {
-                if let Some(project_and_abs_path) =
-                    borrowed_history.paths_by_item.get(&entry.item.id())
-                {
-                    f(entry, project_and_abs_path.clone());
-                } else if let Some(item) = entry.item.upgrade(cx) {
-                    if let Some(path) = item.project_path(cx) {
-                        f(entry, (path, None));
-                    }
-                }
-            })
-    }
-
-    pub fn set_mode(&mut self, mode: NavigationMode) {
-        self.0.borrow_mut().mode = mode;
-    }
+    //     pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
+    //         let mut index = self.active_item_index;
+    //         if index > 0 {
+    //             index -= 1;
+    //         } else if !self.items.is_empty() {
+    //             index = self.items.len() - 1;
+    //         }
+    //         self.activate_item(index, activate_pane, activate_pane, cx);
+    //     }
+
+    //     pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
+    //         let mut index = self.active_item_index;
+    //         if index + 1 < self.items.len() {
+    //             index += 1;
+    //         } else {
+    //             index = 0;
+    //         }
+    //         self.activate_item(index, activate_pane, activate_pane, cx);
+    //     }
+
+    //     pub fn close_active_item(
+    //         &mut self,
+    //         action: &CloseActiveItem,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_item_by_id(
+    //             active_item_id,
+    //             action.save_intent.unwrap_or(SaveIntent::Close),
+    //             cx,
+    //         ))
+    //     }
+
+    //     pub fn close_item_by_id(
+    //         &mut self,
+    //         item_id_to_close: usize,
+    //         save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
+    //     }
+
+    //     pub fn close_inactive_items(
+    //         &mut self,
+    //         _: &CloseInactiveItems,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_id != active_item_id
+    //         }))
+    //     }
+
+    //     pub fn close_clean_items(
+    //         &mut self,
+    //         _: &CloseCleanItems,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let item_ids: Vec<_> = self
+    //             .items()
+    //             .filter(|item| !item.is_dirty(cx))
+    //             .map(|item| item.id())
+    //             .collect();
+    //         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_ids.contains(&item_id)
+    //         }))
+    //     }
+
+    //     pub fn close_items_to_the_left(
+    //         &mut self,
+    //         _: &CloseItemsToTheLeft,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_items_to_the_left_by_id(active_item_id, cx))
+    //     }
+
+    //     pub fn close_items_to_the_left_by_id(
+    //         &mut self,
+    //         item_id: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let item_ids: Vec<_> = self
+    //             .items()
+    //             .take_while(|item| item.id() != item_id)
+    //             .map(|item| item.id())
+    //             .collect();
+    //         self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_ids.contains(&item_id)
+    //         })
+    //     }
+
+    //     pub fn close_items_to_the_right(
+    //         &mut self,
+    //         _: &CloseItemsToTheRight,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         Some(self.close_items_to_the_right_by_id(active_item_id, cx))
+    //     }
+
+    //     pub fn close_items_to_the_right_by_id(
+    //         &mut self,
+    //         item_id: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let item_ids: Vec<_> = self
+    //             .items()
+    //             .rev()
+    //             .take_while(|item| item.id() != item_id)
+    //             .map(|item| item.id())
+    //             .collect();
+    //         self.close_items(cx, SaveIntent::Close, move |item_id| {
+    //             item_ids.contains(&item_id)
+    //         })
+    //     }
+
+    //     pub fn close_all_items(
+    //         &mut self,
+    //         action: &CloseAllItems,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         if self.items.is_empty() {
+    //             return None;
+    //         }
+
+    //         Some(
+    //             self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
+    //                 true
+    //             }),
+    //         )
+    //     }
+
+    //     pub(super) fn file_names_for_prompt(
+    //         items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
+    //         all_dirty_items: usize,
+    //         cx: &AppContext,
+    //     ) -> String {
+    //         /// Quantity of item paths displayed in prompt prior to cutoff..
+    //         const FILE_NAMES_CUTOFF_POINT: usize = 10;
+    //         let mut file_names: Vec<_> = items
+    //             .filter_map(|item| {
+    //                 item.project_path(cx).and_then(|project_path| {
+    //                     project_path
+    //                         .path
+    //                         .file_name()
+    //                         .and_then(|name| name.to_str().map(ToOwned::to_owned))
+    //                 })
+    //             })
+    //             .take(FILE_NAMES_CUTOFF_POINT)
+    //             .collect();
+    //         let should_display_followup_text =
+    //             all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
+    //         if should_display_followup_text {
+    //             let not_shown_files = all_dirty_items - file_names.len();
+    //             if not_shown_files == 1 {
+    //                 file_names.push(".. 1 file not shown".into());
+    //             } else {
+    //                 file_names.push(format!(".. {} files not shown", not_shown_files).into());
+    //             }
+    //         }
+    //         let file_names = file_names.join("\n");
+    //         format!(
+    //             "Do you want to save changes to the following {} files?\n{file_names}",
+    //             all_dirty_items
+    //         )
+    //     }
+
+    //     pub fn close_items(
+    //         &mut self,
+    //         cx: &mut ViewContext<Pane>,
+    //         mut save_intent: SaveIntent,
+    //         should_close: impl 'static + Fn(usize) -> bool,
+    //     ) -> Task<Result<()>> {
+    //         // Find the items to close.
+    //         let mut items_to_close = Vec::new();
+    //         let mut dirty_items = Vec::new();
+    //         for item in &self.items {
+    //             if should_close(item.id()) {
+    //                 items_to_close.push(item.boxed_clone());
+    //                 if item.is_dirty(cx) {
+    //                     dirty_items.push(item.boxed_clone());
+    //                 }
+    //             }
+    //         }
+
+    //         // If a buffer is open both in a singleton editor and in a multibuffer, make sure
+    //         // to focus the singleton buffer when prompting to save that buffer, as opposed
+    //         // to focusing the multibuffer, because this gives the user a more clear idea
+    //         // of what content they would be saving.
+    //         items_to_close.sort_by_key(|item| !item.is_singleton(cx));
+
+    //         let workspace = self.workspace.clone();
+    //         cx.spawn(|pane, mut cx| async move {
+    //             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+    //                 let mut answer = pane.update(&mut cx, |_, cx| {
+    //                     let prompt =
+    //                         Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
+    //                     cx.prompt(
+    //                         PromptLevel::Warning,
+    //                         &prompt,
+    //                         &["Save all", "Discard all", "Cancel"],
+    //                     )
+    //                 })?;
+    //                 match answer.next().await {
+    //                     Some(0) => save_intent = SaveIntent::SaveAll,
+    //                     Some(1) => save_intent = SaveIntent::Skip,
+    //                     _ => {}
+    //                 }
+    //             }
+    //             let mut saved_project_items_ids = HashSet::default();
+    //             for item in items_to_close.clone() {
+    //                 // Find the item's current index and its set of project item models. Avoid
+    //                 // storing these in advance, in case they have changed since this task
+    //                 // was started.
+    //                 let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
+    //                     (pane.index_for_item(&*item), item.project_item_model_ids(cx))
+    //                 })?;
+    //                 let item_ix = if let Some(ix) = item_ix {
+    //                     ix
+    //                 } else {
+    //                     continue;
+    //                 };
+
+    //                 // Check if this view has any project items that are not open anywhere else
+    //                 // in the workspace, AND that the user has not already been prompted to save.
+    //                 // If there are any such project entries, prompt the user to save this item.
+    //                 let project = workspace.read_with(&cx, |workspace, cx| {
+    //                     for item in workspace.items(cx) {
+    //                         if !items_to_close
+    //                             .iter()
+    //                             .any(|item_to_close| item_to_close.id() == item.id())
+    //                         {
+    //                             let other_project_item_ids = item.project_item_model_ids(cx);
+    //                             project_item_ids.retain(|id| !other_project_item_ids.contains(id));
+    //                         }
+    //                     }
+    //                     workspace.project().clone()
+    //                 })?;
+    //                 let should_save = project_item_ids
+    //                     .iter()
+    //                     .any(|id| saved_project_items_ids.insert(*id));
+
+    //                 if should_save
+    //                     && !Self::save_item(
+    //                         project.clone(),
+    //                         &pane,
+    //                         item_ix,
+    //                         &*item,
+    //                         save_intent,
+    //                         &mut cx,
+    //                     )
+    //                     .await?
+    //                 {
+    //                     break;
+    //                 }
+
+    //                 // Remove the item from the pane.
+    //                 pane.update(&mut cx, |pane, cx| {
+    //                     if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
+    //                         pane.remove_item(item_ix, false, cx);
+    //                     }
+    //                 })?;
+    //             }
+
+    //             pane.update(&mut cx, |_, cx| cx.notify())?;
+    //             Ok(())
+    //         })
+    //     }
+
+    //     pub fn remove_item(
+    //         &mut self,
+    //         item_index: usize,
+    //         activate_pane: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.activation_history
+    //             .retain(|&history_entry| history_entry != self.items[item_index].id());
+
+    //         if item_index == self.active_item_index {
+    //             let index_to_activate = self
+    //                 .activation_history
+    //                 .pop()
+    //                 .and_then(|last_activated_item| {
+    //                     self.items.iter().enumerate().find_map(|(index, item)| {
+    //                         (item.id() == last_activated_item).then_some(index)
+    //                     })
+    //                 })
+    //                 // We didn't have a valid activation history entry, so fallback
+    //                 // to activating the item to the left
+    //                 .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
+
+    //             let should_activate = activate_pane || self.has_focus;
+    //             self.activate_item(index_to_activate, should_activate, should_activate, cx);
+    //         }
+
+    //         let item = self.items.remove(item_index);
+
+    //         cx.emit(Event::RemoveItem { item_id: item.id() });
+    //         if self.items.is_empty() {
+    //             item.deactivated(cx);
+    //             self.update_toolbar(cx);
+    //             cx.emit(Event::Remove);
+    //         }
+
+    //         if item_index < self.active_item_index {
+    //             self.active_item_index -= 1;
+    //         }
+
+    //         self.nav_history.set_mode(NavigationMode::ClosingItem);
+    //         item.deactivated(cx);
+    //         self.nav_history.set_mode(NavigationMode::Normal);
+
+    //         if let Some(path) = item.project_path(cx) {
+    //             let abs_path = self
+    //                 .nav_history
+    //                 .0
+    //                 .borrow()
+    //                 .paths_by_item
+    //                 .get(&item.id())
+    //                 .and_then(|(_, abs_path)| abs_path.clone());
+
+    //             self.nav_history
+    //                 .0
+    //                 .borrow_mut()
+    //                 .paths_by_item
+    //                 .insert(item.id(), (path, abs_path));
+    //         } else {
+    //             self.nav_history
+    //                 .0
+    //                 .borrow_mut()
+    //                 .paths_by_item
+    //                 .remove(&item.id());
+    //         }
+
+    //         if self.items.is_empty() && self.zoomed {
+    //             cx.emit(Event::ZoomOut);
+    //         }
+
+    //         cx.notify();
+    //     }
+
+    //     pub async fn save_item(
+    //         project: ModelHandle<Project>,
+    //         pane: &WeakViewHandle<Pane>,
+    //         item_ix: usize,
+    //         item: &dyn ItemHandle,
+    //         save_intent: SaveIntent,
+    //         cx: &mut AsyncAppContext,
+    //     ) -> Result<bool> {
+    //         const CONFLICT_MESSAGE: &str =
+    //             "This file has changed on disk since you started editing it. Do you want to overwrite it?";
+
+    //         if save_intent == SaveIntent::Skip {
+    //             return Ok(true);
+    //         }
+
+    //         let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
+    //             (
+    //                 item.has_conflict(cx),
+    //                 item.is_dirty(cx),
+    //                 item.can_save(cx),
+    //                 item.is_singleton(cx),
+    //             )
+    //         });
+
+    //         // when saving a single buffer, we ignore whether or not it's dirty.
+    //         if save_intent == SaveIntent::Save {
+    //             is_dirty = true;
+    //         }
+
+    //         if save_intent == SaveIntent::SaveAs {
+    //             is_dirty = true;
+    //             has_conflict = false;
+    //             can_save = false;
+    //         }
+
+    //         if save_intent == SaveIntent::Overwrite {
+    //             has_conflict = false;
+    //         }
+
+    //         if has_conflict && can_save {
+    //             let mut answer = pane.update(cx, |pane, cx| {
+    //                 pane.activate_item(item_ix, true, true, cx);
+    //                 cx.prompt(
+    //                     PromptLevel::Warning,
+    //                     CONFLICT_MESSAGE,
+    //                     &["Overwrite", "Discard", "Cancel"],
+    //                 )
+    //             })?;
+    //             match answer.next().await {
+    //                 Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+    //                 Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
+    //                 _ => return Ok(false),
+    //             }
+    //         } else if is_dirty && (can_save || can_save_as) {
+    //             if save_intent == SaveIntent::Close {
+    //                 let will_autosave = cx.read(|cx| {
+    //                     matches!(
+    //                         settings::get::<WorkspaceSettings>(cx).autosave,
+    //                         AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
+    //                     ) && Self::can_autosave_item(&*item, cx)
+    //                 });
+    //                 if !will_autosave {
+    //                     let mut answer = pane.update(cx, |pane, cx| {
+    //                         pane.activate_item(item_ix, true, true, cx);
+    //                         let prompt = dirty_message_for(item.project_path(cx));
+    //                         cx.prompt(
+    //                             PromptLevel::Warning,
+    //                             &prompt,
+    //                             &["Save", "Don't Save", "Cancel"],
+    //                         )
+    //                     })?;
+    //                     match answer.next().await {
+    //                         Some(0) => {}
+    //                         Some(1) => return Ok(true), // Don't save his file
+    //                         _ => return Ok(false),      // Cancel
+    //                     }
+    //                 }
+    //             }
+
+    //             if can_save {
+    //                 pane.update(cx, |_, cx| item.save(project, cx))?.await?;
+    //             } else if can_save_as {
+    //                 let start_abs_path = project
+    //                     .read_with(cx, |project, cx| {
+    //                         let worktree = project.visible_worktrees(cx).next()?;
+    //                         Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
+    //                     })
+    //                     .unwrap_or_else(|| Path::new("").into());
+
+    //                 let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
+    //                 if let Some(abs_path) = abs_path.next().await.flatten() {
+    //                     pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+    //                         .await?;
+    //                 } else {
+    //                     return Ok(false);
+    //                 }
+    //             }
+    //         }
+    //         Ok(true)
+    //     }
+
+    //     fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
+    //         let is_deleted = item.project_entry_ids(cx).is_empty();
+    //         item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
+    //     }
+
+    //     pub fn autosave_item(
+    //         item: &dyn ItemHandle,
+    //         project: ModelHandle<Project>,
+    //         cx: &mut WindowContext,
+    //     ) -> Task<Result<()>> {
+    //         if Self::can_autosave_item(item, cx) {
+    //             item.save(project, cx)
+    //         } else {
+    //             Task::ready(Ok(()))
+    //         }
+    //     }
+
+    //     pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
+    //         if let Some(active_item) = self.active_item() {
+    //             cx.focus(active_item.as_any());
+    //         }
+    //     }
+
+    //     pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
+    //         cx.emit(Event::Split(direction));
+    //     }
+
+    //     fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
+    //             menu.toggle(
+    //                 Default::default(),
+    //                 AnchorCorner::TopRight,
+    //                 vec![
+    //                     ContextMenuItem::action("Split Right", SplitRight),
+    //                     ContextMenuItem::action("Split Left", SplitLeft),
+    //                     ContextMenuItem::action("Split Up", SplitUp),
+    //                     ContextMenuItem::action("Split Down", SplitDown),
+    //                 ],
+    //                 cx,
+    //             );
+    //         });
+
+    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
+    //     }
+
+    //     fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
+    //             menu.toggle(
+    //                 Default::default(),
+    //                 AnchorCorner::TopRight,
+    //                 vec![
+    //                     ContextMenuItem::action("New File", NewFile),
+    //                     ContextMenuItem::action("New Terminal", NewCenterTerminal),
+    //                     ContextMenuItem::action("New Search", NewSearch),
+    //                 ],
+    //                 cx,
+    //             );
+    //         });
+
+    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
+    //     }
+
+    //     fn deploy_tab_context_menu(
+    //         &mut self,
+    //         position: Vector2F,
+    //         target_item_id: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let active_item_id = self.items[self.active_item_index].id();
+    //         let is_active_item = target_item_id == active_item_id;
+    //         let target_pane = cx.weak_handle();
+
+    //         // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on.  Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
+
+    //         self.tab_context_menu.update(cx, |menu, cx| {
+    //             menu.show(
+    //                 position,
+    //                 AnchorCorner::TopLeft,
+    //                 if is_active_item {
+    //                     vec![
+    //                         ContextMenuItem::action(
+    //                             "Close Active Item",
+    //                             CloseActiveItem { save_intent: None },
+    //                         ),
+    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+    //                         ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
+    //                         ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
+    //                         ContextMenuItem::action(
+    //                             "Close All Items",
+    //                             CloseAllItems { save_intent: None },
+    //                         ),
+    //                     ]
+    //                 } else {
+    //                     // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
+    //                     vec![
+    //                         ContextMenuItem::handler("Close Inactive Item", {
+    //                             let pane = target_pane.clone();
+    //                             move |cx| {
+    //                                 if let Some(pane) = pane.upgrade(cx) {
+    //                                     pane.update(cx, |pane, cx| {
+    //                                         pane.close_item_by_id(
+    //                                             target_item_id,
+    //                                             SaveIntent::Close,
+    //                                             cx,
+    //                                         )
+    //                                         .detach_and_log_err(cx);
+    //                                     })
+    //                                 }
+    //                             }
+    //                         }),
+    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+    //                         ContextMenuItem::handler("Close Items To The Left", {
+    //                             let pane = target_pane.clone();
+    //                             move |cx| {
+    //                                 if let Some(pane) = pane.upgrade(cx) {
+    //                                     pane.update(cx, |pane, cx| {
+    //                                         pane.close_items_to_the_left_by_id(target_item_id, cx)
+    //                                             .detach_and_log_err(cx);
+    //                                     })
+    //                                 }
+    //                             }
+    //                         }),
+    //                         ContextMenuItem::handler("Close Items To The Right", {
+    //                             let pane = target_pane.clone();
+    //                             move |cx| {
+    //                                 if let Some(pane) = pane.upgrade(cx) {
+    //                                     pane.update(cx, |pane, cx| {
+    //                                         pane.close_items_to_the_right_by_id(target_item_id, cx)
+    //                                             .detach_and_log_err(cx);
+    //                                     })
+    //                                 }
+    //                             }
+    //                         }),
+    //                         ContextMenuItem::action(
+    //                             "Close All Items",
+    //                             CloseAllItems { save_intent: None },
+    //                         ),
+    //                     ]
+    //                 },
+    //                 cx,
+    //             );
+    //         });
+    //     }
+
+    //     pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
+    //         &self.toolbar
+    //     }
+
+    //     pub fn handle_deleted_project_item(
+    //         &mut self,
+    //         entry_id: ProjectEntryId,
+    //         cx: &mut ViewContext<Pane>,
+    //     ) -> Option<()> {
+    //         let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
+    //             if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+    //                 Some((i, item.id()))
+    //             } else {
+    //                 None
+    //             }
+    //         })?;
+
+    //         self.remove_item(item_index_to_delete, false, cx);
+    //         self.nav_history.remove_item(item_id);
+
+    //         Some(())
+    //     }
+
+    //     fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
+    //         let active_item = self
+    //             .items
+    //             .get(self.active_item_index)
+    //             .map(|item| item.as_ref());
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.set_active_item(active_item, cx);
+    //         });
+    //     }
+
+    //     fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
+    //         let theme = theme::current(cx).clone();
+
+    //         let pane = cx.handle().downgrade();
+    //         let autoscroll = if mem::take(&mut self.autoscroll) {
+    //             Some(self.active_item_index)
+    //         } else {
+    //             None
+    //         };
+
+    //         let pane_active = self.has_focus;
+
+    //         enum Tabs {}
+    //         let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
+    //         for (ix, (item, detail)) in self
+    //             .items
+    //             .iter()
+    //             .cloned()
+    //             .zip(self.tab_details(cx))
+    //             .enumerate()
+    //         {
+    //             let git_status = item
+    //                 .project_path(cx)
+    //                 .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
+    //                 .and_then(|entry| entry.git_status());
+
+    //             let detail = if detail == 0 { None } else { Some(detail) };
+    //             let tab_active = ix == self.active_item_index;
+
+    //             row.add_child({
+    //                 enum TabDragReceiver {}
+    //                 let mut receiver =
+    //                     dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
+    //                         let item = item.clone();
+    //                         let pane = pane.clone();
+    //                         let detail = detail.clone();
+
+    //                         let theme = theme::current(cx).clone();
+    //                         let mut tooltip_theme = theme.tooltip.clone();
+    //                         tooltip_theme.max_text_width = None;
+    //                         let tab_tooltip_text =
+    //                             item.tab_tooltip_text(cx).map(|text| text.into_owned());
+
+    //                         let mut tab_style = theme
+    //                             .workspace
+    //                             .tab_bar
+    //                             .tab_style(pane_active, tab_active)
+    //                             .clone();
+    //                         let should_show_status = settings::get::<ItemSettings>(cx).git_status;
+    //                         if should_show_status && git_status != None {
+    //                             tab_style.label.text.color = match git_status.unwrap() {
+    //                                 GitFileStatus::Added => tab_style.git.inserted,
+    //                                 GitFileStatus::Modified => tab_style.git.modified,
+    //                                 GitFileStatus::Conflict => tab_style.git.conflict,
+    //                             };
+    //                         }
+
+    //                         move |mouse_state, cx| {
+    //                             let hovered = mouse_state.hovered();
+
+    //                             enum Tab {}
+    //                             let mouse_event_handler =
+    //                                 MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
+    //                                     Self::render_tab(
+    //                                         &item,
+    //                                         pane.clone(),
+    //                                         ix == 0,
+    //                                         detail,
+    //                                         hovered,
+    //                                         &tab_style,
+    //                                         cx,
+    //                                     )
+    //                                 })
+    //                                 .on_down(MouseButton::Left, move |_, this, cx| {
+    //                                     this.activate_item(ix, true, true, cx);
+    //                                 })
+    //                                 .on_click(MouseButton::Middle, {
+    //                                     let item_id = item.id();
+    //                                     move |_, pane, cx| {
+    //                                         pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+    //                                             .detach_and_log_err(cx);
+    //                                     }
+    //                                 })
+    //                                 .on_down(
+    //                                     MouseButton::Right,
+    //                                     move |event, pane, cx| {
+    //                                         pane.deploy_tab_context_menu(event.position, item.id(), cx);
+    //                                     },
+    //                                 );
+
+    //                             if let Some(tab_tooltip_text) = tab_tooltip_text {
+    //                                 mouse_event_handler
+    //                                     .with_tooltip::<Self>(
+    //                                         ix,
+    //                                         tab_tooltip_text,
+    //                                         None,
+    //                                         tooltip_theme,
+    //                                         cx,
+    //                                     )
+    //                                     .into_any()
+    //                             } else {
+    //                                 mouse_event_handler.into_any()
+    //                             }
+    //                         }
+    //                     });
+
+    //                 if !pane_active || !tab_active {
+    //                     receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
+    //                 }
+
+    //                 receiver.as_draggable(
+    //                     DraggedItem {
+    //                         handle: item,
+    //                         pane: pane.clone(),
+    //                     },
+    //                     {
+    //                         let theme = theme::current(cx).clone();
+
+    //                         let detail = detail.clone();
+    //                         move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
+    //                             let tab_style = &theme.workspace.tab_bar.dragged_tab;
+    //                             Self::render_dragged_tab(
+    //                                 &dragged_item.handle,
+    //                                 dragged_item.pane.clone(),
+    //                                 false,
+    //                                 detail,
+    //                                 false,
+    //                                 &tab_style,
+    //                                 cx,
+    //                             )
+    //                         }
+    //                     },
+    //                 )
+    //             })
+    //         }
+
+    //         // Use the inactive tab style along with the current pane's active status to decide how to render
+    //         // the filler
+    //         let filler_index = self.items.len();
+    //         let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
+    //         enum Filler {}
+    //         row.add_child(
+    //             dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
+    //                 Empty::new()
+    //                     .contained()
+    //                     .with_style(filler_style.container)
+    //                     .with_border(filler_style.container.border)
+    //             })
+    //             .flex(1., true)
+    //             .into_any_named("filler"),
+    //         );
+
+    //         row
+    //     }
+
+    //     fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
+    //         let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
+
+    //         let mut tab_descriptions = HashMap::default();
+    //         let mut done = false;
+    //         while !done {
+    //             done = true;
+
+    //             // Store item indices by their tab description.
+    //             for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
+    //                 if let Some(description) = item.tab_description(*detail, cx) {
+    //                     if *detail == 0
+    //                         || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
+    //                     {
+    //                         tab_descriptions
+    //                             .entry(description)
+    //                             .or_insert(Vec::new())
+    //                             .push(ix);
+    //                     }
+    //                 }
+    //             }
+
+    //             // If two or more items have the same tab description, increase their level
+    //             // of detail and try again.
+    //             for (_, item_ixs) in tab_descriptions.drain() {
+    //                 if item_ixs.len() > 1 {
+    //                     done = false;
+    //                     for ix in item_ixs {
+    //                         tab_details[ix] += 1;
+    //                     }
+    //                 }
+    //             }
+    //         }
+
+    //         tab_details
+    //     }
+
+    //     fn render_tab(
+    //         item: &Box<dyn ItemHandle>,
+    //         pane: WeakViewHandle<Pane>,
+    //         first: bool,
+    //         detail: Option<usize>,
+    //         hovered: bool,
+    //         tab_style: &theme::Tab,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         let title = item.tab_content(detail, &tab_style, cx);
+    //         Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+    //     }
+
+    //     fn render_dragged_tab(
+    //         item: &Box<dyn ItemHandle>,
+    //         pane: WeakViewHandle<Pane>,
+    //         first: bool,
+    //         detail: Option<usize>,
+    //         hovered: bool,
+    //         tab_style: &theme::Tab,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> AnyElement<Workspace> {
+    //         let title = item.dragged_tab_content(detail, &tab_style, cx);
+    //         Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+    //     }
+
+    //     fn render_tab_with_title<T: View>(
+    //         title: AnyElement<T>,
+    //         item: &Box<dyn ItemHandle>,
+    //         pane: WeakViewHandle<Pane>,
+    //         first: bool,
+    //         hovered: bool,
+    //         tab_style: &theme::Tab,
+    //         cx: &mut ViewContext<T>,
+    //     ) -> AnyElement<T> {
+    //         let mut container = tab_style.container.clone();
+    //         if first {
+    //             container.border.left = false;
+    //         }
+
+    //         let buffer_jewel_element = {
+    //             let diameter = 7.0;
+    //             let icon_color = if item.has_conflict(cx) {
+    //                 Some(tab_style.icon_conflict)
+    //             } else if item.is_dirty(cx) {
+    //                 Some(tab_style.icon_dirty)
+    //             } else {
+    //                 None
+    //             };
+
+    //             Canvas::new(move |bounds, _, _, cx| {
+    //                 if let Some(color) = icon_color {
+    //                     let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
+    //                     cx.scene().push_quad(Quad {
+    //                         bounds: square,
+    //                         background: Some(color),
+    //                         border: Default::default(),
+    //                         corner_radii: (diameter / 2.).into(),
+    //                     });
+    //                 }
+    //             })
+    //             .constrained()
+    //             .with_width(diameter)
+    //             .with_height(diameter)
+    //             .aligned()
+    //         };
+
+    //         let title_element = title.aligned().contained().with_style(ContainerStyle {
+    //             margin: Margin {
+    //                 left: tab_style.spacing,
+    //                 right: tab_style.spacing,
+    //                 ..Default::default()
+    //             },
+    //             ..Default::default()
+    //         });
+
+    //         let close_element = if hovered {
+    //             let item_id = item.id();
+    //             enum TabCloseButton {}
+    //             let icon = Svg::new("icons/x.svg");
+    //             MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
+    //                 if mouse_state.hovered() {
+    //                     icon.with_color(tab_style.icon_close_active)
+    //                 } else {
+    //                     icon.with_color(tab_style.icon_close)
+    //                 }
+    //             })
+    //             .with_padding(Padding::uniform(4.))
+    //             .with_cursor_style(CursorStyle::PointingHand)
+    //             .on_click(MouseButton::Left, {
+    //                 let pane = pane.clone();
+    //                 move |_, _, cx| {
+    //                     let pane = pane.clone();
+    //                     cx.window_context().defer(move |cx| {
+    //                         if let Some(pane) = pane.upgrade(cx) {
+    //                             pane.update(cx, |pane, cx| {
+    //                                 pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+    //                                     .detach_and_log_err(cx);
+    //                             });
+    //                         }
+    //                     });
+    //                 }
+    //             })
+    //             .into_any_named("close-tab-icon")
+    //             .constrained()
+    //         } else {
+    //             Empty::new().constrained()
+    //         }
+    //         .with_width(tab_style.close_icon_width)
+    //         .aligned();
+
+    //         let close_right = settings::get::<ItemSettings>(cx).close_position.right();
+
+    //         if close_right {
+    //             Flex::row()
+    //                 .with_child(buffer_jewel_element)
+    //                 .with_child(title_element)
+    //                 .with_child(close_element)
+    //         } else {
+    //             Flex::row()
+    //                 .with_child(close_element)
+    //                 .with_child(title_element)
+    //                 .with_child(buffer_jewel_element)
+    //         }
+    //         .contained()
+    //         .with_style(container)
+    //         .constrained()
+    //         .with_height(tab_style.height)
+    //         .into_any()
+    //     }
+
+    //     pub fn render_tab_bar_button<
+    //         F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
+    //         F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
+    //     >(
+    //         index: usize,
+    //         icon: &'static str,
+    //         is_active: bool,
+    //         tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
+    //         cx: &mut ViewContext<Pane>,
+    //         on_click: F1,
+    //         on_down: F2,
+    //         context_menu: Option<ViewHandle<ContextMenu>>,
+    //     ) -> AnyElement<Pane> {
+    //         enum TabBarButton {}
+
+    //         let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
+    //             let theme = &settings2::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
+    //             let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
+    //             Svg::new(icon)
+    //                 .with_color(style.color)
+    //                 .constrained()
+    //                 .with_width(style.icon_width)
+    //                 .aligned()
+    //                 .constrained()
+    //                 .with_width(style.button_width)
+    //                 .with_height(style.button_width)
+    //         })
+    //         .with_cursor_style(CursorStyle::PointingHand)
+    //         .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
+    //         .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
+    //         .into_any();
+    //         if let Some((tooltip, action)) = tooltip {
+    //             let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
+    //             button = button
+    //                 .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
+    //                 .into_any();
+    //         }
+
+    //         Stack::new()
+    //             .with_child(button)
+    //             .with_children(
+    //                 context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
+    //             )
+    //             .flex(1., false)
+    //             .into_any_named("tab bar button")
+    //     }
+
+    //     fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+    //         let background = theme.workspace.background;
+    //         Empty::new()
+    //             .contained()
+    //             .with_background_color(background)
+    //             .into_any()
+    //     }
+
+    //     pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
+    //         self.zoomed = zoomed;
+    //         cx.notify();
+    //     }
+
+    //     pub fn is_zoomed(&self) -> bool {
+    //         self.zoomed
+    //     }
+    // }
+
+    // impl Entity for Pane {
+    //     type Event = Event;
+    // }
+
+    // impl View for Pane {
+    //     fn ui_name() -> &'static str {
+    //         "Pane"
+    //     }
+
+    //     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+    //         enum MouseNavigationHandler {}
+
+    //         MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
+    //             let active_item_index = self.active_item_index;
+
+    //             if let Some(active_item) = self.active_item() {
+    //                 Flex::column()
+    //                     .with_child({
+    //                         let theme = theme::current(cx).clone();
+
+    //                         let mut stack = Stack::new();
+
+    //                         enum TabBarEventHandler {}
+    //                         stack.add_child(
+    //                             MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
+    //                                 Empty::new()
+    //                                     .contained()
+    //                                     .with_style(theme.workspace.tab_bar.container)
+    //                             })
+    //                             .on_down(
+    //                                 MouseButton::Left,
+    //                                 move |_, this, cx| {
+    //                                     this.activate_item(active_item_index, true, true, cx);
+    //                                 },
+    //                             ),
+    //                         );
+    //                         let tooltip_style = theme.tooltip.clone();
+    //                         let tab_bar_theme = theme.workspace.tab_bar.clone();
+
+    //                         let nav_button_height = tab_bar_theme.height;
+    //                         let button_style = tab_bar_theme.nav_button;
+    //                         let border_for_nav_buttons = tab_bar_theme
+    //                             .tab_style(false, false)
+    //                             .container
+    //                             .border
+    //                             .clone();
+
+    //                         let mut tab_row = Flex::row()
+    //                             .with_child(nav_button(
+    //                                 "icons/arrow_left.svg",
+    //                                 button_style.clone(),
+    //                                 nav_button_height,
+    //                                 tooltip_style.clone(),
+    //                                 self.can_navigate_backward(),
+    //                                 {
+    //                                     move |pane, cx| {
+    //                                         if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                                             let pane = cx.weak_handle();
+    //                                             cx.window_context().defer(move |cx| {
+    //                                                 workspace.update(cx, |workspace, cx| {
+    //                                                     workspace
+    //                                                         .go_back(pane, cx)
+    //                                                         .detach_and_log_err(cx)
+    //                                                 })
+    //                                             })
+    //                                         }
+    //                                     }
+    //                                 },
+    //                                 super::GoBack,
+    //                                 "Go Back",
+    //                                 cx,
+    //                             ))
+    //                             .with_child(
+    //                                 nav_button(
+    //                                     "icons/arrow_right.svg",
+    //                                     button_style.clone(),
+    //                                     nav_button_height,
+    //                                     tooltip_style,
+    //                                     self.can_navigate_forward(),
+    //                                     {
+    //                                         move |pane, cx| {
+    //                                             if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                                                 let pane = cx.weak_handle();
+    //                                                 cx.window_context().defer(move |cx| {
+    //                                                     workspace.update(cx, |workspace, cx| {
+    //                                                         workspace
+    //                                                             .go_forward(pane, cx)
+    //                                                             .detach_and_log_err(cx)
+    //                                                     })
+    //                                                 })
+    //                                             }
+    //                                         }
+    //                                     },
+    //                                     super::GoForward,
+    //                                     "Go Forward",
+    //                                     cx,
+    //                                 )
+    //                                 .contained()
+    //                                 .with_border(border_for_nav_buttons),
+    //                             )
+    //                             .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
+
+    //                         if self.has_focus {
+    //                             let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
+    //                             tab_row.add_child(
+    //                                 (render_tab_bar_buttons)(self, cx)
+    //                                     .contained()
+    //                                     .with_style(theme.workspace.tab_bar.pane_button_container)
+    //                                     .flex(1., false)
+    //                                     .into_any(),
+    //                             )
+    //                         }
+
+    //                         stack.add_child(tab_row);
+    //                         stack
+    //                             .constrained()
+    //                             .with_height(theme.workspace.tab_bar.height)
+    //                             .flex(1., false)
+    //                             .into_any_named("tab bar")
+    //                     })
+    //                     .with_child({
+    //                         enum PaneContentTabDropTarget {}
+    //                         dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
+    //                             self,
+    //                             0,
+    //                             self.active_item_index + 1,
+    //                             !self.can_split,
+    //                             if self.can_split { Some(100.) } else { None },
+    //                             cx,
+    //                             {
+    //                                 let toolbar = self.toolbar.clone();
+    //                                 let toolbar_hidden = toolbar.read(cx).hidden();
+    //                                 move |_, cx| {
+    //                                     Flex::column()
+    //                                         .with_children(
+    //                                             (!toolbar_hidden)
+    //                                                 .then(|| ChildView::new(&toolbar, cx).expanded()),
+    //                                         )
+    //                                         .with_child(
+    //                                             ChildView::new(active_item.as_any(), cx).flex(1., true),
+    //                                         )
+    //                                 }
+    //                             },
+    //                         )
+    //                         .flex(1., true)
+    //                     })
+    //                     .with_child(ChildView::new(&self.tab_context_menu, cx))
+    //                     .into_any()
+    //             } else {
+    //                 enum EmptyPane {}
+    //                 let theme = theme::current(cx).clone();
+
+    //                 dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
+    //                     self.render_blank_pane(&theme, cx)
+    //                 })
+    //                 .on_down(MouseButton::Left, |_, _, cx| {
+    //                     cx.focus_parent();
+    //                 })
+    //                 .into_any()
+    //             }
+    //         })
+    //         .on_down(
+    //             MouseButton::Navigate(NavigationDirection::Back),
+    //             move |_, pane, cx| {
+    //                 if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                     let pane = cx.weak_handle();
+    //                     cx.window_context().defer(move |cx| {
+    //                         workspace.update(cx, |workspace, cx| {
+    //                             workspace.go_back(pane, cx).detach_and_log_err(cx)
+    //                         })
+    //                     })
+    //                 }
+    //             },
+    //         )
+    //         .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
+    //             move |_, pane, cx| {
+    //                 if let Some(workspace) = pane.workspace.upgrade(cx) {
+    //                     let pane = cx.weak_handle();
+    //                     cx.window_context().defer(move |cx| {
+    //                         workspace.update(cx, |workspace, cx| {
+    //                             workspace.go_forward(pane, cx).detach_and_log_err(cx)
+    //                         })
+    //                     })
+    //                 }
+    //             }
+    //         })
+    //         .into_any_named("pane")
+    //     }
+
+    //     fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    //         if !self.has_focus {
+    //             self.has_focus = true;
+    //             cx.emit(Event::Focus);
+    //             cx.notify();
+    //         }
+
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.focus_changed(true, cx);
+    //         });
+
+    //         if let Some(active_item) = self.active_item() {
+    //             if cx.is_self_focused() {
+    //                 // Pane was focused directly. We need to either focus a view inside the active item,
+    //                 // or focus the active item itself
+    //                 if let Some(weak_last_focused_view) =
+    //                     self.last_focused_view_by_item.get(&active_item.id())
+    //                 {
+    //                     if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
+    //                         cx.focus(&last_focused_view);
+    //                         return;
+    //                     } else {
+    //                         self.last_focused_view_by_item.remove(&active_item.id());
+    //                     }
+    //                 }
+
+    //                 cx.focus(active_item.as_any());
+    //             } else if focused != self.tab_bar_context_menu.handle {
+    //                 self.last_focused_view_by_item
+    //                     .insert(active_item.id(), focused.downgrade());
+    //             }
+    //         }
+    //     }
+
+    //     fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    //         self.has_focus = false;
+    //         self.toolbar.update(cx, |toolbar, cx| {
+    //             toolbar.focus_changed(false, cx);
+    //         });
+    //         cx.notify();
+    //     }
+
+    //     fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+    //         Self::reset_to_default_keymap_context(keymap);
+    //     }
+    // }
+
+    // impl ItemNavHistory {
+    //     pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
+    //         self.history.push(data, self.item.clone(), cx);
+    //     }
+
+    //     pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
+    //         self.history.pop(NavigationMode::GoingBack, cx)
+    //     }
+
+    //     pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
+    //         self.history.pop(NavigationMode::GoingForward, cx)
+    //     }
+    // }
+
+    // impl NavHistory {
+    //     pub fn for_each_entry(
+    //         &self,
+    //         cx: &AppContext,
+    //         mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
+    //     ) {
+    //         let borrowed_history = self.0.borrow();
+    //         borrowed_history
+    //             .forward_stack
+    //             .iter()
+    //             .chain(borrowed_history.backward_stack.iter())
+    //             .chain(borrowed_history.closed_stack.iter())
+    //             .for_each(|entry| {
+    //                 if let Some(project_and_abs_path) =
+    //                     borrowed_history.paths_by_item.get(&entry.item.id())
+    //                 {
+    //                     f(entry, project_and_abs_path.clone());
+    //                 } else if let Some(item) = entry.item.upgrade(cx) {
+    //                     if let Some(path) = item.project_path(cx) {
+    //                         f(entry, (path, None));
+    //                     }
+    //                 }
+    //             })
+    //     }
+
+    //     pub fn set_mode(&mut self, mode: NavigationMode) {
+    //         self.0.borrow_mut().mode = mode;
+    //     }
 
     pub fn mode(&self) -> NavigationMode {
         self.0.borrow().mode
     }
 
-    pub fn disable(&mut self) {
-        self.0.borrow_mut().mode = NavigationMode::Disabled;
-    }
-
-    pub fn enable(&mut self) {
-        self.0.borrow_mut().mode = NavigationMode::Normal;
-    }
-
-    pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        let mut state = self.0.borrow_mut();
-        let entry = match mode {
-            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
-                return None
-            }
-            NavigationMode::GoingBack => &mut state.backward_stack,
-            NavigationMode::GoingForward => &mut state.forward_stack,
-            NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
-        }
-        .pop_back();
-        if entry.is_some() {
-            state.did_update(cx);
-        }
-        entry
-    }
-
-    pub fn push<D: 'static + Any>(
-        &mut self,
-        data: Option<D>,
-        item: Rc<dyn WeakItemHandle>,
-        cx: &mut WindowContext,
-    ) {
-        let state = &mut *self.0.borrow_mut();
-        match state.mode {
-            NavigationMode::Disabled => {}
-            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
-                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.backward_stack.pop_front();
-                }
-                state.backward_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-                state.forward_stack.clear();
-            }
-            NavigationMode::GoingBack => {
-                if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.forward_stack.pop_front();
-                }
-                state.forward_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-            }
-            NavigationMode::GoingForward => {
-                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.backward_stack.pop_front();
-                }
-                state.backward_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-            }
-            NavigationMode::ClosingItem => {
-                if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.closed_stack.pop_front();
-                }
-                state.closed_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-            }
-        }
-        state.did_update(cx);
-    }
-
-    pub fn remove_item(&mut self, item_id: usize) {
-        let mut state = self.0.borrow_mut();
-        state.paths_by_item.remove(&item_id);
-        state
-            .backward_stack
-            .retain(|entry| entry.item.id() != item_id);
-        state
-            .forward_stack
-            .retain(|entry| entry.item.id() != item_id);
-        state
-            .closed_stack
-            .retain(|entry| entry.item.id() != item_id);
-    }
-
-    pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
-        self.0.borrow().paths_by_item.get(&item_id).cloned()
-    }
-}
-
-impl NavHistoryState {
-    pub fn did_update(&self, cx: &mut WindowContext) {
-        if let Some(pane) = self.pane.upgrade(cx) {
-            cx.defer(move |cx| {
-                pane.update(cx, |pane, cx| pane.history_updated(cx));
-            });
-        }
-    }
-}
-
-pub struct PaneBackdrop<V> {
-    child_view: usize,
-    child: AnyElement<V>,
+    //     pub fn disable(&mut self) {
+    //         self.0.borrow_mut().mode = NavigationMode::Disabled;
+    //     }
+
+    //     pub fn enable(&mut self) {
+    //         self.0.borrow_mut().mode = NavigationMode::Normal;
+    //     }
+
+    //     pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
+    //         let mut state = self.0.borrow_mut();
+    //         let entry = match mode {
+    //             NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
+    //                 return None
+    //             }
+    //             NavigationMode::GoingBack => &mut state.backward_stack,
+    //             NavigationMode::GoingForward => &mut state.forward_stack,
+    //             NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
+    //         }
+    //         .pop_back();
+    //         if entry.is_some() {
+    //             state.did_update(cx);
+    //         }
+    //         entry
+    //     }
+
+    //     pub fn push<D: 'static + Any>(
+    //         &mut self,
+    //         data: Option<D>,
+    //         item: Rc<dyn WeakItemHandle>,
+    //         cx: &mut WindowContext,
+    //     ) {
+    //         let state = &mut *self.0.borrow_mut();
+    //         match state.mode {
+    //             NavigationMode::Disabled => {}
+    //             NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
+    //                 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.backward_stack.pop_front();
+    //                 }
+    //                 state.backward_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //                 state.forward_stack.clear();
+    //             }
+    //             NavigationMode::GoingBack => {
+    //                 if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.forward_stack.pop_front();
+    //                 }
+    //                 state.forward_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //             }
+    //             NavigationMode::GoingForward => {
+    //                 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.backward_stack.pop_front();
+    //                 }
+    //                 state.backward_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //             }
+    //             NavigationMode::ClosingItem => {
+    //                 if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+    //                     state.closed_stack.pop_front();
+    //                 }
+    //                 state.closed_stack.push_back(NavigationEntry {
+    //                     item,
+    //                     data: data.map(|data| Box::new(data) as Box<dyn Any>),
+    //                     timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+    //                 });
+    //             }
+    //         }
+    //         state.did_update(cx);
+    //     }
+
+    //     pub fn remove_item(&mut self, item_id: usize) {
+    //         let mut state = self.0.borrow_mut();
+    //         state.paths_by_item.remove(&item_id);
+    //         state
+    //             .backward_stack
+    //             .retain(|entry| entry.item.id() != item_id);
+    //         state
+    //             .forward_stack
+    //             .retain(|entry| entry.item.id() != item_id);
+    //         state
+    //             .closed_stack
+    //             .retain(|entry| entry.item.id() != item_id);
+    //     }
+
+    //     pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
+    //         self.0.borrow().paths_by_item.get(&item_id).cloned()
+    //     }
 }
 
-impl<V> PaneBackdrop<V> {
-    pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
-        PaneBackdrop {
-            child,
-            child_view: pane_item_view,
-        }
-    }
-}
-
-impl<V: 'static> Element<V> for PaneBackdrop<V> {
-    type LayoutState = ();
-
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: gpui::SizeConstraint,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, view, cx);
-        (size, ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Self::PaintState {
-        let background = theme::current(cx).editor.background;
-
-        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-        cx.scene().push_quad(gpui::Quad {
-            bounds: RectF::new(bounds.origin(), bounds.size()),
-            background: Some(background),
-            ..Default::default()
-        });
-
-        let child_view_id = self.child_view;
-        cx.scene().push_mouse_region(
-            MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
-                gpui::platform::MouseButton::Left,
-                move |_, _: &mut V, cx| {
-                    let window = cx.window();
-                    cx.app_context().focus(window, Some(child_view_id))
-                },
-            ),
-        );
-
-        cx.scene().push_layer(Some(bounds));
-        self.child.paint(bounds.origin(), visible_bounds, view, cx);
-        cx.scene().pop_layer();
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: std::ops::Range<usize>,
-        _bounds: RectF,
-        _visible_bounds: RectF,
-        _layout: &Self::LayoutState,
-        _paint: &Self::PaintState,
-        view: &V,
-        cx: &gpui::ViewContext<V>,
-    ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _bounds: RectF,
-        _layout: &Self::LayoutState,
-        _paint: &Self::PaintState,
-        view: &V,
-        cx: &gpui::ViewContext<V>,
-    ) -> serde_json::Value {
-        gpui::json::json!({
-            "type": "Pane Back Drop",
-            "view": self.child_view,
-            "child": self.child.debug(view, cx),
-        })
-    }
-}
-
-fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
-    let path = buffer_path
-        .as_ref()
-        .and_then(|p| p.path.to_str())
-        .unwrap_or(&"This buffer");
-    let path = truncate_and_remove_front(path, 80);
-    format!("{path} contains unsaved edits. Do you want to save it?")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::item::test::{TestItem, TestProjectItem};
-    use gpui::TestAppContext;
-    use project::FakeFs;
-    use settings::SettingsStore;
-
-    #[gpui::test]
-    async fn test_remove_active_empty(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        pane.update(cx, |pane, cx| {
-            assert!(pane
-                .close_active_item(&CloseActiveItem { save_intent: None }, cx)
-                .is_none())
-        });
-    }
-
-    #[gpui::test]
-    async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        // 1. Add with a destination index
-        //   a. Add before the active item
-        set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
-                false,
-                false,
-                Some(0),
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
-
-        //   b. Add after the active item
-        set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
-                false,
-                false,
-                Some(2),
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
-
-        //   c. Add at the end of the item list (including off the length)
-        set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
-                false,
-                false,
-                Some(5),
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
-        // 2. Add without a destination index
-        //   a. Add with active item at the start of the item list
-        set_labeled_items(&pane, ["A*", "B", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
-
-        //   b. Add with active item at the end of the item list
-        set_labeled_items(&pane, ["A", "B", "C*"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        // 1. Add with a destination index
-        //   1a. Add before the active item
-        let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(d, false, false, Some(0), cx);
-        });
-        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
-
-        //   1b. Add after the active item
-        let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(d, false, false, Some(2), cx);
-        });
-        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
-
-        //   1c. Add at the end of the item list (including off the length)
-        let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(a, false, false, Some(5), cx);
-        });
-        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
-
-        //   1d. Add same item to active index
-        let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(b, false, false, Some(1), cx);
-        });
-        assert_item_labels(&pane, ["A", "B*", "C"], cx);
-
-        //   1e. Add item to index after same item in last position
-        let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(c, false, false, Some(2), cx);
-        });
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
-        // 2. Add without a destination index
-        //   2a. Add with active item at the start of the item list
-        let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(d, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
-
-        //   2b. Add with active item at the end of the item list
-        let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(a, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
-
-        //   2c. Add active item to active item at end of list
-        let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(c, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
-        //   2d. Add active item to active item at start of list
-        let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(a, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["A*", "B", "C"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        // singleton view
-        pane.update(cx, |pane, cx| {
-            let item = TestItem::new()
-                .with_singleton(true)
-                .with_label("buffer 1")
-                .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
-
-            pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["buffer 1*"], cx);
-
-        // new singleton view with the same project entry
-        pane.update(cx, |pane, cx| {
-            let item = TestItem::new()
-                .with_singleton(true)
-                .with_label("buffer 1")
-                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
-
-            pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["buffer 1*"], cx);
-
-        // new singleton view with different project entry
-        pane.update(cx, |pane, cx| {
-            let item = TestItem::new()
-                .with_singleton(true)
-                .with_label("buffer 2")
-                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
-            pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
-
-        // new multibuffer view with the same project entry
-        pane.update(cx, |pane, cx| {
-            let item = TestItem::new()
-                .with_singleton(false)
-                .with_label("multibuffer 1")
-                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
-
-            pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
-
-        // another multibuffer view with the same project entry
-        pane.update(cx, |pane, cx| {
-            let item = TestItem::new()
-                .with_singleton(false)
-                .with_label("multibuffer 1b")
-                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
-
-            pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
-        });
-        assert_item_labels(
-            &pane,
-            ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
-            cx,
-        );
-    }
-
-    #[gpui::test]
-    async fn test_remove_item_ordering(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        add_labeled_item(&pane, "A", false, cx);
-        add_labeled_item(&pane, "B", false, cx);
-        add_labeled_item(&pane, "C", false, cx);
-        add_labeled_item(&pane, "D", false, cx);
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
-        pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
-        add_labeled_item(&pane, "1", false, cx);
-        assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
-
-        pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "B*", "C"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "C*"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_inactive_items(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_inactive_items(&CloseInactiveItems, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["C*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_clean_items(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        add_labeled_item(&pane, "A", true, cx);
-        add_labeled_item(&pane, "B", false, cx);
-        add_labeled_item(&pane, "C", true, cx);
-        add_labeled_item(&pane, "D", false, cx);
-        add_labeled_item(&pane, "E", false, cx);
-        assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
-
-        pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
-            .unwrap()
-            .await
-            .unwrap();
-        assert_item_labels(&pane, ["A^", "C*^"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["C*", "D", "E"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_all_items(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.background());
-
-        let project = Project::test(fs, None, cx).await;
-        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let workspace = window.root(cx);
-        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-        add_labeled_item(&pane, "A", false, cx);
-        add_labeled_item(&pane, "B", false, cx);
-        add_labeled_item(&pane, "C", false, cx);
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, [], cx);
-
-        add_labeled_item(&pane, "A", true, cx);
-        add_labeled_item(&pane, "B", true, cx);
-        add_labeled_item(&pane, "C", true, cx);
-        assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
-
-        let save = pane
-            .update(cx, |pane, cx| {
-                pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
-            })
-            .unwrap();
-
-        cx.foreground().run_until_parked();
-        window.simulate_prompt_answer(2, cx);
-        save.await.unwrap();
-        assert_item_labels(&pane, [], cx);
-    }
-
-    fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            cx.set_global(SettingsStore::test(cx));
-            theme::init((), cx);
-            crate::init_settings(cx);
-            Project::init_settings(cx);
-        });
-    }
-
-    fn add_labeled_item(
-        pane: &ViewHandle<Pane>,
-        label: &str,
-        is_dirty: bool,
-        cx: &mut TestAppContext,
-    ) -> Box<ViewHandle<TestItem>> {
-        pane.update(cx, |pane, cx| {
-            let labeled_item =
-                Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
-            pane.add_item(labeled_item.clone(), false, false, None, cx);
-            labeled_item
-        })
-    }
-
-    fn set_labeled_items<const COUNT: usize>(
-        pane: &ViewHandle<Pane>,
-        labels: [&str; COUNT],
-        cx: &mut TestAppContext,
-    ) -> [Box<ViewHandle<TestItem>>; COUNT] {
-        pane.update(cx, |pane, cx| {
-            pane.items.clear();
-            let mut active_item_index = 0;
-
-            let mut index = 0;
-            let items = labels.map(|mut label| {
-                if label.ends_with("*") {
-                    label = label.trim_end_matches("*");
-                    active_item_index = index;
-                }
-
-                let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
-                pane.add_item(labeled_item.clone(), false, false, None, cx);
-                index += 1;
-                labeled_item
-            });
-
-            pane.activate_item(active_item_index, false, false, cx);
-
-            items
-        })
-    }
-
-    // Assert the item label, with the active item label suffixed with a '*'
-    fn assert_item_labels<const COUNT: usize>(
-        pane: &ViewHandle<Pane>,
-        expected_states: [&str; COUNT],
-        cx: &mut TestAppContext,
-    ) {
-        pane.read_with(cx, |pane, cx| {
-            let actual_states = pane
-                .items
-                .iter()
-                .enumerate()
-                .map(|(ix, item)| {
-                    let mut state = item
-                        .as_any()
-                        .downcast_ref::<TestItem>()
-                        .unwrap()
-                        .read(cx)
-                        .label
-                        .clone();
-                    if ix == pane.active_item_index {
-                        state.push('*');
-                    }
-                    if item.is_dirty(cx) {
-                        state.push('^');
-                    }
-                    state
-                })
-                .collect::<Vec<_>>();
-
-            assert_eq!(
-                actual_states, expected_states,
-                "pane items do not match expectation"
-            );
-        })
-    }
-}
+// impl NavHistoryState {
+//     pub fn did_update(&self, cx: &mut WindowContext) {
+//         if let Some(pane) = self.pane.upgrade(cx) {
+//             cx.defer(move |cx| {
+//                 pane.update(cx, |pane, cx| pane.history_updated(cx));
+//             });
+//         }
+//     }
+// }
+
+// pub struct PaneBackdrop<V> {
+//     child_view: usize,
+//     child: AnyElement<V>,
+// }
+
+// impl<V> PaneBackdrop<V> {
+//     pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
+//         PaneBackdrop {
+//             child,
+//             child_view: pane_item_view,
+//         }
+//     }
+// }
+
+// impl<V: 'static> Element<V> for PaneBackdrop<V> {
+//     type LayoutState = ();
+
+//     type PaintState = ();
+
+//     fn layout(
+//         &mut self,
+//         constraint: gpui::SizeConstraint,
+//         view: &mut V,
+//         cx: &mut ViewContext<V>,
+//     ) -> (Vector2F, Self::LayoutState) {
+//         let size = self.child.layout(constraint, view, cx);
+//         (size, ())
+//     }
+
+//     fn paint(
+//         &mut self,
+//         bounds: RectF,
+//         visible_bounds: RectF,
+//         _: &mut Self::LayoutState,
+//         view: &mut V,
+//         cx: &mut ViewContext<V>,
+//     ) -> Self::PaintState {
+//         let background = theme::current(cx).editor.background;
+
+//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+//         cx.scene().push_quad(gpui::Quad {
+//             bounds: RectF::new(bounds.origin(), bounds.size()),
+//             background: Some(background),
+//             ..Default::default()
+//         });
+
+//         let child_view_id = self.child_view;
+//         cx.scene().push_mouse_region(
+//             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
+//                 gpui::platform::MouseButton::Left,
+//                 move |_, _: &mut V, cx| {
+//                     let window = cx.window();
+//                     cx.app_context().focus(window, Some(child_view_id))
+//                 },
+//             ),
+//         );
+
+//         cx.scene().push_layer(Some(bounds));
+//         self.child.paint(bounds.origin(), visible_bounds, view, cx);
+//         cx.scene().pop_layer();
+//     }
+
+//     fn rect_for_text_range(
+//         &self,
+//         range_utf16: std::ops::Range<usize>,
+//         _bounds: RectF,
+//         _visible_bounds: RectF,
+//         _layout: &Self::LayoutState,
+//         _paint: &Self::PaintState,
+//         view: &V,
+//         cx: &gpui::ViewContext<V>,
+//     ) -> Option<RectF> {
+//         self.child.rect_for_text_range(range_utf16, view, cx)
+//     }
+
+//     fn debug(
+//         &self,
+//         _bounds: RectF,
+//         _layout: &Self::LayoutState,
+//         _paint: &Self::PaintState,
+//         view: &V,
+//         cx: &gpui::ViewContext<V>,
+//     ) -> serde_json::Value {
+//         gpui::json::json!({
+//             "type": "Pane Back Drop",
+//             "view": self.child_view,
+//             "child": self.child.debug(view, cx),
+//         })
+//     }
+// }
+
+// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
+//     let path = buffer_path
+//         .as_ref()
+//         .and_then(|p| p.path.to_str())
+//         .unwrap_or(&"This buffer");
+//     let path = truncate_and_remove_front(path, 80);
+//     format!("{path} contains unsaved edits. Do you want to save it?")
+// }
+
+// todo!("uncomment tests")
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate::item::test::{TestItem, TestProjectItem};
+//     use gpui::TestAppContext;
+//     use project::FakeFs;
+//     use settings::SettingsStore;
+
+//     #[gpui::test]
+//     async fn test_remove_active_empty(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         pane.update(cx, |pane, cx| {
+//             assert!(pane
+//                 .close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//                 .is_none())
+//         });
+//     }
+
+//     #[gpui::test]
+//     async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         // 1. Add with a destination index
+//         //   a. Add before the active item
+//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 Some(0),
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
+
+//         //   b. Add after the active item
+//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 Some(2),
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
+
+//         //   c. Add at the end of the item list (including off the length)
+//         set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 Some(5),
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+//         // 2. Add without a destination index
+//         //   a. Add with active item at the start of the item list
+//         set_labeled_items(&pane, ["A*", "B", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 None,
+//                 cx,
+//             );
+//         });
+//         set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
+
+//         //   b. Add with active item at the end of the item list
+//         set_labeled_items(&pane, ["A", "B", "C*"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+//                 false,
+//                 false,
+//                 None,
+//                 cx,
+//             );
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         // 1. Add with a destination index
+//         //   1a. Add before the active item
+//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(d, false, false, Some(0), cx);
+//         });
+//         assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
+
+//         //   1b. Add after the active item
+//         let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(d, false, false, Some(2), cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
+
+//         //   1c. Add at the end of the item list (including off the length)
+//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(a, false, false, Some(5), cx);
+//         });
+//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
+
+//         //   1d. Add same item to active index
+//         let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(b, false, false, Some(1), cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
+
+//         //   1e. Add item to index after same item in last position
+//         let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(c, false, false, Some(2), cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+//         // 2. Add without a destination index
+//         //   2a. Add with active item at the start of the item list
+//         let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(d, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
+
+//         //   2b. Add with active item at the end of the item list
+//         let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(a, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
+
+//         //   2c. Add active item to active item at end of list
+//         let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(c, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+//         //   2d. Add active item to active item at start of list
+//         let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
+//         pane.update(cx, |pane, cx| {
+//             pane.add_item(a, false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["A*", "B", "C"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
+//         cx.foreground().forbid_parking();
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         // singleton view
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(true)
+//                 .with_label("buffer 1")
+//                 .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1*"], cx);
+
+//         // new singleton view with the same project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(true)
+//                 .with_label("buffer 1")
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1*"], cx);
+
+//         // new singleton view with different project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(true)
+//                 .with_label("buffer 2")
+//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
+
+//         // new multibuffer view with the same project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(false)
+//                 .with_label("multibuffer 1")
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
+
+//         // another multibuffer view with the same project entry
+//         pane.update(cx, |pane, cx| {
+//             let item = TestItem::new()
+//                 .with_singleton(false)
+//                 .with_label("multibuffer 1b")
+//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+//             pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+//         });
+//         assert_item_labels(
+//             &pane,
+//             ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
+//             cx,
+//         );
+//     }
+
+//     #[gpui::test]
+//     async fn test_remove_item_ordering(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         add_labeled_item(&pane, "A", false, cx);
+//         add_labeled_item(&pane, "B", false, cx);
+//         add_labeled_item(&pane, "C", false, cx);
+//         add_labeled_item(&pane, "D", false, cx);
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+//         pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
+//         add_labeled_item(&pane, "1", false, cx);
+//         assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
+
+//         pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
+//         assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "B*", "C"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "C*"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_inactive_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_inactive_items(&CloseInactiveItems, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["C*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_clean_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         add_labeled_item(&pane, "A", true, cx);
+//         add_labeled_item(&pane, "B", false, cx);
+//         add_labeled_item(&pane, "C", true, cx);
+//         add_labeled_item(&pane, "D", false, cx);
+//         add_labeled_item(&pane, "E", false, cx);
+//         assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
+
+//         pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
+//             .unwrap()
+//             .await
+//             .unwrap();
+//         assert_item_labels(&pane, ["A^", "C*^"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["C*", "D", "E"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+//     }
+
+//     #[gpui::test]
+//     async fn test_close_all_items(cx: &mut TestAppContext) {
+//         init_test(cx);
+//         let fs = FakeFs::new(cx.background());
+
+//         let project = Project::test(fs, None, cx).await;
+//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+//         let workspace = window.root(cx);
+//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+//         add_labeled_item(&pane, "A", false, cx);
+//         add_labeled_item(&pane, "B", false, cx);
+//         add_labeled_item(&pane, "C", false, cx);
+//         assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+//         pane.update(cx, |pane, cx| {
+//             pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
+//         })
+//         .unwrap()
+//         .await
+//         .unwrap();
+//         assert_item_labels(&pane, [], cx);
+
+//         add_labeled_item(&pane, "A", true, cx);
+//         add_labeled_item(&pane, "B", true, cx);
+//         add_labeled_item(&pane, "C", true, cx);
+//         assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
+
+//         let save = pane
+//             .update(cx, |pane, cx| {
+//                 pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
+//             })
+//             .unwrap();
+
+//         cx.foreground().run_until_parked();
+//         window.simulate_prompt_answer(2, cx);
+//         save.await.unwrap();
+//         assert_item_labels(&pane, [], cx);
+//     }
+
+//     fn init_test(cx: &mut TestAppContext) {
+//         cx.update(|cx| {
+//             cx.set_global(SettingsStore::test(cx));
+//             theme::init((), cx);
+//             crate::init_settings(cx);
+//             Project::init_settings(cx);
+//         });
+//     }
+
+//     fn add_labeled_item(
+//         pane: &ViewHandle<Pane>,
+//         label: &str,
+//         is_dirty: bool,
+//         cx: &mut TestAppContext,
+//     ) -> Box<ViewHandle<TestItem>> {
+//         pane.update(cx, |pane, cx| {
+//             let labeled_item =
+//                 Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
+//             pane.add_item(labeled_item.clone(), false, false, None, cx);
+//             labeled_item
+//         })
+//     }
+
+//     fn set_labeled_items<const COUNT: usize>(
+//         pane: &ViewHandle<Pane>,
+//         labels: [&str; COUNT],
+//         cx: &mut TestAppContext,
+//     ) -> [Box<ViewHandle<TestItem>>; COUNT] {
+//         pane.update(cx, |pane, cx| {
+//             pane.items.clear();
+//             let mut active_item_index = 0;
+
+//             let mut index = 0;
+//             let items = labels.map(|mut label| {
+//                 if label.ends_with("*") {
+//                     label = label.trim_end_matches("*");
+//                     active_item_index = index;
+//                 }
+
+//                 let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
+//                 pane.add_item(labeled_item.clone(), false, false, None, cx);
+//                 index += 1;
+//                 labeled_item
+//             });
+
+//             pane.activate_item(active_item_index, false, false, cx);
+
+//             items
+//         })
+//     }
+
+//     // Assert the item label, with the active item label suffixed with a '*'
+//     fn assert_item_labels<const COUNT: usize>(
+//         pane: &ViewHandle<Pane>,
+//         expected_states: [&str; COUNT],
+//         cx: &mut TestAppContext,
+//     ) {
+//         pane.read_with(cx, |pane, cx| {
+//             let actual_states = pane
+//                 .items
+//                 .iter()
+//                 .enumerate()
+//                 .map(|(ix, item)| {
+//                     let mut state = item
+//                         .as_any()
+//                         .downcast_ref::<TestItem>()
+//                         .unwrap()
+//                         .read(cx)
+//                         .label
+//                         .clone();
+//                     if ix == pane.active_item_index {
+//                         state.push('*');
+//                     }
+//                     if item.is_dirty(cx) {
+//                         state.push('^');
+//                     }
+//                     state
+//                 })
+//                 .collect::<Vec<_>>();
+
+//             assert_eq!(
+//                 actual_states, expected_states,
+//                 "pane items do not match expectation"
+//             );
+//         })
+//     }
+// }

crates/workspace2/src/pane_group.rs 🔗

@@ -1,22 +1,22 @@
 use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, Result};
-use call::{ActiveCall, ParticipantLocation};
+use call2::{ActiveCall, ParticipantLocation};
 use collections::HashMap;
-use gpui::{
-    elements::*,
-    geometry::{rect::RectF, vector::Vector2F},
-    platform::{CursorStyle, MouseButton},
-    AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
-};
-use project::Project;
+use gpui2::{Bounds, Handle, Pixels, Point, View, ViewContext};
+use project2::Project;
 use serde::Deserialize;
 use std::{cell::RefCell, rc::Rc, sync::Arc};
-use theme::Theme;
+use theme2::Theme;
 
 const HANDLE_HITBOX_SIZE: f32 = 4.0;
 const HORIZONTAL_MIN_SIZE: f32 = 80.;
 const VERTICAL_MIN_SIZE: f32 = 100.;
 
+enum Axis {
+    Vertical,
+    Horizontal,
+}
+
 #[derive(Clone, Debug, PartialEq)]
 pub struct PaneGroup {
     pub(crate) root: Member,
@@ -27,7 +27,7 @@ impl PaneGroup {
         Self { root }
     }
 
-    pub fn new(pane: ViewHandle<Pane>) -> Self {
+    pub fn new(pane: View<Pane>) -> Self {
         Self {
             root: Member::Pane(pane),
         }
@@ -35,8 +35,8 @@ impl PaneGroup {
 
     pub fn split(
         &mut self,
-        old_pane: &ViewHandle<Pane>,
-        new_pane: &ViewHandle<Pane>,
+        old_pane: &View<Pane>,
+        new_pane: &View<Pane>,
         direction: SplitDirection,
     ) -> Result<()> {
         match &mut self.root {
@@ -52,14 +52,14 @@ impl PaneGroup {
         }
     }
 
-    pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
+    pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
         match &self.root {
             Member::Pane(_) => None,
             Member::Axis(axis) => axis.bounding_box_for_pane(pane),
         }
     }
 
-    pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
+    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
         match &self.root {
             Member::Pane(pane) => Some(pane),
             Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
@@ -70,7 +70,7 @@ impl PaneGroup {
     /// - Ok(true) if it found and removed a pane
     /// - Ok(false) if it found but did not remove the pane
     /// - Err(_) if it did not find the pane
-    pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
+    pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
         match &mut self.root {
             Member::Pane(_) => Ok(false),
             Member::Axis(axis) => {
@@ -82,7 +82,7 @@ impl PaneGroup {
         }
     }
 
-    pub fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
+    pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
         match &mut self.root {
             Member::Pane(_) => {}
             Member::Axis(axis) => axis.swap(from, to),
@@ -91,11 +91,11 @@ impl PaneGroup {
 
     pub(crate) fn render(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Handle<Project>,
         theme: &Theme,
-        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
-        active_call: Option<&ModelHandle<ActiveCall>>,
-        active_pane: &ViewHandle<Pane>,
+        follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Handle<ActiveCall>>,
+        active_pane: &View<Pane>,
         zoomed: Option<&AnyViewHandle>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
@@ -113,7 +113,7 @@ impl PaneGroup {
         )
     }
 
-    pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
+    pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
         let mut panes = Vec::new();
         self.root.collect_panes(&mut panes);
         panes
@@ -123,15 +123,11 @@ impl PaneGroup {
 #[derive(Clone, Debug, PartialEq)]
 pub(crate) enum Member {
     Axis(PaneAxis),
-    Pane(ViewHandle<Pane>),
+    Pane(View<Pane>),
 }
 
 impl Member {
-    fn new_axis(
-        old_pane: ViewHandle<Pane>,
-        new_pane: ViewHandle<Pane>,
-        direction: SplitDirection,
-    ) -> Self {
+    fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
         use Axis::*;
         use SplitDirection::*;
 
@@ -148,7 +144,7 @@ impl Member {
         Member::Axis(PaneAxis::new(axis, members))
     }
 
-    fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
+    fn contains(&self, needle: &View<Pane>) -> bool {
         match self {
             Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
             Member::Pane(pane) => pane == needle,
@@ -157,12 +153,12 @@ impl Member {
 
     pub fn render(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Handle<Project>,
         basis: usize,
         theme: &Theme,
-        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
-        active_call: Option<&ModelHandle<ActiveCall>>,
-        active_pane: &ViewHandle<Pane>,
+        follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Handle<ActiveCall>>,
+        active_pane: &View<Pane>,
         zoomed: Option<&AnyViewHandle>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
@@ -295,7 +291,7 @@ impl Member {
         }
     }
 
-    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
+    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
         match self {
             Member::Axis(axis) => {
                 for member in &axis.members {
@@ -343,8 +339,8 @@ impl PaneAxis {
 
     fn split(
         &mut self,
-        old_pane: &ViewHandle<Pane>,
-        new_pane: &ViewHandle<Pane>,
+        old_pane: &View<Pane>,
+        new_pane: &View<Pane>,
         direction: SplitDirection,
     ) -> Result<()> {
         for (mut idx, member) in self.members.iter_mut().enumerate() {
@@ -375,7 +371,7 @@ impl PaneAxis {
         Err(anyhow!("Pane not found"))
     }
 
-    fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
+    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
         let mut found_pane = false;
         let mut remove_member = None;
         for (idx, member) in self.members.iter_mut().enumerate() {
@@ -417,7 +413,7 @@ impl PaneAxis {
         }
     }
 
-    fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
+    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
         for member in self.members.iter_mut() {
             match member {
                 Member::Axis(axis) => axis.swap(from, to),
@@ -432,7 +428,7 @@ impl PaneAxis {
         }
     }
 
-    fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
+    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<RectF> {
         debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
 
         for (idx, member) in self.members.iter().enumerate() {
@@ -452,7 +448,7 @@ impl PaneAxis {
         None
     }
 
-    fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
+    fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&View<Pane>> {
         debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
 
         let bounding_boxes = self.bounding_boxes.borrow();
@@ -472,12 +468,12 @@ impl PaneAxis {
 
     fn render(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Handle<Project>,
         basis: usize,
         theme: &Theme,
-        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
-        active_call: Option<&ModelHandle<ActiveCall>>,
-        active_pane: &ViewHandle<Pane>,
+        follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Handle<ActiveCall>>,
+        active_pane: &View<Pane>,
         zoomed: Option<&AnyViewHandle>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,

crates/workspace2/src/searchable.rs 🔗

@@ -1,12 +1,12 @@
 use std::{any::Any, sync::Arc};
 
-use gpui::{
-    AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
-    WeakViewHandle, WindowContext,
-};
-use project::search::SearchQuery;
+use gpui2::{AppContext, Subscription, Task, View, ViewContext, WindowContext};
+use project2::search::SearchQuery;
 
-use crate::{item::WeakItemHandle, Item, ItemHandle};
+use crate::{
+    item::{Item, WeakItemHandle},
+    ItemHandle,
+};
 
 #[derive(Debug)]
 pub enum SearchEvent {
@@ -128,7 +128,7 @@ pub trait SearchableItemHandle: ItemHandle {
     ) -> Option<usize>;
 }
 
-impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
+impl<T: SearchableItem> SearchableItemHandle for View<T> {
     fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
         Box::new(self.downgrade())
     }
@@ -231,17 +231,19 @@ fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T
         )
 }
 
-impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
-    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
-        this.as_any().clone()
-    }
-}
+// todo!()
+// impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
+//     fn from(this: Box<dyn SearchableItemHandle>) -> Self {
+//         this.as_any().clone()
+//     }
+// }
 
-impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
-    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
-        this.as_any().clone()
-    }
-}
+// todo!()
+// impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
+//     fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
+//         this.as_any().clone()
+//     }
+// }
 
 impl PartialEq for Box<dyn SearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {
@@ -254,18 +256,20 @@ impl Eq for Box<dyn SearchableItemHandle> {}
 pub trait WeakSearchableItemHandle: WeakItemHandle {
     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
 
-    fn into_any(self) -> AnyWeakViewHandle;
+    // todo!()
+    // fn into_any(self) -> AnyWeakViewHandle;
 }
 
-impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(self.upgrade(cx)?))
-    }
+// todo!()
+// impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
+//     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+//         Some(Box::new(self.upgrade(cx)?))
+//     }
 
-    fn into_any(self) -> AnyWeakViewHandle {
-        self.into_any()
-    }
-}
+//     fn into_any(self) -> AnyWeakViewHandle {
+//         self.into_any()
+//     }
+// }
 
 impl PartialEq for Box<dyn WeakSearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {

crates/workspace2/src/workspace2.rs 🔗

@@ -1,16 +1,16 @@
 // pub mod dock;
 pub mod item;
 // pub mod notifications;
-// pub mod pane;
-// pub mod pane_group;
+pub mod pane;
+pub mod pane_group;
 // mod persistence;
-// pub mod searchable;
+pub mod searchable;
 // pub mod shared_screen;
 // mod status_bar;
-// mod toolbar;
+mod toolbar;
 // mod workspace_settings;
 
-// use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Result};
 // use call2::ActiveCall;
 // use client2::{
 //     proto::{self, PeerId},
@@ -52,8 +52,8 @@ pub mod item;
 // use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
 // use lazy_static::lazy_static;
 // use notifications::{NotificationHandle, NotifyResultExt};
-// pub use pane::*;
-// pub use pane_group::*;
+pub use pane::*;
+pub use pane_group::*;
 // use persistence::{model::SerializedItem, DB};
 // pub use persistence::{
 //     model::{ItemId, WorkspaceLocation},
@@ -66,7 +66,7 @@ pub mod item;
 // use status_bar::StatusBar;
 // pub use status_bar::StatusItemView;
 // use theme::{Theme, ThemeSettings};
-// pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
+pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
 // use util::ResultExt;
 // pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
 
@@ -234,7 +234,7 @@ pub mod item;
 //     ]
 // );
 
-// pub type WorkspaceId = i64;
+pub type WorkspaceId = i64;
 
 // pub fn init_settings(cx: &mut AppContext) {
 //     settings::register::<WorkspaceSettings>(cx);
@@ -365,3381 +365,3381 @@ pub mod item;
 //     });
 // }
 
-// type ProjectItemBuilders = HashMap<
-//     TypeId,
-//     fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
-// >;
-// pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
-//     cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
-//         builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
-//             let item = model.downcast::<I::Item>().unwrap();
-//             Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
-//         });
-//     });
-// }
-
-// type FollowableItemBuilder = fn(
-//     ViewHandle<Pane>,
-//     ViewHandle<Workspace>,
-//     ViewId,
-//     &mut Option<proto::view::Variant>,
-//     &mut AppContext,
-// ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
-// type FollowableItemBuilders = HashMap<
-//     TypeId,
-//     (
-//         FollowableItemBuilder,
-//         fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
-//     ),
-// >;
-// pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
-//     cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
-//         builders.insert(
-//             TypeId::of::<I>(),
-//             (
-//                 |pane, workspace, id, state, cx| {
-//                     I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
-//                         cx.foreground()
-//                             .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
-//                     })
-//                 },
-//                 |this| Box::new(this.clone().downcast::<I>().unwrap()),
-//             ),
-//         );
-//     });
-// }
-
-// type ItemDeserializers = HashMap<
-//     Arc<str>,
-//     fn(
-//         ModelHandle<Project>,
-//         WeakViewHandle<Workspace>,
-//         WorkspaceId,
-//         ItemId,
-//         &mut ViewContext<Pane>,
-//     ) -> Task<Result<Box<dyn ItemHandle>>>,
-// >;
-// pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
-//     cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
-//         if let Some(serialized_item_kind) = I::serialized_item_kind() {
-//             deserializers.insert(
-//                 Arc::from(serialized_item_kind),
-//                 |project, workspace, workspace_id, item_id, cx| {
-//                     let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
-//                     cx.foreground()
-//                         .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
-//                 },
-//             );
-//         }
-//     });
-// }
-
-pub struct AppState {
-    pub languages: Arc<LanguageRegistry>,
-    pub client: Arc<Client>,
-    pub user_store: Handle<UserStore>,
-    pub workspace_store: Handle<WorkspaceStore>,
-    pub fs: Arc<dyn fs2::Fs>,
-    pub build_window_options:
-        fn(Option<WindowBounds>, Option<DisplayId>, &MainThread<AppContext>) -> WindowOptions,
-    pub initialize_workspace:
-        fn(WeakHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<anyhow::Result<()>>,
-    pub node_runtime: Arc<dyn NodeRuntime>,
-}
-
-pub struct WorkspaceStore {
-    workspaces: HashSet<WeakHandle<Workspace>>,
-    followers: Vec<Follower>,
-    client: Arc<Client>,
-    _subscriptions: Vec<client2::Subscription>,
-}
-
-#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
-struct Follower {
-    project_id: Option<u64>,
-    peer_id: PeerId,
-}
-
-// impl AppState {
-//     #[cfg(any(test, feature = "test-support"))]
-//     pub fn test(cx: &mut AppContext) -> Arc<Self> {
-//         use node_runtime::FakeNodeRuntime;
-//         use settings::SettingsStore;
-
-//         if !cx.has_global::<SettingsStore>() {
-//             cx.set_global(SettingsStore::test(cx));
-//         }
-
-//         let fs = fs::FakeFs::new(cx.background().clone());
-//         let languages = Arc::new(LanguageRegistry::test());
-//         let http_client = util::http::FakeHttpClient::with_404_response();
-//         let client = Client::new(http_client.clone(), cx);
-//         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-//         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
-
-//         theme::init((), cx);
-//         client::init(&client, cx);
-//         crate::init_settings(cx);
-
-//         Arc::new(Self {
-//             client,
-//             fs,
-//             languages,
-//             user_store,
-//             // channel_store,
-//             workspace_store,
-//             node_runtime: FakeNodeRuntime::new(),
-//             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
-//             build_window_options: |_, _, _| Default::default(),
-//         })
-//     }
-// }
-
-// struct DelayedDebouncedEditAction {
-//     task: Option<Task<()>>,
-//     cancel_channel: Option<oneshot::Sender<()>>,
-// }
-
-// impl DelayedDebouncedEditAction {
-//     fn new() -> DelayedDebouncedEditAction {
-//         DelayedDebouncedEditAction {
-//             task: None,
-//             cancel_channel: None,
-//         }
-//     }
-
-//     fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
-//     where
-//         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
-//     {
-//         if let Some(channel) = self.cancel_channel.take() {
-//             _ = channel.send(());
-//         }
-
-//         let (sender, mut receiver) = oneshot::channel::<()>();
-//         self.cancel_channel = Some(sender);
-
-//         let previous_task = self.task.take();
-//         self.task = Some(cx.spawn(|workspace, mut cx| async move {
-//             let mut timer = cx.background().timer(delay).fuse();
-//             if let Some(previous_task) = previous_task {
-//                 previous_task.await;
-//             }
-
-//             futures::select_biased! {
-//                 _ = receiver => return,
-//                     _ = timer => {}
-//             }
-
-//             if let Some(result) = workspace
-//                 .update(&mut cx, |workspace, cx| (func)(workspace, cx))
-//                 .log_err()
-//             {
-//                 result.await.log_err();
-//             }
-//         }));
-//     }
-// }
-
-// pub enum Event {
-//     PaneAdded(ViewHandle<Pane>),
-//     ContactRequestedJoin(u64),
-// }
-
-pub struct Workspace {
-    weak_self: WeakHandle<Self>,
-    //     modal: Option<ActiveModal>,
-    //     zoomed: Option<AnyWeakViewHandle>,
-    //     zoomed_position: Option<DockPosition>,
-    //     center: PaneGroup,
-    //     left_dock: ViewHandle<Dock>,
-    //     bottom_dock: ViewHandle<Dock>,
-    //     right_dock: ViewHandle<Dock>,
-    //     panes: Vec<ViewHandle<Pane>>,
-    //     panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
-    //     active_pane: ViewHandle<Pane>,
-    //     last_active_center_pane: Option<WeakViewHandle<Pane>>,
-    //     last_active_view_id: Option<proto::ViewId>,
-    //     status_bar: ViewHandle<StatusBar>,
-    //     titlebar_item: Option<AnyViewHandle>,
-    //     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
-    project: Handle<Project>,
-    //     follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
-    //     last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
-    //     window_edited: bool,
-    //     active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
-    //     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
-    //     database_id: WorkspaceId,
-    //     app_state: Arc<AppState>,
-    //     subscriptions: Vec<Subscription>,
-    //     _apply_leader_updates: Task<Result<()>>,
-    //     _observe_current_user: Task<Result<()>>,
-    //     _schedule_serialize: Option<Task<()>>,
-    //     pane_history_timestamp: Arc<AtomicUsize>,
-}
-
-// struct ActiveModal {
-//     view: Box<dyn ModalHandle>,
-//     previously_focused_view_id: Option<usize>,
-// }
-
-// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-// pub struct ViewId {
-//     pub creator: PeerId,
-//     pub id: u64,
-// }
-
-// #[derive(Default)]
-// struct FollowerState {
-//     leader_id: PeerId,
-//     active_view_id: Option<ViewId>,
-//     items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
-// }
-
-// enum WorkspaceBounds {}
-
-// impl Workspace {
-//     pub fn new(
-//         workspace_id: WorkspaceId,
-//         project: ModelHandle<Project>,
-//         app_state: Arc<AppState>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Self {
-//         cx.observe(&project, |_, _, cx| cx.notify()).detach();
-//         cx.subscribe(&project, move |this, _, event, cx| {
-//             match event {
-//                 project::Event::RemoteIdChanged(_) => {
-//                     this.update_window_title(cx);
-//                 }
-
-//                 project::Event::CollaboratorLeft(peer_id) => {
-//                     this.collaborator_left(*peer_id, cx);
-//                 }
-
-//                 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
-//                     this.update_window_title(cx);
-//                     this.serialize_workspace(cx);
-//                 }
-
-//                 project::Event::DisconnectedFromHost => {
-//                     this.update_window_edited(cx);
-//                     cx.blur();
-//                 }
-
-//                 project::Event::Closed => {
-//                     cx.remove_window();
-//                 }
-
-//                 project::Event::DeletedEntry(entry_id) => {
-//                     for pane in this.panes.iter() {
-//                         pane.update(cx, |pane, cx| {
-//                             pane.handle_deleted_project_item(*entry_id, cx)
-//                         });
-//                     }
-//                 }
-
-//                 project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
-//                     cx.add_view(|_| MessageNotification::new(message.clone()))
-//                 }),
-
-//                 _ => {}
-//             }
-//             cx.notify()
-//         })
-//         .detach();
-
-//         let weak_handle = cx.weak_handle();
-//         let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
-
-//         let center_pane = cx.add_view(|cx| {
-//             Pane::new(
-//                 weak_handle.clone(),
-//                 project.clone(),
-//                 pane_history_timestamp.clone(),
-//                 cx,
-//             )
-//         });
-//         cx.subscribe(&center_pane, Self::handle_pane_event).detach();
-//         cx.focus(&center_pane);
-//         cx.emit(Event::PaneAdded(center_pane.clone()));
-
-//         app_state.workspace_store.update(cx, |store, _| {
-//             store.workspaces.insert(weak_handle.clone());
-//         });
-
-//         let mut current_user = app_state.user_store.read(cx).watch_current_user();
-//         let mut connection_status = app_state.client.status();
-//         let _observe_current_user = cx.spawn(|this, mut cx| async move {
-//             current_user.recv().await;
-//             connection_status.recv().await;
-//             let mut stream =
-//                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
-
-//             while stream.recv().await.is_some() {
-//                 this.update(&mut cx, |_, cx| cx.notify())?;
-//             }
-//             anyhow::Ok(())
-//         });
-
-//         // All leader updates are enqueued and then processed in a single task, so
-//         // that each asynchronous operation can be run in order.
-//         let (leader_updates_tx, mut leader_updates_rx) =
-//             mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
-//         let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
-//             while let Some((leader_id, update)) = leader_updates_rx.next().await {
-//                 Self::process_leader_update(&this, leader_id, update, &mut cx)
-//                     .await
-//                     .log_err();
-//             }
-
-//             Ok(())
-//         });
-
-//         cx.emit_global(WorkspaceCreated(weak_handle.clone()));
-
-//         let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
-//         let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
-//         let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
-//         let left_dock_buttons =
-//             cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
-//         let bottom_dock_buttons =
-//             cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
-//         let right_dock_buttons =
-//             cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
-//         let status_bar = cx.add_view(|cx| {
-//             let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
-//             status_bar.add_left_item(left_dock_buttons, cx);
-//             status_bar.add_right_item(right_dock_buttons, cx);
-//             status_bar.add_right_item(bottom_dock_buttons, cx);
-//             status_bar
-//         });
-
-//         cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
-//             drag_and_drop.register_container(weak_handle.clone());
-//         });
-
-//         let mut active_call = None;
-//         if cx.has_global::<ModelHandle<ActiveCall>>() {
-//             let call = cx.global::<ModelHandle<ActiveCall>>().clone();
-//             let mut subscriptions = Vec::new();
-//             subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
-//             active_call = Some((call, subscriptions));
-//         }
-
-//         let subscriptions = vec![
-//             cx.observe_fullscreen(|_, _, cx| cx.notify()),
-//             cx.observe_window_activation(Self::on_window_activation_changed),
-//             cx.observe_window_bounds(move |_, mut bounds, display, cx| {
-//                 // Transform fixed bounds to be stored in terms of the containing display
-//                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
-//                     if let Some(screen) = cx.platform().screen_by_id(display) {
-//                         let screen_bounds = screen.bounds();
-//                         window_bounds
-//                             .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
-//                         window_bounds
-//                             .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
-//                         bounds = WindowBounds::Fixed(window_bounds);
-//                     }
-//                 }
-
-//                 cx.background()
-//                     .spawn(DB.set_window_bounds(workspace_id, bounds, display))
-//                     .detach_and_log_err(cx);
-//             }),
-//             cx.observe(&left_dock, |this, _, cx| {
-//                 this.serialize_workspace(cx);
-//                 cx.notify();
-//             }),
-//             cx.observe(&bottom_dock, |this, _, cx| {
-//                 this.serialize_workspace(cx);
-//                 cx.notify();
-//             }),
-//             cx.observe(&right_dock, |this, _, cx| {
-//                 this.serialize_workspace(cx);
-//                 cx.notify();
-//             }),
-//         ];
-
-//         cx.defer(|this, cx| this.update_window_title(cx));
-//         Workspace {
-//             weak_self: weak_handle.clone(),
-//             modal: None,
-//             zoomed: None,
-//             zoomed_position: None,
-//             center: PaneGroup::new(center_pane.clone()),
-//             panes: vec![center_pane.clone()],
-//             panes_by_item: Default::default(),
-//             active_pane: center_pane.clone(),
-//             last_active_center_pane: Some(center_pane.downgrade()),
-//             last_active_view_id: None,
-//             status_bar,
-//             titlebar_item: None,
-//             notifications: Default::default(),
-//             left_dock,
-//             bottom_dock,
-//             right_dock,
-//             project: project.clone(),
-//             follower_states: Default::default(),
-//             last_leaders_by_pane: Default::default(),
-//             window_edited: false,
-//             active_call,
-//             database_id: workspace_id,
-//             app_state,
-//             _observe_current_user,
-//             _apply_leader_updates,
-//             _schedule_serialize: None,
-//             leader_updates_tx,
-//             subscriptions,
-//             pane_history_timestamp,
-//         }
-//     }
-
-//     fn new_local(
-//         abs_paths: Vec<PathBuf>,
-//         app_state: Arc<AppState>,
-//         requesting_window: Option<WindowHandle<Workspace>>,
-//         cx: &mut AppContext,
-//     ) -> Task<(
-//         WeakViewHandle<Workspace>,
-//         Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-//     )> {
-//         let project_handle = Project::local(
-//             app_state.client.clone(),
-//             app_state.node_runtime.clone(),
-//             app_state.user_store.clone(),
-//             app_state.languages.clone(),
-//             app_state.fs.clone(),
-//             cx,
-//         );
-
-//         cx.spawn(|mut cx| async move {
-//             let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
-
-//             let paths_to_open = Arc::new(abs_paths);
-
-//             // Get project paths for all of the abs_paths
-//             let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
-//             let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
-//                 Vec::with_capacity(paths_to_open.len());
-//             for path in paths_to_open.iter().cloned() {
-//                 if let Some((worktree, project_entry)) = cx
-//                     .update(|cx| {
-//                         Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
-//                     })
-//                     .await
-//                     .log_err()
-//                 {
-//                     worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
-//                     project_paths.push((path, Some(project_entry)));
-//                 } else {
-//                     project_paths.push((path, None));
-//                 }
-//             }
-
-//             let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
-//                 serialized_workspace.id
-//             } else {
-//                 DB.next_id().await.unwrap_or(0)
-//             };
-
-//             let window = if let Some(window) = requesting_window {
-//                 window.replace_root(&mut cx, |cx| {
-//                     Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-//                 });
-//                 window
-//             } else {
-//                 {
-//                     let window_bounds_override = window_bounds_env_override(&cx);
-//                     let (bounds, display) = if let Some(bounds) = window_bounds_override {
-//                         (Some(bounds), None)
-//                     } else {
-//                         serialized_workspace
-//                             .as_ref()
-//                             .and_then(|serialized_workspace| {
-//                                 let display = serialized_workspace.display?;
-//                                 let mut bounds = serialized_workspace.bounds?;
-
-//                                 // Stored bounds are relative to the containing display.
-//                                 // So convert back to global coordinates if that screen still exists
-//                                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
-//                                     if let Some(screen) = cx.platform().screen_by_id(display) {
-//                                         let screen_bounds = screen.bounds();
-//                                         window_bounds.set_origin_x(
-//                                             window_bounds.origin_x() + screen_bounds.origin_x(),
-//                                         );
-//                                         window_bounds.set_origin_y(
-//                                             window_bounds.origin_y() + screen_bounds.origin_y(),
-//                                         );
-//                                         bounds = WindowBounds::Fixed(window_bounds);
-//                                     } else {
-//                                         // Screen no longer exists. Return none here.
-//                                         return None;
-//                                     }
-//                                 }
-
-//                                 Some((bounds, display))
-//                             })
-//                             .unzip()
-//                     };
-
-//                     // Use the serialized workspace to construct the new window
-//                     cx.add_window(
-//                         (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
-//                         |cx| {
-//                             Workspace::new(
-//                                 workspace_id,
-//                                 project_handle.clone(),
-//                                 app_state.clone(),
-//                                 cx,
-//                             )
-//                         },
-//                     )
-//                 }
-//             };
-
-//             // We haven't yielded the main thread since obtaining the window handle,
-//             // so the window exists.
-//             let workspace = window.root(&cx).unwrap();
-
-//             (app_state.initialize_workspace)(
-//                 workspace.downgrade(),
-//                 serialized_workspace.is_some(),
-//                 app_state.clone(),
-//                 cx.clone(),
-//             )
-//             .await
-//             .log_err();
-
-//             window.update(&mut cx, |cx| cx.activate_window());
-
-//             let workspace = workspace.downgrade();
-//             notify_if_database_failed(&workspace, &mut cx);
-//             let opened_items = open_items(
-//                 serialized_workspace,
-//                 &workspace,
-//                 project_paths,
-//                 app_state,
-//                 cx,
-//             )
-//             .await
-//             .unwrap_or_default();
-
-//             (workspace, opened_items)
-//         })
-//     }
-
-//     pub fn weak_handle(&self) -> WeakViewHandle<Self> {
-//         self.weak_self.clone()
-//     }
-
-//     pub fn left_dock(&self) -> &ViewHandle<Dock> {
-//         &self.left_dock
-//     }
-
-//     pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
-//         &self.bottom_dock
-//     }
-
-//     pub fn right_dock(&self) -> &ViewHandle<Dock> {
-//         &self.right_dock
-//     }
-
-//     pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
-//     where
-//         T::Event: std::fmt::Debug,
-//     {
-//         self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
-//     }
-
-//     pub fn add_panel_with_extra_event_handler<T: Panel, F>(
-//         &mut self,
-//         panel: ViewHandle<T>,
-//         cx: &mut ViewContext<Self>,
-//         handler: F,
-//     ) where
-//         T::Event: std::fmt::Debug,
-//         F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
-//     {
-//         let dock = match panel.position(cx) {
-//             DockPosition::Left => &self.left_dock,
-//             DockPosition::Bottom => &self.bottom_dock,
-//             DockPosition::Right => &self.right_dock,
-//         };
-
-//         self.subscriptions.push(cx.subscribe(&panel, {
-//             let mut dock = dock.clone();
-//             let mut prev_position = panel.position(cx);
-//             move |this, panel, event, cx| {
-//                 if T::should_change_position_on_event(event) {
-//                     let new_position = panel.read(cx).position(cx);
-//                     let mut was_visible = false;
-//                     dock.update(cx, |dock, cx| {
-//                         prev_position = new_position;
-
-//                         was_visible = dock.is_open()
-//                             && dock
-//                                 .visible_panel()
-//                                 .map_or(false, |active_panel| active_panel.id() == panel.id());
-//                         dock.remove_panel(&panel, cx);
-//                     });
-
-//                     if panel.is_zoomed(cx) {
-//                         this.zoomed_position = Some(new_position);
-//                     }
-
-//                     dock = match panel.read(cx).position(cx) {
-//                         DockPosition::Left => &this.left_dock,
-//                         DockPosition::Bottom => &this.bottom_dock,
-//                         DockPosition::Right => &this.right_dock,
-//                     }
-//                     .clone();
-//                     dock.update(cx, |dock, cx| {
-//                         dock.add_panel(panel.clone(), cx);
-//                         if was_visible {
-//                             dock.set_open(true, cx);
-//                             dock.activate_panel(dock.panels_len() - 1, cx);
-//                         }
-//                     });
-//                 } else if T::should_zoom_in_on_event(event) {
-//                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
-//                     if !panel.has_focus(cx) {
-//                         cx.focus(&panel);
-//                     }
-//                     this.zoomed = Some(panel.downgrade().into_any());
-//                     this.zoomed_position = Some(panel.read(cx).position(cx));
-//                 } else if T::should_zoom_out_on_event(event) {
-//                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
-//                     if this.zoomed_position == Some(prev_position) {
-//                         this.zoomed = None;
-//                         this.zoomed_position = None;
-//                     }
-//                     cx.notify();
-//                 } else if T::is_focus_event(event) {
-//                     let position = panel.read(cx).position(cx);
-//                     this.dismiss_zoomed_items_to_reveal(Some(position), cx);
-//                     if panel.is_zoomed(cx) {
-//                         this.zoomed = Some(panel.downgrade().into_any());
-//                         this.zoomed_position = Some(position);
-//                     } else {
-//                         this.zoomed = None;
-//                         this.zoomed_position = None;
-//                     }
-//                     this.update_active_view_for_followers(cx);
-//                     cx.notify();
-//                 } else {
-//                     handler(this, &panel, event, cx)
-//                 }
-//             }
-//         }));
-
-//         dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
-//     }
-
-//     pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
-//         &self.status_bar
-//     }
-
-//     pub fn app_state(&self) -> &Arc<AppState> {
-//         &self.app_state
-//     }
-
-//     pub fn user_store(&self) -> &ModelHandle<UserStore> {
-//         &self.app_state.user_store
-//     }
-
-//     pub fn project(&self) -> &ModelHandle<Project> {
-//         &self.project
-//     }
-
-//     pub fn recent_navigation_history(
-//         &self,
-//         limit: Option<usize>,
-//         cx: &AppContext,
-//     ) -> Vec<(ProjectPath, Option<PathBuf>)> {
-//         let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
-//         let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
-//         for pane in &self.panes {
-//             let pane = pane.read(cx);
-//             pane.nav_history()
-//                 .for_each_entry(cx, |entry, (project_path, fs_path)| {
-//                     if let Some(fs_path) = &fs_path {
-//                         abs_paths_opened
-//                             .entry(fs_path.clone())
-//                             .or_default()
-//                             .insert(project_path.clone());
-//                     }
-//                     let timestamp = entry.timestamp;
-//                     match history.entry(project_path) {
-//                         hash_map::Entry::Occupied(mut entry) => {
-//                             let (_, old_timestamp) = entry.get();
-//                             if &timestamp > old_timestamp {
-//                                 entry.insert((fs_path, timestamp));
-//                             }
-//                         }
-//                         hash_map::Entry::Vacant(entry) => {
-//                             entry.insert((fs_path, timestamp));
-//                         }
-//                     }
-//                 });
-//         }
-
-//         history
-//             .into_iter()
-//             .sorted_by_key(|(_, (_, timestamp))| *timestamp)
-//             .map(|(project_path, (fs_path, _))| (project_path, fs_path))
-//             .rev()
-//             .filter(|(history_path, abs_path)| {
-//                 let latest_project_path_opened = abs_path
-//                     .as_ref()
-//                     .and_then(|abs_path| abs_paths_opened.get(abs_path))
-//                     .and_then(|project_paths| {
-//                         project_paths
-//                             .iter()
-//                             .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
-//                     });
-
-//                 match latest_project_path_opened {
-//                     Some(latest_project_path_opened) => latest_project_path_opened == history_path,
-//                     None => true,
-//                 }
-//             })
-//             .take(limit.unwrap_or(usize::MAX))
-//             .collect()
-//     }
-
-//     fn navigate_history(
-//         &mut self,
-//         pane: WeakViewHandle<Pane>,
-//         mode: NavigationMode,
-//         cx: &mut ViewContext<Workspace>,
-//     ) -> Task<Result<()>> {
-//         let to_load = if let Some(pane) = pane.upgrade(cx) {
-//             cx.focus(&pane);
-
-//             pane.update(cx, |pane, cx| {
-//                 loop {
-//                     // Retrieve the weak item handle from the history.
-//                     let entry = pane.nav_history_mut().pop(mode, cx)?;
-
-//                     // If the item is still present in this pane, then activate it.
-//                     if let Some(index) = entry
-//                         .item
-//                         .upgrade(cx)
-//                         .and_then(|v| pane.index_for_item(v.as_ref()))
-//                     {
-//                         let prev_active_item_index = pane.active_item_index();
-//                         pane.nav_history_mut().set_mode(mode);
-//                         pane.activate_item(index, true, true, cx);
-//                         pane.nav_history_mut().set_mode(NavigationMode::Normal);
-
-//                         let mut navigated = prev_active_item_index != pane.active_item_index();
-//                         if let Some(data) = entry.data {
-//                             navigated |= pane.active_item()?.navigate(data, cx);
-//                         }
-
-//                         if navigated {
-//                             break None;
-//                         }
-//                     }
-//                     // If the item is no longer present in this pane, then retrieve its
-//                     // project path in order to reopen it.
-//                     else {
-//                         break pane
-//                             .nav_history()
-//                             .path_for_item(entry.item.id())
-//                             .map(|(project_path, _)| (project_path, entry));
-//                     }
-//                 }
-//             })
-//         } else {
-//             None
-//         };
-
-//         if let Some((project_path, entry)) = to_load {
-//             // If the item was no longer present, then load it again from its previous path.
-//             let task = self.load_path(project_path, cx);
-//             cx.spawn(|workspace, mut cx| async move {
-//                 let task = task.await;
-//                 let mut navigated = false;
-//                 if let Some((project_entry_id, build_item)) = task.log_err() {
-//                     let prev_active_item_id = pane.update(&mut cx, |pane, _| {
-//                         pane.nav_history_mut().set_mode(mode);
-//                         pane.active_item().map(|p| p.id())
-//                     })?;
-
-//                     pane.update(&mut cx, |pane, cx| {
-//                         let item = pane.open_item(project_entry_id, true, cx, build_item);
-//                         navigated |= Some(item.id()) != prev_active_item_id;
-//                         pane.nav_history_mut().set_mode(NavigationMode::Normal);
-//                         if let Some(data) = entry.data {
-//                             navigated |= item.navigate(data, cx);
-//                         }
-//                     })?;
-//                 }
-
-//                 if !navigated {
-//                     workspace
-//                         .update(&mut cx, |workspace, cx| {
-//                             Self::navigate_history(workspace, pane, mode, cx)
-//                         })?
-//                         .await?;
-//                 }
-
-//                 Ok(())
-//             })
-//         } else {
-//             Task::ready(Ok(()))
-//         }
-//     }
-
-//     pub fn go_back(
-//         &mut self,
-//         pane: WeakViewHandle<Pane>,
-//         cx: &mut ViewContext<Workspace>,
-//     ) -> Task<Result<()>> {
-//         self.navigate_history(pane, NavigationMode::GoingBack, cx)
-//     }
-
-//     pub fn go_forward(
-//         &mut self,
-//         pane: WeakViewHandle<Pane>,
-//         cx: &mut ViewContext<Workspace>,
-//     ) -> Task<Result<()>> {
-//         self.navigate_history(pane, NavigationMode::GoingForward, cx)
-//     }
-
-//     pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
-//         self.navigate_history(
-//             self.active_pane().downgrade(),
-//             NavigationMode::ReopeningClosedItem,
-//             cx,
-//         )
-//     }
-
-//     pub fn client(&self) -> &Client {
-//         &self.app_state.client
-//     }
-
-//     pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
-//         self.titlebar_item = Some(item);
-//         cx.notify();
-//     }
-
-//     pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
-//         self.titlebar_item.clone()
-//     }
-
-//     /// Call the given callback with a workspace whose project is local.
-//     ///
-//     /// If the given workspace has a local project, then it will be passed
-//     /// to the callback. Otherwise, a new empty window will be created.
-//     pub fn with_local_workspace<T, F>(
-//         &mut self,
-//         cx: &mut ViewContext<Self>,
-//         callback: F,
-//     ) -> Task<Result<T>>
-//     where
-//         T: 'static,
-//         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
-//     {
-//         if self.project.read(cx).is_local() {
-//             Task::Ready(Some(Ok(callback(self, cx))))
-//         } else {
-//             let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
-//             cx.spawn(|_vh, mut cx| async move {
-//                 let (workspace, _) = task.await;
-//                 workspace.update(&mut cx, callback)
-//             })
-//         }
-//     }
-
-//     pub fn worktrees<'a>(
-//         &self,
-//         cx: &'a AppContext,
-//     ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
-//         self.project.read(cx).worktrees(cx)
-//     }
-
-//     pub fn visible_worktrees<'a>(
-//         &self,
-//         cx: &'a AppContext,
-//     ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
-//         self.project.read(cx).visible_worktrees(cx)
-//     }
-
-//     pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
-//         let futures = self
-//             .worktrees(cx)
-//             .filter_map(|worktree| worktree.read(cx).as_local())
-//             .map(|worktree| worktree.scan_complete())
-//             .collect::<Vec<_>>();
-//         async move {
-//             for future in futures {
-//                 future.await;
-//             }
-//         }
-//     }
-
-//     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
-//         cx.spawn(|mut cx| async move {
-//             let window = cx
-//                 .windows()
-//                 .into_iter()
-//                 .find(|window| window.is_active(&cx).unwrap_or(false));
-//             if let Some(window) = window {
-//                 //This can only get called when the window's project connection has been lost
-//                 //so we don't need to prompt the user for anything and instead just close the window
-//                 window.remove(&mut cx);
-//             }
-//         })
-//         .detach();
-//     }
-
-//     pub fn close(
-//         &mut self,
-//         _: &CloseWindow,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let window = cx.window();
-//         let prepare = self.prepare_to_close(false, cx);
-//         Some(cx.spawn(|_, mut cx| async move {
-//             if prepare.await? {
-//                 window.remove(&mut cx);
-//             }
-//             Ok(())
-//         }))
-//     }
-
-//     pub fn prepare_to_close(
-//         &mut self,
-//         quitting: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<bool>> {
-//         let active_call = self.active_call().cloned();
-//         let window = cx.window();
-
-//         cx.spawn(|this, mut cx| async move {
-//             let workspace_count = cx
-//                 .windows()
-//                 .into_iter()
-//                 .filter(|window| window.root_is::<Workspace>())
-//                 .count();
-
-//             if let Some(active_call) = active_call {
-//                 if !quitting
-//                     && workspace_count == 1
-//                     && active_call.read_with(&cx, |call, _| call.room().is_some())
-//                 {
-//                     let answer = window.prompt(
-//                         PromptLevel::Warning,
-//                         "Do you want to leave the current call?",
-//                         &["Close window and hang up", "Cancel"],
-//                         &mut cx,
-//                     );
-
-//                     if let Some(mut answer) = answer {
-//                         if answer.next().await == Some(1) {
-//                             return anyhow::Ok(false);
-//                         } else {
-//                             active_call
-//                                 .update(&mut cx, |call, cx| call.hang_up(cx))
-//                                 .await
-//                                 .log_err();
-//                         }
-//                     }
-//                 }
-//             }
-
-//             Ok(this
-//                 .update(&mut cx, |this, cx| {
-//                     this.save_all_internal(SaveIntent::Close, cx)
-//                 })?
-//                 .await?)
-//         })
-//     }
-
-//     fn save_all(
-//         &mut self,
-//         action: &SaveAll,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let save_all =
-//             self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
-//         Some(cx.foreground().spawn(async move {
-//             save_all.await?;
-//             Ok(())
-//         }))
-//     }
-
-//     fn save_all_internal(
-//         &mut self,
-//         mut save_intent: SaveIntent,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<bool>> {
-//         if self.project.read(cx).is_read_only() {
-//             return Task::ready(Ok(true));
-//         }
-//         let dirty_items = self
-//             .panes
-//             .iter()
-//             .flat_map(|pane| {
-//                 pane.read(cx).items().filter_map(|item| {
-//                     if item.is_dirty(cx) {
-//                         Some((pane.downgrade(), item.boxed_clone()))
-//                     } else {
-//                         None
-//                     }
-//                 })
-//             })
-//             .collect::<Vec<_>>();
-
-//         let project = self.project.clone();
-//         cx.spawn(|workspace, mut cx| async move {
-//             // Override save mode and display "Save all files" prompt
-//             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-//                 let mut answer = workspace.update(&mut cx, |_, cx| {
-//                     let prompt = Pane::file_names_for_prompt(
-//                         &mut dirty_items.iter().map(|(_, handle)| handle),
-//                         dirty_items.len(),
-//                         cx,
-//                     );
-//                     cx.prompt(
-//                         PromptLevel::Warning,
-//                         &prompt,
-//                         &["Save all", "Discard all", "Cancel"],
-//                     )
-//                 })?;
-//                 match answer.next().await {
-//                     Some(0) => save_intent = SaveIntent::SaveAll,
-//                     Some(1) => save_intent = SaveIntent::Skip,
-//                     _ => {}
-//                 }
-//             }
-//             for (pane, item) in dirty_items {
-//                 let (singleton, project_entry_ids) =
-//                     cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
-//                 if singleton || !project_entry_ids.is_empty() {
-//                     if let Some(ix) =
-//                         pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
-//                     {
-//                         if !Pane::save_item(
-//                             project.clone(),
-//                             &pane,
-//                             ix,
-//                             &*item,
-//                             save_intent,
-//                             &mut cx,
-//                         )
-//                         .await?
-//                         {
-//                             return Ok(false);
-//                         }
-//                     }
-//                 }
-//             }
-//             Ok(true)
-//         })
-//     }
-
-//     pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
-//         let mut paths = cx.prompt_for_paths(PathPromptOptions {
-//             files: true,
-//             directories: true,
-//             multiple: true,
-//         });
-
-//         Some(cx.spawn(|this, mut cx| async move {
-//             if let Some(paths) = paths.recv().await.flatten() {
-//                 if let Some(task) = this
-//                     .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
-//                     .log_err()
-//                 {
-//                     task.await?
-//                 }
-//             }
-//             Ok(())
-//         }))
-//     }
-
-//     pub fn open_workspace_for_paths(
-//         &mut self,
-//         paths: Vec<PathBuf>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<()>> {
-//         let window = cx.window().downcast::<Self>();
-//         let is_remote = self.project.read(cx).is_remote();
-//         let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
-//         let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
-//         let close_task = if is_remote || has_worktree || has_dirty_items {
-//             None
-//         } else {
-//             Some(self.prepare_to_close(false, cx))
-//         };
-//         let app_state = self.app_state.clone();
-
-//         cx.spawn(|_, mut cx| async move {
-//             let window_to_replace = if let Some(close_task) = close_task {
-//                 if !close_task.await? {
-//                     return Ok(());
-//                 }
-//                 window
-//             } else {
-//                 None
-//             };
-//             cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
-//                 .await?;
-//             Ok(())
-//         })
-//     }
-
-//     #[allow(clippy::type_complexity)]
-//     pub fn open_paths(
-//         &mut self,
-//         mut abs_paths: Vec<PathBuf>,
-//         visible: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
-//         log::info!("open paths {:?}", abs_paths);
-
-//         let fs = self.app_state.fs.clone();
-
-//         // Sort the paths to ensure we add worktrees for parents before their children.
-//         abs_paths.sort_unstable();
-//         cx.spawn(|this, mut cx| async move {
-//             let mut tasks = Vec::with_capacity(abs_paths.len());
-//             for abs_path in &abs_paths {
-//                 let project_path = match this
-//                     .update(&mut cx, |this, cx| {
-//                         Workspace::project_path_for_path(
-//                             this.project.clone(),
-//                             abs_path,
-//                             visible,
-//                             cx,
-//                         )
-//                     })
-//                     .log_err()
-//                 {
-//                     Some(project_path) => project_path.await.log_err(),
-//                     None => None,
-//                 };
-
-//                 let this = this.clone();
-//                 let task = cx.spawn(|mut cx| {
-//                     let fs = fs.clone();
-//                     let abs_path = abs_path.clone();
-//                     async move {
-//                         let (worktree, project_path) = project_path?;
-//                         if fs.is_file(&abs_path).await {
-//                             Some(
-//                                 this.update(&mut cx, |this, cx| {
-//                                     this.open_path(project_path, None, true, cx)
-//                                 })
-//                                 .log_err()?
-//                                 .await,
-//                             )
-//                         } else {
-//                             this.update(&mut cx, |workspace, cx| {
-//                                 let worktree = worktree.read(cx);
-//                                 let worktree_abs_path = worktree.abs_path();
-//                                 let entry_id = if abs_path == worktree_abs_path.as_ref() {
-//                                     worktree.root_entry()
-//                                 } else {
-//                                     abs_path
-//                                         .strip_prefix(worktree_abs_path.as_ref())
-//                                         .ok()
-//                                         .and_then(|relative_path| {
-//                                             worktree.entry_for_path(relative_path)
-//                                         })
-//                                 }
-//                                 .map(|entry| entry.id);
-//                                 if let Some(entry_id) = entry_id {
-//                                     workspace.project().update(cx, |_, cx| {
-//                                         cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
-//                                     })
-//                                 }
-//                             })
-//                             .log_err()?;
-//                             None
-//                         }
-//                     }
-//                 });
-//                 tasks.push(task);
-//             }
-
-//             futures::future::join_all(tasks).await
-//         })
-//     }
-
-//     fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
-//         let mut paths = cx.prompt_for_paths(PathPromptOptions {
-//             files: false,
-//             directories: true,
-//             multiple: true,
-//         });
-//         cx.spawn(|this, mut cx| async move {
-//             if let Some(paths) = paths.recv().await.flatten() {
-//                 let results = this
-//                     .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
-//                     .await;
-//                 for result in results.into_iter().flatten() {
-//                     result.log_err();
-//                 }
-//             }
-//             anyhow::Ok(())
-//         })
-//         .detach_and_log_err(cx);
-//     }
-
-//     fn project_path_for_path(
-//         project: ModelHandle<Project>,
-//         abs_path: &Path,
-//         visible: bool,
-//         cx: &mut AppContext,
-//     ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
-//         let entry = project.update(cx, |project, cx| {
-//             project.find_or_create_local_worktree(abs_path, visible, cx)
-//         });
-//         cx.spawn(|cx| async move {
-//             let (worktree, path) = entry.await?;
-//             let worktree_id = worktree.read_with(&cx, |t, _| t.id());
-//             Ok((
-//                 worktree,
-//                 ProjectPath {
-//                     worktree_id,
-//                     path: path.into(),
-//                 },
-//             ))
-//         })
-//     }
-
-//     /// Returns the modal that was toggled closed if it was open.
-//     pub fn toggle_modal<V, F>(
-//         &mut self,
-//         cx: &mut ViewContext<Self>,
-//         add_view: F,
-//     ) -> Option<ViewHandle<V>>
-//     where
-//         V: 'static + Modal,
-//         F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
-//     {
-//         cx.notify();
-//         // Whatever modal was visible is getting clobbered. If its the same type as V, then return
-//         // it. Otherwise, create a new modal and set it as active.
-//         if let Some(already_open_modal) = self
-//             .dismiss_modal(cx)
-//             .and_then(|modal| modal.downcast::<V>())
-//         {
-//             cx.focus_self();
-//             Some(already_open_modal)
-//         } else {
-//             let modal = add_view(self, cx);
-//             cx.subscribe(&modal, |this, _, event, cx| {
-//                 if V::dismiss_on_event(event) {
-//                     this.dismiss_modal(cx);
-//                 }
-//             })
-//             .detach();
-//             let previously_focused_view_id = cx.focused_view_id();
-//             cx.focus(&modal);
-//             self.modal = Some(ActiveModal {
-//                 view: Box::new(modal),
-//                 previously_focused_view_id,
-//             });
-//             None
-//         }
-//     }
-
-//     pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
-//         self.modal
-//             .as_ref()
-//             .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
-//     }
-
-//     pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
-//         if let Some(modal) = self.modal.take() {
-//             if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
-//                 if modal.view.has_focus(cx) {
-//                     cx.window_context().focus(Some(previously_focused_view_id));
-//                 }
-//             }
-//             cx.notify();
-//             Some(modal.view.as_any().clone())
-//         } else {
-//             None
-//         }
-//     }
-
-//     pub fn items<'a>(
-//         &'a self,
-//         cx: &'a AppContext,
-//     ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
-//         self.panes.iter().flat_map(|pane| pane.read(cx).items())
-//     }
-
-//     pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
-//         self.items_of_type(cx).max_by_key(|item| item.id())
-//     }
-
-//     pub fn items_of_type<'a, T: Item>(
-//         &'a self,
-//         cx: &'a AppContext,
-//     ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
-//         self.panes
-//             .iter()
-//             .flat_map(|pane| pane.read(cx).items_of_type())
-//     }
-
-//     pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-//         self.active_pane().read(cx).active_item()
-//     }
-
-//     fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
-//         self.active_item(cx).and_then(|item| item.project_path(cx))
-//     }
-
-//     pub fn save_active_item(
-//         &mut self,
-//         save_intent: SaveIntent,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<()>> {
-//         let project = self.project.clone();
-//         let pane = self.active_pane();
-//         let item_ix = pane.read(cx).active_item_index();
-//         let item = pane.read(cx).active_item();
-//         let pane = pane.downgrade();
-
-//         cx.spawn(|_, mut cx| async move {
-//             if let Some(item) = item {
-//                 Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
-//                     .await
-//                     .map(|_| ())
-//             } else {
-//                 Ok(())
-//             }
-//         })
-//     }
-
-//     pub fn close_inactive_items_and_panes(
-//         &mut self,
-//         _: &CloseInactiveTabsAndPanes,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         self.close_all_internal(true, SaveIntent::Close, cx)
-//     }
-
-//     pub fn close_all_items_and_panes(
-//         &mut self,
-//         action: &CloseAllItemsAndPanes,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
-//     }
-
-//     fn close_all_internal(
-//         &mut self,
-//         retain_active_pane: bool,
-//         save_intent: SaveIntent,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let current_pane = self.active_pane();
-
-//         let mut tasks = Vec::new();
-
-//         if retain_active_pane {
-//             if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
-//                 pane.close_inactive_items(&CloseInactiveItems, cx)
-//             }) {
-//                 tasks.push(current_pane_close);
-//             };
-//         }
-
-//         for pane in self.panes() {
-//             if retain_active_pane && pane.id() == current_pane.id() {
-//                 continue;
-//             }
-
-//             if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
-//                 pane.close_all_items(
-//                     &CloseAllItems {
-//                         save_intent: Some(save_intent),
-//                     },
-//                     cx,
-//                 )
-//             }) {
-//                 tasks.push(close_pane_items)
-//             }
-//         }
-
-//         if tasks.is_empty() {
-//             None
-//         } else {
-//             Some(cx.spawn(|_, _| async move {
-//                 for task in tasks {
-//                     task.await?
-//                 }
-//                 Ok(())
-//             }))
-//         }
-//     }
-
-//     pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
-//         let dock = match dock_side {
-//             DockPosition::Left => &self.left_dock,
-//             DockPosition::Bottom => &self.bottom_dock,
-//             DockPosition::Right => &self.right_dock,
-//         };
-//         let mut focus_center = false;
-//         let mut reveal_dock = false;
-//         dock.update(cx, |dock, cx| {
-//             let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
-//             let was_visible = dock.is_open() && !other_is_zoomed;
-//             dock.set_open(!was_visible, cx);
-
-//             if let Some(active_panel) = dock.active_panel() {
-//                 if was_visible {
-//                     if active_panel.has_focus(cx) {
-//                         focus_center = true;
-//                     }
-//                 } else {
-//                     cx.focus(active_panel.as_any());
-//                     reveal_dock = true;
-//                 }
-//             }
-//         });
-
-//         if reveal_dock {
-//             self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
-//         }
-
-//         if focus_center {
-//             cx.focus_self();
-//         }
-
-//         cx.notify();
-//         self.serialize_workspace(cx);
-//     }
-
-//     pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
-//         let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
-
-//         for dock in docks {
-//             dock.update(cx, |dock, cx| {
-//                 dock.set_open(false, cx);
-//             });
-//         }
-
-//         cx.focus_self();
-//         cx.notify();
-//         self.serialize_workspace(cx);
-//     }
-
-//     /// Transfer focus to the panel of the given type.
-//     pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
-//         self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
-//             .as_any()
-//             .clone()
-//             .downcast()
-//     }
-
-//     /// Focus the panel of the given type if it isn't already focused. If it is
-//     /// already focused, then transfer focus back to the workspace center.
-//     pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
-//         self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
-//     }
-
-//     /// Focus or unfocus the given panel type, depending on the given callback.
-//     fn focus_or_unfocus_panel<T: Panel>(
-//         &mut self,
-//         cx: &mut ViewContext<Self>,
-//         should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
-//     ) -> Option<Rc<dyn PanelHandle>> {
-//         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
-//             if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
-//                 let mut focus_center = false;
-//                 let mut reveal_dock = false;
-//                 let panel = dock.update(cx, |dock, cx| {
-//                     dock.activate_panel(panel_index, cx);
-
-//                     let panel = dock.active_panel().cloned();
-//                     if let Some(panel) = panel.as_ref() {
-//                         if should_focus(&**panel, cx) {
-//                             dock.set_open(true, cx);
-//                             cx.focus(panel.as_any());
-//                             reveal_dock = true;
-//                         } else {
-//                             // if panel.is_zoomed(cx) {
-//                             //     dock.set_open(false, cx);
-//                             // }
-//                             focus_center = true;
-//                         }
-//                     }
-//                     panel
-//                 });
-
-//                 if focus_center {
-//                     cx.focus_self();
-//                 }
-
-//                 self.serialize_workspace(cx);
-//                 cx.notify();
-//                 return panel;
-//             }
-//         }
-//         None
-//     }
-
-//     pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
-//         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
-//             let dock = dock.read(cx);
-//             if let Some(panel) = dock.panel::<T>() {
-//                 return Some(panel);
-//             }
-//         }
-//         None
-//     }
-
-//     fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
-//         for pane in &self.panes {
-//             pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-//         }
-
-//         self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-//         self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-//         self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-//         self.zoomed = None;
-//         self.zoomed_position = None;
-
-//         cx.notify();
-//     }
-
-//     #[cfg(any(test, feature = "test-support"))]
-//     pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
-//         self.zoomed.and_then(|view| view.upgrade(cx))
-//     }
-
-//     fn dismiss_zoomed_items_to_reveal(
-//         &mut self,
-//         dock_to_reveal: Option<DockPosition>,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         // If a center pane is zoomed, unzoom it.
-//         for pane in &self.panes {
-//             if pane != &self.active_pane || dock_to_reveal.is_some() {
-//                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-//             }
-//         }
-
-//         // If another dock is zoomed, hide it.
-//         let mut focus_center = false;
-//         for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
-//             dock.update(cx, |dock, cx| {
-//                 if Some(dock.position()) != dock_to_reveal {
-//                     if let Some(panel) = dock.active_panel() {
-//                         if panel.is_zoomed(cx) {
-//                             focus_center |= panel.has_focus(cx);
-//                             dock.set_open(false, cx);
-//                         }
-//                     }
-//                 }
-//             });
-//         }
-
-//         if focus_center {
-//             cx.focus_self();
-//         }
-
-//         if self.zoomed_position != dock_to_reveal {
-//             self.zoomed = None;
-//             self.zoomed_position = None;
-//         }
-
-//         cx.notify();
-//     }
-
-//     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
-//         let pane = cx.add_view(|cx| {
-//             Pane::new(
-//                 self.weak_handle(),
-//                 self.project.clone(),
-//                 self.pane_history_timestamp.clone(),
-//                 cx,
-//             )
-//         });
-//         cx.subscribe(&pane, Self::handle_pane_event).detach();
-//         self.panes.push(pane.clone());
-//         cx.focus(&pane);
-//         cx.emit(Event::PaneAdded(pane.clone()));
-//         pane
-//     }
-
-//     pub fn add_item_to_center(
-//         &mut self,
-//         item: Box<dyn ItemHandle>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> bool {
-//         if let Some(center_pane) = self.last_active_center_pane.clone() {
-//             if let Some(center_pane) = center_pane.upgrade(cx) {
-//                 center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
-//                 true
-//             } else {
-//                 false
-//             }
-//         } else {
-//             false
-//         }
-//     }
-
-//     pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-//         self.active_pane
-//             .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
-//     }
-
-//     pub fn split_item(
-//         &mut self,
-//         split_direction: SplitDirection,
-//         item: Box<dyn ItemHandle>,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
-//         new_pane.update(cx, move |new_pane, cx| {
-//             new_pane.add_item(item, true, true, None, cx)
-//         })
-//     }
-
-//     pub fn open_abs_path(
-//         &mut self,
-//         abs_path: PathBuf,
-//         visible: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
-//         cx.spawn(|workspace, mut cx| async move {
-//             let open_paths_task_result = workspace
-//                 .update(&mut cx, |workspace, cx| {
-//                     workspace.open_paths(vec![abs_path.clone()], visible, cx)
-//                 })
-//                 .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
-//                 .await;
-//             anyhow::ensure!(
-//                 open_paths_task_result.len() == 1,
-//                 "open abs path {abs_path:?} task returned incorrect number of results"
-//             );
-//             match open_paths_task_result
-//                 .into_iter()
-//                 .next()
-//                 .expect("ensured single task result")
-//             {
-//                 Some(open_result) => {
-//                     open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
-//                 }
-//                 None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
-//             }
-//         })
-//     }
-
-//     pub fn split_abs_path(
-//         &mut self,
-//         abs_path: PathBuf,
-//         visible: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
-//         let project_path_task =
-//             Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
-//         cx.spawn(|this, mut cx| async move {
-//             let (_, path) = project_path_task.await?;
-//             this.update(&mut cx, |this, cx| this.split_path(path, cx))?
-//                 .await
-//         })
-//     }
-
-//     pub fn open_path(
-//         &mut self,
-//         path: impl Into<ProjectPath>,
-//         pane: Option<WeakViewHandle<Pane>>,
-//         focus_item: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
-//         let pane = pane.unwrap_or_else(|| {
-//             self.last_active_center_pane.clone().unwrap_or_else(|| {
-//                 self.panes
-//                     .first()
-//                     .expect("There must be an active pane")
-//                     .downgrade()
-//             })
-//         });
-
-//         let task = self.load_path(path.into(), cx);
-//         cx.spawn(|_, mut cx| async move {
-//             let (project_entry_id, build_item) = task.await?;
-//             pane.update(&mut cx, |pane, cx| {
-//                 pane.open_item(project_entry_id, focus_item, cx, build_item)
-//             })
-//         })
-//     }
-
-//     pub fn split_path(
-//         &mut self,
-//         path: impl Into<ProjectPath>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
-//         let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
-//             self.panes
-//                 .first()
-//                 .expect("There must be an active pane")
-//                 .downgrade()
-//         });
-
-//         if let Member::Pane(center_pane) = &self.center.root {
-//             if center_pane.read(cx).items_len() == 0 {
-//                 return self.open_path(path, Some(pane), true, cx);
-//             }
-//         }
-
-//         let task = self.load_path(path.into(), cx);
-//         cx.spawn(|this, mut cx| async move {
-//             let (project_entry_id, build_item) = task.await?;
-//             this.update(&mut cx, move |this, cx| -> Option<_> {
-//                 let pane = pane.upgrade(cx)?;
-//                 let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
-//                 new_pane.update(cx, |new_pane, cx| {
-//                     Some(new_pane.open_item(project_entry_id, true, cx, build_item))
-//                 })
-//             })
-//             .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
-//         })
-//     }
-
-//     pub(crate) fn load_path(
-//         &mut self,
-//         path: ProjectPath,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Task<
-//         Result<(
-//             ProjectEntryId,
-//             impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
-//         )>,
-//     > {
-//         let project = self.project().clone();
-//         let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
-//         cx.spawn(|_, mut cx| async move {
-//             let (project_entry_id, project_item) = project_item.await?;
-//             let build_item = cx.update(|cx| {
-//                 cx.default_global::<ProjectItemBuilders>()
-//                     .get(&project_item.model_type())
-//                     .ok_or_else(|| anyhow!("no item builder for project item"))
-//                     .cloned()
-//             })?;
-//             let build_item =
-//                 move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
-//             Ok((project_entry_id, build_item))
-//         })
-//     }
-
-//     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 _;
-
-//         let entry_id = project_item.read(cx).entry_id(cx);
-//         if let Some(item) = entry_id
-//             .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
-//             .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 split_project_item<T>(
-//         &mut self,
-//         project_item: ModelHandle<T::Item>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> ViewHandle<T>
-//     where
-//         T: ProjectItem,
-//     {
-//         use project::Item as _;
-
-//         let entry_id = project_item.read(cx).entry_id(cx);
-//         if let Some(item) = entry_id
-//             .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
-//             .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.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
-//         item
-//     }
-
-//     pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-//         if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
-//             self.active_pane.update(cx, |pane, cx| {
-//                 pane.add_item(Box::new(shared_screen), false, true, None, cx)
-//             });
-//         }
-//     }
-
-//     pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
-//         let result = self.panes.iter().find_map(|pane| {
-//             pane.read(cx)
-//                 .index_for_item(item)
-//                 .map(|ix| (pane.clone(), ix))
-//         });
-//         if let Some((pane, ix)) = result {
-//             pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
-//             true
-//         } else {
-//             false
-//         }
-//     }
-
-//     fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
-//         let panes = self.center.panes();
-//         if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
-//             cx.focus(&pane);
-//         } else {
-//             self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
-//         }
-//     }
-
-//     pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
-//         let panes = self.center.panes();
-//         if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
-//             let next_ix = (ix + 1) % panes.len();
-//             let next_pane = panes[next_ix].clone();
-//             cx.focus(&next_pane);
-//         }
-//     }
-
-//     pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
-//         let panes = self.center.panes();
-//         if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
-//             let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
-//             let prev_pane = panes[prev_ix].clone();
-//             cx.focus(&prev_pane);
-//         }
-//     }
-
-//     pub fn activate_pane_in_direction(
-//         &mut self,
-//         direction: SplitDirection,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         if let Some(pane) = self.find_pane_in_direction(direction, cx) {
-//             cx.focus(pane);
-//         }
-//     }
-
-//     pub fn swap_pane_in_direction(
-//         &mut self,
-//         direction: SplitDirection,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         if let Some(to) = self
-//             .find_pane_in_direction(direction, cx)
-//             .map(|pane| pane.clone())
-//         {
-//             self.center.swap(&self.active_pane.clone(), &to);
-//             cx.notify();
-//         }
-//     }
-
-//     fn find_pane_in_direction(
-//         &mut self,
-//         direction: SplitDirection,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<&ViewHandle<Pane>> {
-//         let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
-//             return None;
-//         };
-//         let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
-//         let center = match cursor {
-//             Some(cursor) if bounding_box.contains_point(cursor) => cursor,
-//             _ => bounding_box.center(),
-//         };
-
-//         let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
-
-//         let target = match direction {
-//             SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
-//             SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
-//             SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
-//             SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
-//         };
-//         self.center.pane_at_pixel_position(target)
-//     }
-
-//     fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
-//         if self.active_pane != pane {
-//             self.active_pane = pane.clone();
-//             self.status_bar.update(cx, |status_bar, cx| {
-//                 status_bar.set_active_pane(&self.active_pane, cx);
-//             });
-//             self.active_item_path_changed(cx);
-//             self.last_active_center_pane = Some(pane.downgrade());
-//         }
-
-//         self.dismiss_zoomed_items_to_reveal(None, cx);
-//         if pane.read(cx).is_zoomed() {
-//             self.zoomed = Some(pane.downgrade().into_any());
-//         } else {
-//             self.zoomed = None;
-//         }
-//         self.zoomed_position = None;
-//         self.update_active_view_for_followers(cx);
-
-//         cx.notify();
-//     }
-
-//     fn handle_pane_event(
-//         &mut self,
-//         pane: ViewHandle<Pane>,
-//         event: &pane::Event,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         match event {
-//             pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
-//             pane::Event::Split(direction) => {
-//                 self.split_and_clone(pane, *direction, cx);
-//             }
-//             pane::Event::Remove => self.remove_pane(pane, cx),
-//             pane::Event::ActivateItem { local } => {
-//                 if *local {
-//                     self.unfollow(&pane, cx);
-//                 }
-//                 if &pane == self.active_pane() {
-//                     self.active_item_path_changed(cx);
-//                 }
-//             }
-//             pane::Event::ChangeItemTitle => {
-//                 if pane == self.active_pane {
-//                     self.active_item_path_changed(cx);
-//                 }
-//                 self.update_window_edited(cx);
-//             }
-//             pane::Event::RemoveItem { item_id } => {
-//                 self.update_window_edited(cx);
-//                 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
-//                     if entry.get().id() == pane.id() {
-//                         entry.remove();
-//                     }
-//                 }
-//             }
-//             pane::Event::Focus => {
-//                 self.handle_pane_focused(pane.clone(), cx);
-//             }
-//             pane::Event::ZoomIn => {
-//                 if pane == self.active_pane {
-//                     pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
-//                     if pane.read(cx).has_focus() {
-//                         self.zoomed = Some(pane.downgrade().into_any());
-//                         self.zoomed_position = None;
-//                     }
-//                     cx.notify();
-//                 }
-//             }
-//             pane::Event::ZoomOut => {
-//                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-//                 if self.zoomed_position.is_none() {
-//                     self.zoomed = None;
-//                 }
-//                 cx.notify();
-//             }
-//         }
-
-//         self.serialize_workspace(cx);
-//     }
-
-//     pub fn split_pane(
-//         &mut self,
-//         pane_to_split: ViewHandle<Pane>,
-//         split_direction: SplitDirection,
-//         cx: &mut ViewContext<Self>,
-//     ) -> ViewHandle<Pane> {
-//         let new_pane = self.add_pane(cx);
-//         self.center
-//             .split(&pane_to_split, &new_pane, split_direction)
-//             .unwrap();
-//         cx.notify();
-//         new_pane
-//     }
-
-//     pub fn split_and_clone(
-//         &mut self,
-//         pane: ViewHandle<Pane>,
-//         direction: SplitDirection,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<ViewHandle<Pane>> {
-//         let item = pane.read(cx).active_item()?;
-//         let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
-//             let new_pane = self.add_pane(cx);
-//             new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
-//             self.center.split(&pane, &new_pane, direction).unwrap();
-//             Some(new_pane)
-//         } else {
-//             None
-//         };
-//         cx.notify();
-//         maybe_pane_handle
-//     }
-
-//     pub fn split_pane_with_item(
-//         &mut self,
-//         pane_to_split: WeakViewHandle<Pane>,
-//         split_direction: SplitDirection,
-//         from: WeakViewHandle<Pane>,
-//         item_id_to_move: usize,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
-//             return;
-//         };
-//         let Some(from) = from.upgrade(cx) else {
-//             return;
-//         };
-
-//         let new_pane = self.add_pane(cx);
-//         self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
-//         self.center
-//             .split(&pane_to_split, &new_pane, split_direction)
-//             .unwrap();
-//         cx.notify();
-//     }
-
-//     pub fn split_pane_with_project_entry(
-//         &mut self,
-//         pane_to_split: WeakViewHandle<Pane>,
-//         split_direction: SplitDirection,
-//         project_entry: ProjectEntryId,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let pane_to_split = pane_to_split.upgrade(cx)?;
-//         let new_pane = self.add_pane(cx);
-//         self.center
-//             .split(&pane_to_split, &new_pane, split_direction)
-//             .unwrap();
-
-//         let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
-//         let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
-//         Some(cx.foreground().spawn(async move {
-//             task.await?;
-//             Ok(())
-//         }))
-//     }
-
-//     pub fn move_item(
-//         &mut self,
-//         source: ViewHandle<Pane>,
-//         destination: ViewHandle<Pane>,
-//         item_id_to_move: usize,
-//         destination_index: usize,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         let item_to_move = source
-//             .read(cx)
-//             .items()
-//             .enumerate()
-//             .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
-
-//         if item_to_move.is_none() {
-//             log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
-//             return;
-//         }
-//         let (item_ix, item_handle) = item_to_move.unwrap();
-//         let item_handle = item_handle.clone();
-
-//         if source != destination {
-//             // Close item from previous pane
-//             source.update(cx, |source, cx| {
-//                 source.remove_item(item_ix, false, cx);
-//             });
-//         }
-
-//         // This automatically removes duplicate items in the pane
-//         destination.update(cx, |destination, cx| {
-//             destination.add_item(item_handle, true, true, Some(destination_index), cx);
-//             cx.focus_self();
-//         });
-//     }
-
-//     fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
-//         if self.center.remove(&pane).unwrap() {
-//             self.force_remove_pane(&pane, cx);
-//             self.unfollow(&pane, cx);
-//             self.last_leaders_by_pane.remove(&pane.downgrade());
-//             for removed_item in pane.read(cx).items() {
-//                 self.panes_by_item.remove(&removed_item.id());
-//             }
-
-//             cx.notify();
-//         } else {
-//             self.active_item_path_changed(cx);
-//         }
-//     }
-
-//     pub fn panes(&self) -> &[ViewHandle<Pane>] {
-//         &self.panes
-//     }
-
-//     pub fn active_pane(&self) -> &ViewHandle<Pane> {
-//         &self.active_pane
-//     }
-
-//     fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-//         self.follower_states.retain(|_, state| {
-//             if state.leader_id == peer_id {
-//                 for item in state.items_by_leader_view_id.values() {
-//                     item.set_leader_peer_id(None, cx);
-//                 }
-//                 false
-//             } else {
-//                 true
-//             }
-//         });
-//         cx.notify();
-//     }
-
-//     fn start_following(
-//         &mut self,
-//         leader_id: PeerId,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let pane = self.active_pane().clone();
-
-//         self.last_leaders_by_pane
-//             .insert(pane.downgrade(), leader_id);
-//         self.unfollow(&pane, cx);
-//         self.follower_states.insert(
-//             pane.clone(),
-//             FollowerState {
-//                 leader_id,
-//                 active_view_id: None,
-//                 items_by_leader_view_id: Default::default(),
-//             },
-//         );
-//         cx.notify();
-
-//         let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-//         let project_id = self.project.read(cx).remote_id();
-//         let request = self.app_state.client.request(proto::Follow {
-//             room_id,
-//             project_id,
-//             leader_id: Some(leader_id),
-//         });
-
-//         Some(cx.spawn(|this, mut cx| async move {
-//             let response = request.await?;
-//             this.update(&mut cx, |this, _| {
-//                 let state = this
-//                     .follower_states
-//                     .get_mut(&pane)
-//                     .ok_or_else(|| anyhow!("following interrupted"))?;
-//                 state.active_view_id = if let Some(active_view_id) = response.active_view_id {
-//                     Some(ViewId::from_proto(active_view_id)?)
-//                 } else {
-//                     None
-//                 };
-//                 Ok::<_, anyhow::Error>(())
-//             })??;
-//             Self::add_views_from_leader(
-//                 this.clone(),
-//                 leader_id,
-//                 vec![pane],
-//                 response.views,
-//                 &mut cx,
-//             )
-//             .await?;
-//             this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
-//             Ok(())
-//         }))
-//     }
-
-//     pub fn follow_next_collaborator(
-//         &mut self,
-//         _: &FollowNextCollaborator,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let collaborators = self.project.read(cx).collaborators();
-//         let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
-//             let mut collaborators = collaborators.keys().copied();
-//             for peer_id in collaborators.by_ref() {
-//                 if peer_id == leader_id {
-//                     break;
-//                 }
-//             }
-//             collaborators.next()
-//         } else if let Some(last_leader_id) =
-//             self.last_leaders_by_pane.get(&self.active_pane.downgrade())
-//         {
-//             if collaborators.contains_key(last_leader_id) {
-//                 Some(*last_leader_id)
-//             } else {
-//                 None
-//             }
-//         } else {
-//             None
-//         };
-
-//         let pane = self.active_pane.clone();
-//         let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
-//         else {
-//             return None;
-//         };
-//         if Some(leader_id) == self.unfollow(&pane, cx) {
-//             return None;
-//         }
-//         self.follow(leader_id, cx)
-//     }
-
-//     pub fn follow(
-//         &mut self,
-//         leader_id: PeerId,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<Task<Result<()>>> {
-//         let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
-//         let project = self.project.read(cx);
-
-//         let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
-//             return None;
-//         };
-
-//         let other_project_id = match remote_participant.location {
-//             call::ParticipantLocation::External => None,
-//             call::ParticipantLocation::UnsharedProject => None,
-//             call::ParticipantLocation::SharedProject { project_id } => {
-//                 if Some(project_id) == project.remote_id() {
-//                     None
-//                 } else {
-//                     Some(project_id)
-//                 }
-//             }
-//         };
-
-//         // if they are active in another project, follow there.
-//         if let Some(project_id) = other_project_id {
-//             let app_state = self.app_state.clone();
-//             return Some(crate::join_remote_project(
-//                 project_id,
-//                 remote_participant.user.id,
-//                 app_state,
-//                 cx,
-//             ));
-//         }
-
-//         // if you're already following, find the right pane and focus it.
-//         for (pane, state) in &self.follower_states {
-//             if leader_id == state.leader_id {
-//                 cx.focus(pane);
-//                 return None;
-//             }
-//         }
-
-//         // Otherwise, follow.
-//         self.start_following(leader_id, cx)
-//     }
-
-//     pub fn unfollow(
-//         &mut self,
-//         pane: &ViewHandle<Pane>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<PeerId> {
-//         let state = self.follower_states.remove(pane)?;
-//         let leader_id = state.leader_id;
-//         for (_, item) in state.items_by_leader_view_id {
-//             item.set_leader_peer_id(None, cx);
-//         }
-
-//         if self
-//             .follower_states
-//             .values()
-//             .all(|state| state.leader_id != state.leader_id)
-//         {
-//             let project_id = self.project.read(cx).remote_id();
-//             let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-//             self.app_state
-//                 .client
-//                 .send(proto::Unfollow {
-//                     room_id,
-//                     project_id,
-//                     leader_id: Some(leader_id),
-//                 })
-//                 .log_err();
-//         }
-
-//         cx.notify();
-//         Some(leader_id)
-//     }
-
-//     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
-//         self.follower_states
-//             .values()
-//             .any(|state| state.leader_id == peer_id)
-//     }
-
-//     fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         // TODO: There should be a better system in place for this
-//         // (https://github.com/zed-industries/zed/issues/1290)
-//         let is_fullscreen = cx.window_is_fullscreen();
-//         let container_theme = if is_fullscreen {
-//             let mut container_theme = theme.titlebar.container;
-//             container_theme.padding.left = container_theme.padding.right;
-//             container_theme
-//         } else {
-//             theme.titlebar.container
-//         };
-
-//         enum TitleBar {}
-//         MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
-//             Stack::new()
-//                 .with_children(
-//                     self.titlebar_item
-//                         .as_ref()
-//                         .map(|item| ChildView::new(item, cx)),
-//                 )
-//                 .contained()
-//                 .with_style(container_theme)
-//         })
-//         .on_click(MouseButton::Left, |event, _, cx| {
-//             if event.click_count == 2 {
-//                 cx.zoom_window();
-//             }
-//         })
-//         .constrained()
-//         .with_height(theme.titlebar.height)
-//         .into_any_named("titlebar")
-//     }
-
-//     fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
-//         let active_entry = self.active_project_path(cx);
-//         self.project
-//             .update(cx, |project, cx| project.set_active_path(active_entry, cx));
-//         self.update_window_title(cx);
-//     }
-
-//     fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
-//         let project = self.project().read(cx);
-//         let mut title = String::new();
-
-//         if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
-//             let filename = path
-//                 .path
-//                 .file_name()
-//                 .map(|s| s.to_string_lossy())
-//                 .or_else(|| {
-//                     Some(Cow::Borrowed(
-//                         project
-//                             .worktree_for_id(path.worktree_id, cx)?
-//                             .read(cx)
-//                             .root_name(),
-//                     ))
-//                 });
-
-//             if let Some(filename) = filename {
-//                 title.push_str(filename.as_ref());
-//                 title.push_str(" — ");
-//             }
-//         }
-
-//         for (i, name) in project.worktree_root_names(cx).enumerate() {
-//             if i > 0 {
-//                 title.push_str(", ");
-//             }
-//             title.push_str(name);
-//         }
-
-//         if title.is_empty() {
-//             title = "empty project".to_string();
-//         }
-
-//         if project.is_remote() {
-//             title.push_str(" ↙");
-//         } else if project.is_shared() {
-//             title.push_str(" ↗");
-//         }
-
-//         cx.set_window_title(&title);
-//     }
-
-//     fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
-//         let is_edited = !self.project.read(cx).is_read_only()
-//             && self
-//                 .items(cx)
-//                 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
-//         if is_edited != self.window_edited {
-//             self.window_edited = is_edited;
-//             cx.set_window_edited(self.window_edited)
-//         }
-//     }
-
-//     fn render_disconnected_overlay(
-//         &self,
-//         cx: &mut ViewContext<Workspace>,
-//     ) -> Option<AnyElement<Workspace>> {
-//         if self.project.read(cx).is_read_only() {
-//             enum DisconnectedOverlay {}
-//             Some(
-//                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
-//                     let theme = &theme::current(cx);
-//                     Label::new(
-//                         "Your connection to the remote project has been lost.",
-//                         theme.workspace.disconnected_overlay.text.clone(),
-//                     )
-//                     .aligned()
-//                     .contained()
-//                     .with_style(theme.workspace.disconnected_overlay.container)
-//                 })
-//                 .with_cursor_style(CursorStyle::Arrow)
-//                 .capture_all()
-//                 .into_any_named("disconnected overlay"),
-//             )
-//         } else {
-//             None
-//         }
-//     }
-
-//     fn render_notifications(
-//         &self,
-//         theme: &theme::Workspace,
-//         cx: &AppContext,
-//     ) -> Option<AnyElement<Workspace>> {
-//         if self.notifications.is_empty() {
-//             None
-//         } else {
-//             Some(
-//                 Flex::column()
-//                     .with_children(self.notifications.iter().map(|(_, _, notification)| {
-//                         ChildView::new(notification.as_any(), cx)
-//                             .contained()
-//                             .with_style(theme.notification)
-//                     }))
-//                     .constrained()
-//                     .with_width(theme.notifications.width)
-//                     .contained()
-//                     .with_style(theme.notifications.container)
-//                     .aligned()
-//                     .bottom()
-//                     .right()
-//                     .into_any(),
-//             )
-//         }
-//     }
-
-//     // RPC handlers
-
-//     fn handle_follow(
-//         &mut self,
-//         follower_project_id: Option<u64>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> proto::FollowResponse {
-//         let client = &self.app_state.client;
-//         let project_id = self.project.read(cx).remote_id();
-
-//         let active_view_id = self.active_item(cx).and_then(|i| {
-//             Some(
-//                 i.to_followable_item_handle(cx)?
-//                     .remote_id(client, cx)?
-//                     .to_proto(),
-//             )
-//         });
-
-//         cx.notify();
-
-//         self.last_active_view_id = active_view_id.clone();
-//         proto::FollowResponse {
-//             active_view_id,
-//             views: self
-//                 .panes()
-//                 .iter()
-//                 .flat_map(|pane| {
-//                     let leader_id = self.leader_for_pane(pane);
-//                     pane.read(cx).items().filter_map({
-//                         let cx = &cx;
-//                         move |item| {
-//                             let item = item.to_followable_item_handle(cx)?;
-//                             if (project_id.is_none() || project_id != follower_project_id)
-//                                 && item.is_project_item(cx)
-//                             {
-//                                 return None;
-//                             }
-//                             let id = item.remote_id(client, cx)?.to_proto();
-//                             let variant = item.to_state_proto(cx)?;
-//                             Some(proto::View {
-//                                 id: Some(id),
-//                                 leader_id,
-//                                 variant: Some(variant),
-//                             })
-//                         }
-//                     })
-//                 })
-//                 .collect(),
-//         }
-//     }
-
-//     fn handle_update_followers(
-//         &mut self,
-//         leader_id: PeerId,
-//         message: proto::UpdateFollowers,
-//         _cx: &mut ViewContext<Self>,
-//     ) {
-//         self.leader_updates_tx
-//             .unbounded_send((leader_id, message))
-//             .ok();
-//     }
-
-//     async fn process_leader_update(
-//         this: &WeakViewHandle<Self>,
-//         leader_id: PeerId,
-//         update: proto::UpdateFollowers,
-//         cx: &mut AsyncAppContext,
-//     ) -> Result<()> {
-//         match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
-//             proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
-//                 this.update(cx, |this, _| {
-//                     for (_, state) in &mut this.follower_states {
-//                         if state.leader_id == leader_id {
-//                             state.active_view_id =
-//                                 if let Some(active_view_id) = update_active_view.id.clone() {
-//                                     Some(ViewId::from_proto(active_view_id)?)
-//                                 } else {
-//                                     None
-//                                 };
-//                         }
-//                     }
-//                     anyhow::Ok(())
-//                 })??;
-//             }
-//             proto::update_followers::Variant::UpdateView(update_view) => {
-//                 let variant = update_view
-//                     .variant
-//                     .ok_or_else(|| anyhow!("missing update view variant"))?;
-//                 let id = update_view
-//                     .id
-//                     .ok_or_else(|| anyhow!("missing update view id"))?;
-//                 let mut tasks = Vec::new();
-//                 this.update(cx, |this, cx| {
-//                     let project = this.project.clone();
-//                     for (_, state) in &mut this.follower_states {
-//                         if state.leader_id == leader_id {
-//                             let view_id = ViewId::from_proto(id.clone())?;
-//                             if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
-//                                 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
-//                             }
-//                         }
-//                     }
-//                     anyhow::Ok(())
-//                 })??;
-//                 try_join_all(tasks).await.log_err();
-//             }
-//             proto::update_followers::Variant::CreateView(view) => {
-//                 let panes = this.read_with(cx, |this, _| {
-//                     this.follower_states
-//                         .iter()
-//                         .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
-//                         .cloned()
-//                         .collect()
-//                 })?;
-//                 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
-//             }
-//         }
-//         this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
-//         Ok(())
-//     }
-
-//     async fn add_views_from_leader(
-//         this: WeakViewHandle<Self>,
-//         leader_id: PeerId,
-//         panes: Vec<ViewHandle<Pane>>,
-//         views: Vec<proto::View>,
-//         cx: &mut AsyncAppContext,
-//     ) -> Result<()> {
-//         let this = this
-//             .upgrade(cx)
-//             .ok_or_else(|| anyhow!("workspace dropped"))?;
-
-//         let item_builders = cx.update(|cx| {
-//             cx.default_global::<FollowableItemBuilders>()
-//                 .values()
-//                 .map(|b| b.0)
-//                 .collect::<Vec<_>>()
-//         });
-
-//         let mut item_tasks_by_pane = HashMap::default();
-//         for pane in panes {
-//             let mut item_tasks = Vec::new();
-//             let mut leader_view_ids = Vec::new();
-//             for view in &views {
-//                 let Some(id) = &view.id else { continue };
-//                 let id = ViewId::from_proto(id.clone())?;
-//                 let mut variant = view.variant.clone();
-//                 if variant.is_none() {
-//                     Err(anyhow!("missing view variant"))?;
-//                 }
-//                 for build_item in &item_builders {
-//                     let task = cx
-//                         .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
-//                     if let Some(task) = task {
-//                         item_tasks.push(task);
-//                         leader_view_ids.push(id);
-//                         break;
-//                     } else {
-//                         assert!(variant.is_some());
-//                     }
-//                 }
-//             }
-
-//             item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
-//         }
-
-//         for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
-//             let items = futures::future::try_join_all(item_tasks).await?;
-//             this.update(cx, |this, cx| {
-//                 let state = this.follower_states.get_mut(&pane)?;
-//                 for (id, item) in leader_view_ids.into_iter().zip(items) {
-//                     item.set_leader_peer_id(Some(leader_id), cx);
-//                     state.items_by_leader_view_id.insert(id, item);
-//                 }
-
-//                 Some(())
-//             });
-//         }
-//         Ok(())
-//     }
-
-//     fn update_active_view_for_followers(&mut self, cx: &AppContext) {
-//         let mut is_project_item = true;
-//         let mut update = proto::UpdateActiveView::default();
-//         if self.active_pane.read(cx).has_focus() {
-//             let item = self
-//                 .active_item(cx)
-//                 .and_then(|item| item.to_followable_item_handle(cx));
-//             if let Some(item) = item {
-//                 is_project_item = item.is_project_item(cx);
-//                 update = proto::UpdateActiveView {
-//                     id: item
-//                         .remote_id(&self.app_state.client, cx)
-//                         .map(|id| id.to_proto()),
-//                     leader_id: self.leader_for_pane(&self.active_pane),
-//                 };
-//             }
-//         }
-
-//         if update.id != self.last_active_view_id {
-//             self.last_active_view_id = update.id.clone();
-//             self.update_followers(
-//                 is_project_item,
-//                 proto::update_followers::Variant::UpdateActiveView(update),
-//                 cx,
-//             );
-//         }
-//     }
-
-//     fn update_followers(
-//         &self,
-//         project_only: bool,
-//         update: proto::update_followers::Variant,
-//         cx: &AppContext,
-//     ) -> Option<()> {
-//         let project_id = if project_only {
-//             self.project.read(cx).remote_id()
-//         } else {
-//             None
-//         };
-//         self.app_state().workspace_store.read_with(cx, |store, cx| {
-//             store.update_followers(project_id, update, cx)
-//         })
-//     }
-
-//     pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
-//         self.follower_states.get(pane).map(|state| state.leader_id)
-//     }
-
-//     fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
-//         cx.notify();
-
-//         let call = self.active_call()?;
-//         let room = call.read(cx).room()?.read(cx);
-//         let participant = room.remote_participant_for_peer_id(leader_id)?;
-//         let mut items_to_activate = Vec::new();
-
-//         let leader_in_this_app;
-//         let leader_in_this_project;
-//         match participant.location {
-//             call::ParticipantLocation::SharedProject { project_id } => {
-//                 leader_in_this_app = true;
-//                 leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
-//             }
-//             call::ParticipantLocation::UnsharedProject => {
-//                 leader_in_this_app = true;
-//                 leader_in_this_project = false;
-//             }
-//             call::ParticipantLocation::External => {
-//                 leader_in_this_app = false;
-//                 leader_in_this_project = false;
-//             }
-//         };
-
-//         for (pane, state) in &self.follower_states {
-//             if state.leader_id != leader_id {
-//                 continue;
-//             }
-//             if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
-//                 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
-//                     if leader_in_this_project || !item.is_project_item(cx) {
-//                         items_to_activate.push((pane.clone(), item.boxed_clone()));
-//                     }
-//                 } else {
-//                     log::warn!(
-//                         "unknown view id {:?} for leader {:?}",
-//                         active_view_id,
-//                         leader_id
-//                     );
-//                 }
-//                 continue;
-//             }
-//             if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
-//                 items_to_activate.push((pane.clone(), Box::new(shared_screen)));
-//             }
-//         }
-
-//         for (pane, item) in items_to_activate {
-//             let pane_was_focused = pane.read(cx).has_focus();
-//             if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
-//                 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
-//             } else {
-//                 pane.update(cx, |pane, cx| {
-//                     pane.add_item(item.boxed_clone(), false, false, None, cx)
-//                 });
-//             }
-
-//             if pane_was_focused {
-//                 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
-//             }
-//         }
-
-//         None
-//     }
-
-//     fn shared_screen_for_peer(
-//         &self,
-//         peer_id: PeerId,
-//         pane: &ViewHandle<Pane>,
-//         cx: &mut ViewContext<Self>,
-//     ) -> Option<ViewHandle<SharedScreen>> {
-//         let call = self.active_call()?;
-//         let room = call.read(cx).room()?.read(cx);
-//         let participant = room.remote_participant_for_peer_id(peer_id)?;
-//         let track = participant.video_tracks.values().next()?.clone();
-//         let user = participant.user.clone();
-
-//         for item in pane.read(cx).items_of_type::<SharedScreen>() {
-//             if item.read(cx).peer_id == peer_id {
-//                 return Some(item);
-//             }
-//         }
-
-//         Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
-//     }
-
-//     pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-//         if active {
-//             self.update_active_view_for_followers(cx);
-//             cx.background()
-//                 .spawn(persistence::DB.update_timestamp(self.database_id()))
-//                 .detach();
-//         } else {
-//             for pane in &self.panes {
-//                 pane.update(cx, |pane, cx| {
-//                     if let Some(item) = pane.active_item() {
-//                         item.workspace_deactivated(cx);
-//                     }
-//                     if matches!(
-//                         settings::get::<WorkspaceSettings>(cx).autosave,
-//                         AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
-//                     ) {
-//                         for item in pane.items() {
-//                             Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
-//                                 .detach_and_log_err(cx);
-//                         }
-//                     }
-//                 });
-//             }
-//         }
-//     }
-
-//     fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
-//         self.active_call.as_ref().map(|(call, _)| call)
-//     }
-
-//     fn on_active_call_event(
-//         &mut self,
-//         _: ModelHandle<ActiveCall>,
-//         event: &call::room::Event,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         match event {
-//             call::room::Event::ParticipantLocationChanged { participant_id }
-//             | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
-//                 self.leader_updated(*participant_id, cx);
-//             }
-//             _ => {}
-//         }
-//     }
-
-//     pub fn database_id(&self) -> WorkspaceId {
-//         self.database_id
-//     }
-
-//     fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
-//         let project = self.project().read(cx);
-
-//         if project.is_local() {
-//             Some(
-//                 project
-//                     .visible_worktrees(cx)
-//                     .map(|worktree| worktree.read(cx).abs_path())
-//                     .collect::<Vec<_>>()
-//                     .into(),
-//             )
-//         } else {
-//             None
-//         }
-//     }
-
-//     fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
-//         match member {
-//             Member::Axis(PaneAxis { members, .. }) => {
-//                 for child in members.iter() {
-//                     self.remove_panes(child.clone(), cx)
-//                 }
-//             }
-//             Member::Pane(pane) => {
-//                 self.force_remove_pane(&pane, cx);
-//             }
-//         }
-//     }
-
-//     fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
-//         self.panes.retain(|p| p != pane);
-//         cx.focus(self.panes.last().unwrap());
-//         if self.last_active_center_pane == Some(pane.downgrade()) {
-//             self.last_active_center_pane = None;
-//         }
-//         cx.notify();
-//     }
-
-//     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
-//         self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
-//             cx.background().timer(Duration::from_millis(100)).await;
-//             this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
-//                 .ok();
-//         }));
-//     }
-
-//     fn serialize_workspace(&self, cx: &ViewContext<Self>) {
-//         fn serialize_pane_handle(
-//             pane_handle: &ViewHandle<Pane>,
-//             cx: &AppContext,
-//         ) -> SerializedPane {
-//             let (items, active) = {
-//                 let pane = pane_handle.read(cx);
-//                 let active_item_id = pane.active_item().map(|item| item.id());
-//                 (
-//                     pane.items()
-//                         .filter_map(|item_handle| {
-//                             Some(SerializedItem {
-//                                 kind: Arc::from(item_handle.serialized_item_kind()?),
-//                                 item_id: item_handle.id(),
-//                                 active: Some(item_handle.id()) == active_item_id,
-//                             })
-//                         })
-//                         .collect::<Vec<_>>(),
-//                     pane.has_focus(),
-//                 )
-//             };
-
-//             SerializedPane::new(items, active)
-//         }
-
-//         fn build_serialized_pane_group(
-//             pane_group: &Member,
-//             cx: &AppContext,
-//         ) -> SerializedPaneGroup {
-//             match pane_group {
-//                 Member::Axis(PaneAxis {
-//                     axis,
-//                     members,
-//                     flexes,
-//                     bounding_boxes: _,
-//                 }) => SerializedPaneGroup::Group {
-//                     axis: *axis,
-//                     children: members
-//                         .iter()
-//                         .map(|member| build_serialized_pane_group(member, cx))
-//                         .collect::<Vec<_>>(),
-//                     flexes: Some(flexes.borrow().clone()),
-//                 },
-//                 Member::Pane(pane_handle) => {
-//                     SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
-//                 }
-//             }
-//         }
-
-//         fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
-//             let left_dock = this.left_dock.read(cx);
-//             let left_visible = left_dock.is_open();
-//             let left_active_panel = left_dock.visible_panel().and_then(|panel| {
-//                 Some(
-//                     cx.view_ui_name(panel.as_any().window(), panel.id())?
-//                         .to_string(),
-//                 )
-//             });
-//             let left_dock_zoom = left_dock
-//                 .visible_panel()
-//                 .map(|panel| panel.is_zoomed(cx))
-//                 .unwrap_or(false);
-
-//             let right_dock = this.right_dock.read(cx);
-//             let right_visible = right_dock.is_open();
-//             let right_active_panel = right_dock.visible_panel().and_then(|panel| {
-//                 Some(
-//                     cx.view_ui_name(panel.as_any().window(), panel.id())?
-//                         .to_string(),
-//                 )
-//             });
-//             let right_dock_zoom = right_dock
-//                 .visible_panel()
-//                 .map(|panel| panel.is_zoomed(cx))
-//                 .unwrap_or(false);
-
-//             let bottom_dock = this.bottom_dock.read(cx);
-//             let bottom_visible = bottom_dock.is_open();
-//             let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
-//                 Some(
-//                     cx.view_ui_name(panel.as_any().window(), panel.id())?
-//                         .to_string(),
-//                 )
-//             });
-//             let bottom_dock_zoom = bottom_dock
-//                 .visible_panel()
-//                 .map(|panel| panel.is_zoomed(cx))
-//                 .unwrap_or(false);
-
-//             DockStructure {
-//                 left: DockData {
-//                     visible: left_visible,
-//                     active_panel: left_active_panel,
-//                     zoom: left_dock_zoom,
-//                 },
-//                 right: DockData {
-//                     visible: right_visible,
-//                     active_panel: right_active_panel,
-//                     zoom: right_dock_zoom,
-//                 },
-//                 bottom: DockData {
-//                     visible: bottom_visible,
-//                     active_panel: bottom_active_panel,
-//                     zoom: bottom_dock_zoom,
-//                 },
-//             }
-//         }
-
-//         if let Some(location) = self.location(cx) {
-//             // Load bearing special case:
-//             //  - with_local_workspace() relies on this to not have other stuff open
-//             //    when you open your log
-//             if !location.paths().is_empty() {
-//                 let center_group = build_serialized_pane_group(&self.center.root, cx);
-//                 let docks = build_serialized_docks(self, cx);
-
-//                 let serialized_workspace = SerializedWorkspace {
-//                     id: self.database_id,
-//                     location,
-//                     center_group,
-//                     bounds: Default::default(),
-//                     display: Default::default(),
-//                     docks,
-//                 };
-
-//                 cx.background()
-//                     .spawn(persistence::DB.save_workspace(serialized_workspace))
-//                     .detach();
-//             }
-//         }
-//     }
-
-//     pub(crate) fn load_workspace(
-//         workspace: WeakViewHandle<Workspace>,
-//         serialized_workspace: SerializedWorkspace,
-//         paths_to_open: Vec<Option<ProjectPath>>,
-//         cx: &mut AppContext,
-//     ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
-//         cx.spawn(|mut cx| async move {
-//             let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
-//                 (
-//                     workspace.project().clone(),
-//                     workspace.last_active_center_pane.clone(),
-//                 )
-//             })?;
-
-//             let mut center_group = None;
-//             let mut center_items = None;
-//             // Traverse the splits tree and add to things
-//             if let Some((group, active_pane, items)) = serialized_workspace
-//                 .center_group
-//                 .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
-//                 .await
-//             {
-//                 center_items = Some(items);
-//                 center_group = Some((group, active_pane))
-//             }
-
-//             let mut items_by_project_path = cx.read(|cx| {
-//                 center_items
-//                     .unwrap_or_default()
-//                     .into_iter()
-//                     .filter_map(|item| {
-//                         let item = item?;
-//                         let project_path = item.project_path(cx)?;
-//                         Some((project_path, item))
-//                     })
-//                     .collect::<HashMap<_, _>>()
-//             });
-
-//             let opened_items = paths_to_open
-//                 .into_iter()
-//                 .map(|path_to_open| {
-//                     path_to_open
-//                         .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
-//                 })
-//                 .collect::<Vec<_>>();
-
-//             // Remove old panes from workspace panes list
-//             workspace.update(&mut cx, |workspace, cx| {
-//                 if let Some((center_group, active_pane)) = center_group {
-//                     workspace.remove_panes(workspace.center.root.clone(), cx);
-
-//                     // Swap workspace center group
-//                     workspace.center = PaneGroup::with_root(center_group);
-
-//                     // Change the focus to the workspace first so that we retrigger focus in on the pane.
-//                     cx.focus_self();
-
-//                     if let Some(active_pane) = active_pane {
-//                         cx.focus(&active_pane);
-//                     } else {
-//                         cx.focus(workspace.panes.last().unwrap());
-//                     }
-//                 } else {
-//                     let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
-//                     if let Some(old_center_handle) = old_center_handle {
-//                         cx.focus(&old_center_handle)
-//                     } else {
-//                         cx.focus_self()
-//                     }
-//                 }
-
-//                 let docks = serialized_workspace.docks;
-//                 workspace.left_dock.update(cx, |dock, cx| {
-//                     dock.set_open(docks.left.visible, cx);
-//                     if let Some(active_panel) = docks.left.active_panel {
-//                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
-//                             dock.activate_panel(ix, cx);
-//                         }
-//                     }
-//                     dock.active_panel()
-//                         .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
-//                     if docks.left.visible && docks.left.zoom {
-//                         cx.focus_self()
-//                     }
-//                 });
-//                 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
-//                 workspace.right_dock.update(cx, |dock, cx| {
-//                     dock.set_open(docks.right.visible, cx);
-//                     if let Some(active_panel) = docks.right.active_panel {
-//                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
-//                             dock.activate_panel(ix, cx);
-//                         }
-//                     }
-//                     dock.active_panel()
-//                         .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
-
-//                     if docks.right.visible && docks.right.zoom {
-//                         cx.focus_self()
-//                     }
-//                 });
-//                 workspace.bottom_dock.update(cx, |dock, cx| {
-//                     dock.set_open(docks.bottom.visible, cx);
-//                     if let Some(active_panel) = docks.bottom.active_panel {
-//                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
-//                             dock.activate_panel(ix, cx);
-//                         }
-//                     }
+type ProjectItemBuilders =
+    HashMap<TypeId, fn(Handle<Project>, AnyHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
+pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
+    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
+        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
+            let item = model.downcast::<I::Item>().unwrap();
+            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
+        });
+    });
+}
 
-//                     dock.active_panel()
-//                         .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
+// type FollowableItemBuilder = fn(
+//     ViewHandle<Pane>,
+//     ViewHandle<Workspace>,
+//     ViewId,
+//     &mut Option<proto::view::Variant>,
+//     &mut AppContext,
+// ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
+// type FollowableItemBuilders = HashMap<
+//     TypeId,
+//     (
+//         FollowableItemBuilder,
+//         fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
+//     ),
+// >;
+// pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
+//     cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
+//         builders.insert(
+//             TypeId::of::<I>(),
+//             (
+//                 |pane, workspace, id, state, cx| {
+//                     I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
+//                         cx.foreground()
+//                             .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
+//                     })
+//                 },
+//                 |this| Box::new(this.clone().downcast::<I>().unwrap()),
+//             ),
+//         );
+//     });
+// }
 
-//                     if docks.bottom.visible && docks.bottom.zoom {
-//                         cx.focus_self()
-//                     }
-//                 });
+// type ItemDeserializers = HashMap<
+//     Arc<str>,
+//     fn(
+//         ModelHandle<Project>,
+//         WeakViewHandle<Workspace>,
+//         WorkspaceId,
+//         ItemId,
+//         &mut ViewContext<Pane>,
+//     ) -> Task<Result<Box<dyn ItemHandle>>>,
+// >;
+// pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
+//     cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
+//         if let Some(serialized_item_kind) = I::serialized_item_kind() {
+//             deserializers.insert(
+//                 Arc::from(serialized_item_kind),
+//                 |project, workspace, workspace_id, item_id, cx| {
+//                     let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
+//                     cx.foreground()
+//                         .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
+//                 },
+//             );
+//         }
+//     });
+// }
 
-//                 cx.notify();
-//             })?;
+pub struct AppState {
+    pub languages: Arc<LanguageRegistry>,
+    pub client: Arc<Client>,
+    pub user_store: Handle<UserStore>,
+    pub workspace_store: Handle<WorkspaceStore>,
+    pub fs: Arc<dyn fs2::Fs>,
+    pub build_window_options:
+        fn(Option<WindowBounds>, Option<DisplayId>, &MainThread<AppContext>) -> WindowOptions,
+    pub initialize_workspace:
+        fn(WeakHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<anyhow::Result<()>>,
+    pub node_runtime: Arc<dyn NodeRuntime>,
+}
 
-//             // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
-//             workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
+pub struct WorkspaceStore {
+    workspaces: HashSet<WeakHandle<Workspace>>,
+    followers: Vec<Follower>,
+    client: Arc<Client>,
+    _subscriptions: Vec<client2::Subscription>,
+}
 
-//             Ok(opened_items)
-//         })
-//     }
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
+struct Follower {
+    project_id: Option<u64>,
+    peer_id: PeerId,
+}
 
+// impl AppState {
 //     #[cfg(any(test, feature = "test-support"))]
-//     pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+//     pub fn test(cx: &mut AppContext) -> Arc<Self> {
 //         use node_runtime::FakeNodeRuntime;
+//         use settings::SettingsStore;
 
-//         let client = project.read(cx).client();
-//         let user_store = project.read(cx).user_store();
+//         if !cx.has_global::<SettingsStore>() {
+//             cx.set_global(SettingsStore::test(cx));
+//         }
 
+//         let fs = fs::FakeFs::new(cx.background().clone());
+//         let languages = Arc::new(LanguageRegistry::test());
+//         let http_client = util::http::FakeHttpClient::with_404_response();
+//         let client = Client::new(http_client.clone(), cx);
+//         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 //         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
-//         let app_state = Arc::new(AppState {
-//             languages: project.read(cx).languages().clone(),
-//             workspace_store,
+
+//         theme::init((), cx);
+//         client::init(&client, cx);
+//         crate::init_settings(cx);
+
+//         Arc::new(Self {
 //             client,
+//             fs,
+//             languages,
 //             user_store,
-//             fs: project.read(cx).fs().clone(),
-//             build_window_options: |_, _, _| Default::default(),
-//             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+//             // channel_store,
+//             workspace_store,
 //             node_runtime: FakeNodeRuntime::new(),
-//         });
-//         Self::new(0, project, app_state, cx)
-//     }
-
-//     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
-//         let dock = match position {
-//             DockPosition::Left => &self.left_dock,
-//             DockPosition::Right => &self.right_dock,
-//             DockPosition::Bottom => &self.bottom_dock,
-//         };
-//         let active_panel = dock.read(cx).visible_panel()?;
-//         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
-//             dock.read(cx).render_placeholder(cx)
-//         } else {
-//             ChildView::new(dock, cx).into_any()
-//         };
-
-//         Some(
-//             element
-//                 .constrained()
-//                 .dynamically(move |constraint, _, cx| match position {
-//                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
-//                         Vector2F::new(20., constraint.min.y()),
-//                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
-//                     ),
-//                     DockPosition::Bottom => SizeConstraint::new(
-//                         Vector2F::new(constraint.min.x(), 20.),
-//                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
-//                     ),
-//                 })
-//                 .into_any(),
-//         )
+//             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+//             build_window_options: |_, _, _| Default::default(),
+//         })
 //     }
 // }
 
-// fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
-//     ZED_WINDOW_POSITION
-//         .zip(*ZED_WINDOW_SIZE)
-//         .map(|(position, size)| {
-//             WindowBounds::Fixed(RectF::new(
-//                 cx.platform().screens()[0].bounds().origin() + position,
-//                 size,
-//             ))
-//         })
+// struct DelayedDebouncedEditAction {
+//     task: Option<Task<()>>,
+//     cancel_channel: Option<oneshot::Sender<()>>,
 // }
 
-// async fn open_items(
-//     serialized_workspace: Option<SerializedWorkspace>,
-//     workspace: &WeakViewHandle<Workspace>,
-//     mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
-//     app_state: Arc<AppState>,
-//     mut cx: AsyncAppContext,
-// ) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
-//     let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
-
-//     if let Some(serialized_workspace) = serialized_workspace {
-//         let workspace = workspace.clone();
-//         let restored_items = cx
-//             .update(|cx| {
-//                 Workspace::load_workspace(
-//                     workspace,
-//                     serialized_workspace,
-//                     project_paths_to_open
-//                         .iter()
-//                         .map(|(_, project_path)| project_path)
-//                         .cloned()
-//                         .collect(),
-//                     cx,
-//                 )
-//             })
-//             .await?;
-
-//         let restored_project_paths = cx.read(|cx| {
-//             restored_items
-//                 .iter()
-//                 .filter_map(|item| item.as_ref()?.project_path(cx))
-//                 .collect::<HashSet<_>>()
-//         });
-
-//         for restored_item in restored_items {
-//             opened_items.push(restored_item.map(Ok));
-//         }
-
-//         project_paths_to_open
-//             .iter_mut()
-//             .for_each(|(_, project_path)| {
-//                 if let Some(project_path_to_open) = project_path {
-//                     if restored_project_paths.contains(project_path_to_open) {
-//                         *project_path = None;
-//                     }
-//                 }
-//             });
-//     } else {
-//         for _ in 0..project_paths_to_open.len() {
-//             opened_items.push(None);
+// impl DelayedDebouncedEditAction {
+//     fn new() -> DelayedDebouncedEditAction {
+//         DelayedDebouncedEditAction {
+//             task: None,
+//             cancel_channel: None,
 //         }
 //     }
-//     assert!(opened_items.len() == project_paths_to_open.len());
-
-//     let tasks =
-//         project_paths_to_open
-//             .into_iter()
-//             .enumerate()
-//             .map(|(i, (abs_path, project_path))| {
-//                 let workspace = workspace.clone();
-//                 cx.spawn(|mut cx| {
-//                     let fs = app_state.fs.clone();
-//                     async move {
-//                         let file_project_path = project_path?;
-//                         if fs.is_file(&abs_path).await {
-//                             Some((
-//                                 i,
-//                                 workspace
-//                                     .update(&mut cx, |workspace, cx| {
-//                                         workspace.open_path(file_project_path, None, true, cx)
-//                                     })
-//                                     .log_err()?
-//                                     .await,
-//                             ))
-//                         } else {
-//                             None
-//                         }
-//                     }
-//                 })
-//             });
 
-//     for maybe_opened_path in futures::future::join_all(tasks.into_iter())
-//         .await
-//         .into_iter()
+//     fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
+//     where
+//         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 //     {
-//         if let Some((i, path_open_result)) = maybe_opened_path {
-//             opened_items[i] = Some(path_open_result);
+//         if let Some(channel) = self.cancel_channel.take() {
+//             _ = channel.send(());
 //         }
-//     }
 
-//     Ok(opened_items)
-// }
+//         let (sender, mut receiver) = oneshot::channel::<()>();
+//         self.cancel_channel = Some(sender);
+
+//         let previous_task = self.task.take();
+//         self.task = Some(cx.spawn(|workspace, mut cx| async move {
+//             let mut timer = cx.background().timer(delay).fuse();
+//             if let Some(previous_task) = previous_task {
+//                 previous_task.await;
+//             }
 
-// fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
-//     const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
-//     const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
-//     const MESSAGE_ID: usize = 2;
+//             futures::select_biased! {
+//                 _ = receiver => return,
+//                     _ = timer => {}
+//             }
 
-//     if workspace
-//         .read_with(cx, |workspace, cx| {
-//             workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-//         })
-//         .unwrap_or(false)
-//     {
-//         return;
+//             if let Some(result) = workspace
+//                 .update(&mut cx, |workspace, cx| (func)(workspace, cx))
+//                 .log_err()
+//             {
+//                 result.await.log_err();
+//             }
+//         }));
 //     }
+// }
 
-//     if db::kvp::KEY_VALUE_STORE
-//         .read_kvp(NEW_DOCK_HINT_KEY)
-//         .ok()
-//         .flatten()
-//         .is_some()
-//     {
-//         if !workspace
-//             .read_with(cx, |workspace, cx| {
-//                 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-//             })
-//             .unwrap_or(false)
-//         {
-//             cx.update(|cx| {
-//                 cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
-//                     let entry = tracker
-//                         .entry(TypeId::of::<MessageNotification>())
-//                         .or_default();
-//                     if !entry.contains(&MESSAGE_ID) {
-//                         entry.push(MESSAGE_ID);
-//                     }
-//                 });
-//             });
-//         }
+// pub enum Event {
+//     PaneAdded(ViewHandle<Pane>),
+//     ContactRequestedJoin(u64),
+// }
 
-//         return;
-//     }
+pub struct Workspace {
+    weak_self: WeakHandle<Self>,
+    //     modal: Option<ActiveModal>,
+    //     zoomed: Option<AnyWeakViewHandle>,
+    //     zoomed_position: Option<DockPosition>,
+    //     center: PaneGroup,
+    //     left_dock: ViewHandle<Dock>,
+    //     bottom_dock: ViewHandle<Dock>,
+    //     right_dock: ViewHandle<Dock>,
+    panes: Vec<View<Pane>>,
+    //     panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
+    //     active_pane: ViewHandle<Pane>,
+    last_active_center_pane: Option<WeakView<Pane>>,
+    //     last_active_view_id: Option<proto::ViewId>,
+    //     status_bar: ViewHandle<StatusBar>,
+    //     titlebar_item: Option<AnyViewHandle>,
+    //     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
+    project: Handle<Project>,
+    //     follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
+    //     last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
+    //     window_edited: bool,
+    //     active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
+    //     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
+    //     database_id: WorkspaceId,
+    app_state: Arc<AppState>,
+    //     subscriptions: Vec<Subscription>,
+    //     _apply_leader_updates: Task<Result<()>>,
+    //     _observe_current_user: Task<Result<()>>,
+    //     _schedule_serialize: Option<Task<()>>,
+    //     pane_history_timestamp: Arc<AtomicUsize>,
+}
 
-//     cx.spawn(|_| async move {
-//         db::kvp::KEY_VALUE_STORE
-//             .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
-//             .await
-//             .ok();
-//     })
-//     .detach();
+// struct ActiveModal {
+//     view: Box<dyn ModalHandle>,
+//     previously_focused_view_id: Option<usize>,
+// }
 
-//     workspace
-//         .update(cx, |workspace, cx| {
-//             workspace.show_notification_once(2, cx, |cx| {
-//                 cx.add_view(|_| {
-//                     MessageNotification::new_element(|text, _| {
-//                         Text::new(
-//                             "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
-//                             text,
-//                         )
-//                         .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
-//                             let code_span_background_color = settings::get::<ThemeSettings>(cx)
-//                                 .theme
-//                                 .editor
-//                                 .document_highlight_read_background;
-
-//                             cx.scene().push_quad(gpui::Quad {
-//                                 bounds,
-//                                 background: Some(code_span_background_color),
-//                                 border: Default::default(),
-//                                 corner_radii: (2.0).into(),
-//                             })
-//                         })
-//                         .into_any()
-//                     })
-//                     .with_click_message("Read more about the new panel system")
-//                     .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
-//                 })
-//             })
-//         })
-//         .ok();
+// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+// pub struct ViewId {
+//     pub creator: PeerId,
+//     pub id: u64,
 // }
 
+#[derive(Default)]
+struct FollowerState {
+    leader_id: PeerId,
+    active_view_id: Option<ViewId>,
+    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
+}
+
+// enum WorkspaceBounds {}
+
+impl Workspace {
+    //     pub fn new(
+    //         workspace_id: WorkspaceId,
+    //         project: ModelHandle<Project>,
+    //         app_state: Arc<AppState>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Self {
+    //         cx.observe(&project, |_, _, cx| cx.notify()).detach();
+    //         cx.subscribe(&project, move |this, _, event, cx| {
+    //             match event {
+    //                 project::Event::RemoteIdChanged(_) => {
+    //                     this.update_window_title(cx);
+    //                 }
+
+    //                 project::Event::CollaboratorLeft(peer_id) => {
+    //                     this.collaborator_left(*peer_id, cx);
+    //                 }
+
+    //                 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
+    //                     this.update_window_title(cx);
+    //                     this.serialize_workspace(cx);
+    //                 }
+
+    //                 project::Event::DisconnectedFromHost => {
+    //                     this.update_window_edited(cx);
+    //                     cx.blur();
+    //                 }
+
+    //                 project::Event::Closed => {
+    //                     cx.remove_window();
+    //                 }
+
+    //                 project::Event::DeletedEntry(entry_id) => {
+    //                     for pane in this.panes.iter() {
+    //                         pane.update(cx, |pane, cx| {
+    //                             pane.handle_deleted_project_item(*entry_id, cx)
+    //                         });
+    //                     }
+    //                 }
+
+    //                 project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+    //                     cx.add_view(|_| MessageNotification::new(message.clone()))
+    //                 }),
+
+    //                 _ => {}
+    //             }
+    //             cx.notify()
+    //         })
+    //         .detach();
+
+    //         let weak_handle = cx.weak_handle();
+    //         let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
+
+    //         let center_pane = cx.add_view(|cx| {
+    //             Pane::new(
+    //                 weak_handle.clone(),
+    //                 project.clone(),
+    //                 pane_history_timestamp.clone(),
+    //                 cx,
+    //             )
+    //         });
+    //         cx.subscribe(&center_pane, Self::handle_pane_event).detach();
+    //         cx.focus(&center_pane);
+    //         cx.emit(Event::PaneAdded(center_pane.clone()));
+
+    //         app_state.workspace_store.update(cx, |store, _| {
+    //             store.workspaces.insert(weak_handle.clone());
+    //         });
+
+    //         let mut current_user = app_state.user_store.read(cx).watch_current_user();
+    //         let mut connection_status = app_state.client.status();
+    //         let _observe_current_user = cx.spawn(|this, mut cx| async move {
+    //             current_user.recv().await;
+    //             connection_status.recv().await;
+    //             let mut stream =
+    //                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
+
+    //             while stream.recv().await.is_some() {
+    //                 this.update(&mut cx, |_, cx| cx.notify())?;
+    //             }
+    //             anyhow::Ok(())
+    //         });
+
+    //         // All leader updates are enqueued and then processed in a single task, so
+    //         // that each asynchronous operation can be run in order.
+    //         let (leader_updates_tx, mut leader_updates_rx) =
+    //             mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
+    //         let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
+    //             while let Some((leader_id, update)) = leader_updates_rx.next().await {
+    //                 Self::process_leader_update(&this, leader_id, update, &mut cx)
+    //                     .await
+    //                     .log_err();
+    //             }
+
+    //             Ok(())
+    //         });
+
+    //         cx.emit_global(WorkspaceCreated(weak_handle.clone()));
+
+    //         let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
+    //         let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
+    //         let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
+    //         let left_dock_buttons =
+    //             cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
+    //         let bottom_dock_buttons =
+    //             cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
+    //         let right_dock_buttons =
+    //             cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
+    //         let status_bar = cx.add_view(|cx| {
+    //             let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
+    //             status_bar.add_left_item(left_dock_buttons, cx);
+    //             status_bar.add_right_item(right_dock_buttons, cx);
+    //             status_bar.add_right_item(bottom_dock_buttons, cx);
+    //             status_bar
+    //         });
+
+    //         cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
+    //             drag_and_drop.register_container(weak_handle.clone());
+    //         });
+
+    //         let mut active_call = None;
+    //         if cx.has_global::<ModelHandle<ActiveCall>>() {
+    //             let call = cx.global::<ModelHandle<ActiveCall>>().clone();
+    //             let mut subscriptions = Vec::new();
+    //             subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
+    //             active_call = Some((call, subscriptions));
+    //         }
+
+    //         let subscriptions = vec![
+    //             cx.observe_fullscreen(|_, _, cx| cx.notify()),
+    //             cx.observe_window_activation(Self::on_window_activation_changed),
+    //             cx.observe_window_bounds(move |_, mut bounds, display, cx| {
+    //                 // Transform fixed bounds to be stored in terms of the containing display
+    //                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
+    //                     if let Some(screen) = cx.platform().screen_by_id(display) {
+    //                         let screen_bounds = screen.bounds();
+    //                         window_bounds
+    //                             .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
+    //                         window_bounds
+    //                             .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
+    //                         bounds = WindowBounds::Fixed(window_bounds);
+    //                     }
+    //                 }
+
+    //                 cx.background()
+    //                     .spawn(DB.set_window_bounds(workspace_id, bounds, display))
+    //                     .detach_and_log_err(cx);
+    //             }),
+    //             cx.observe(&left_dock, |this, _, cx| {
+    //                 this.serialize_workspace(cx);
+    //                 cx.notify();
+    //             }),
+    //             cx.observe(&bottom_dock, |this, _, cx| {
+    //                 this.serialize_workspace(cx);
+    //                 cx.notify();
+    //             }),
+    //             cx.observe(&right_dock, |this, _, cx| {
+    //                 this.serialize_workspace(cx);
+    //                 cx.notify();
+    //             }),
+    //         ];
+
+    //         cx.defer(|this, cx| this.update_window_title(cx));
+    //         Workspace {
+    //             weak_self: weak_handle.clone(),
+    //             modal: None,
+    //             zoomed: None,
+    //             zoomed_position: None,
+    //             center: PaneGroup::new(center_pane.clone()),
+    //             panes: vec![center_pane.clone()],
+    //             panes_by_item: Default::default(),
+    //             active_pane: center_pane.clone(),
+    //             last_active_center_pane: Some(center_pane.downgrade()),
+    //             last_active_view_id: None,
+    //             status_bar,
+    //             titlebar_item: None,
+    //             notifications: Default::default(),
+    //             left_dock,
+    //             bottom_dock,
+    //             right_dock,
+    //             project: project.clone(),
+    //             follower_states: Default::default(),
+    //             last_leaders_by_pane: Default::default(),
+    //             window_edited: false,
+    //             active_call,
+    //             database_id: workspace_id,
+    //             app_state,
+    //             _observe_current_user,
+    //             _apply_leader_updates,
+    //             _schedule_serialize: None,
+    //             leader_updates_tx,
+    //             subscriptions,
+    //             pane_history_timestamp,
+    //         }
+    //     }
+
+    //     fn new_local(
+    //         abs_paths: Vec<PathBuf>,
+    //         app_state: Arc<AppState>,
+    //         requesting_window: Option<WindowHandle<Workspace>>,
+    //         cx: &mut AppContext,
+    //     ) -> Task<(
+    //         WeakViewHandle<Workspace>,
+    //         Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+    //     )> {
+    //         let project_handle = Project::local(
+    //             app_state.client.clone(),
+    //             app_state.node_runtime.clone(),
+    //             app_state.user_store.clone(),
+    //             app_state.languages.clone(),
+    //             app_state.fs.clone(),
+    //             cx,
+    //         );
+
+    //         cx.spawn(|mut cx| async move {
+    //             let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+
+    //             let paths_to_open = Arc::new(abs_paths);
+
+    //             // Get project paths for all of the abs_paths
+    //             let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
+    //             let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
+    //                 Vec::with_capacity(paths_to_open.len());
+    //             for path in paths_to_open.iter().cloned() {
+    //                 if let Some((worktree, project_entry)) = cx
+    //                     .update(|cx| {
+    //                         Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
+    //                     })
+    //                     .await
+    //                     .log_err()
+    //                 {
+    //                     worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
+    //                     project_paths.push((path, Some(project_entry)));
+    //                 } else {
+    //                     project_paths.push((path, None));
+    //                 }
+    //             }
+
+    //             let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
+    //                 serialized_workspace.id
+    //             } else {
+    //                 DB.next_id().await.unwrap_or(0)
+    //             };
+
+    //             let window = if let Some(window) = requesting_window {
+    //                 window.replace_root(&mut cx, |cx| {
+    //                     Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+    //                 });
+    //                 window
+    //             } else {
+    //                 {
+    //                     let window_bounds_override = window_bounds_env_override(&cx);
+    //                     let (bounds, display) = if let Some(bounds) = window_bounds_override {
+    //                         (Some(bounds), None)
+    //                     } else {
+    //                         serialized_workspace
+    //                             .as_ref()
+    //                             .and_then(|serialized_workspace| {
+    //                                 let display = serialized_workspace.display?;
+    //                                 let mut bounds = serialized_workspace.bounds?;
+
+    //                                 // Stored bounds are relative to the containing display.
+    //                                 // So convert back to global coordinates if that screen still exists
+    //                                 if let WindowBounds::Fixed(mut window_bounds) = bounds {
+    //                                     if let Some(screen) = cx.platform().screen_by_id(display) {
+    //                                         let screen_bounds = screen.bounds();
+    //                                         window_bounds.set_origin_x(
+    //                                             window_bounds.origin_x() + screen_bounds.origin_x(),
+    //                                         );
+    //                                         window_bounds.set_origin_y(
+    //                                             window_bounds.origin_y() + screen_bounds.origin_y(),
+    //                                         );
+    //                                         bounds = WindowBounds::Fixed(window_bounds);
+    //                                     } else {
+    //                                         // Screen no longer exists. Return none here.
+    //                                         return None;
+    //                                     }
+    //                                 }
+
+    //                                 Some((bounds, display))
+    //                             })
+    //                             .unzip()
+    //                     };
+
+    //                     // Use the serialized workspace to construct the new window
+    //                     cx.add_window(
+    //                         (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+    //                         |cx| {
+    //                             Workspace::new(
+    //                                 workspace_id,
+    //                                 project_handle.clone(),
+    //                                 app_state.clone(),
+    //                                 cx,
+    //                             )
+    //                         },
+    //                     )
+    //                 }
+    //             };
+
+    //             // We haven't yielded the main thread since obtaining the window handle,
+    //             // so the window exists.
+    //             let workspace = window.root(&cx).unwrap();
+
+    //             (app_state.initialize_workspace)(
+    //                 workspace.downgrade(),
+    //                 serialized_workspace.is_some(),
+    //                 app_state.clone(),
+    //                 cx.clone(),
+    //             )
+    //             .await
+    //             .log_err();
+
+    //             window.update(&mut cx, |cx| cx.activate_window());
+
+    //             let workspace = workspace.downgrade();
+    //             notify_if_database_failed(&workspace, &mut cx);
+    //             let opened_items = open_items(
+    //                 serialized_workspace,
+    //                 &workspace,
+    //                 project_paths,
+    //                 app_state,
+    //                 cx,
+    //             )
+    //             .await
+    //             .unwrap_or_default();
+
+    //             (workspace, opened_items)
+    //         })
+    //     }
+
+    //     pub fn weak_handle(&self) -> WeakViewHandle<Self> {
+    //         self.weak_self.clone()
+    //     }
+
+    //     pub fn left_dock(&self) -> &ViewHandle<Dock> {
+    //         &self.left_dock
+    //     }
+
+    //     pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
+    //         &self.bottom_dock
+    //     }
+
+    //     pub fn right_dock(&self) -> &ViewHandle<Dock> {
+    //         &self.right_dock
+    //     }
+
+    //     pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    //     where
+    //         T::Event: std::fmt::Debug,
+    //     {
+    //         self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
+    //     }
+
+    //     pub fn add_panel_with_extra_event_handler<T: Panel, F>(
+    //         &mut self,
+    //         panel: ViewHandle<T>,
+    //         cx: &mut ViewContext<Self>,
+    //         handler: F,
+    //     ) where
+    //         T::Event: std::fmt::Debug,
+    //         F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
+    //     {
+    //         let dock = match panel.position(cx) {
+    //             DockPosition::Left => &self.left_dock,
+    //             DockPosition::Bottom => &self.bottom_dock,
+    //             DockPosition::Right => &self.right_dock,
+    //         };
+
+    //         self.subscriptions.push(cx.subscribe(&panel, {
+    //             let mut dock = dock.clone();
+    //             let mut prev_position = panel.position(cx);
+    //             move |this, panel, event, cx| {
+    //                 if T::should_change_position_on_event(event) {
+    //                     let new_position = panel.read(cx).position(cx);
+    //                     let mut was_visible = false;
+    //                     dock.update(cx, |dock, cx| {
+    //                         prev_position = new_position;
+
+    //                         was_visible = dock.is_open()
+    //                             && dock
+    //                                 .visible_panel()
+    //                                 .map_or(false, |active_panel| active_panel.id() == panel.id());
+    //                         dock.remove_panel(&panel, cx);
+    //                     });
+
+    //                     if panel.is_zoomed(cx) {
+    //                         this.zoomed_position = Some(new_position);
+    //                     }
+
+    //                     dock = match panel.read(cx).position(cx) {
+    //                         DockPosition::Left => &this.left_dock,
+    //                         DockPosition::Bottom => &this.bottom_dock,
+    //                         DockPosition::Right => &this.right_dock,
+    //                     }
+    //                     .clone();
+    //                     dock.update(cx, |dock, cx| {
+    //                         dock.add_panel(panel.clone(), cx);
+    //                         if was_visible {
+    //                             dock.set_open(true, cx);
+    //                             dock.activate_panel(dock.panels_len() - 1, cx);
+    //                         }
+    //                     });
+    //                 } else if T::should_zoom_in_on_event(event) {
+    //                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
+    //                     if !panel.has_focus(cx) {
+    //                         cx.focus(&panel);
+    //                     }
+    //                     this.zoomed = Some(panel.downgrade().into_any());
+    //                     this.zoomed_position = Some(panel.read(cx).position(cx));
+    //                 } else if T::should_zoom_out_on_event(event) {
+    //                     dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
+    //                     if this.zoomed_position == Some(prev_position) {
+    //                         this.zoomed = None;
+    //                         this.zoomed_position = None;
+    //                     }
+    //                     cx.notify();
+    //                 } else if T::is_focus_event(event) {
+    //                     let position = panel.read(cx).position(cx);
+    //                     this.dismiss_zoomed_items_to_reveal(Some(position), cx);
+    //                     if panel.is_zoomed(cx) {
+    //                         this.zoomed = Some(panel.downgrade().into_any());
+    //                         this.zoomed_position = Some(position);
+    //                     } else {
+    //                         this.zoomed = None;
+    //                         this.zoomed_position = None;
+    //                     }
+    //                     this.update_active_view_for_followers(cx);
+    //                     cx.notify();
+    //                 } else {
+    //                     handler(this, &panel, event, cx)
+    //                 }
+    //             }
+    //         }));
+
+    //         dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
+    //     }
+
+    //     pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
+    //         &self.status_bar
+    //     }
+
+    //     pub fn app_state(&self) -> &Arc<AppState> {
+    //         &self.app_state
+    //     }
+
+    //     pub fn user_store(&self) -> &ModelHandle<UserStore> {
+    //         &self.app_state.user_store
+    //     }
+
+    pub fn project(&self) -> &Handle<Project> {
+        &self.project
+    }
+
+    //     pub fn recent_navigation_history(
+    //         &self,
+    //         limit: Option<usize>,
+    //         cx: &AppContext,
+    //     ) -> Vec<(ProjectPath, Option<PathBuf>)> {
+    //         let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
+    //         let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
+    //         for pane in &self.panes {
+    //             let pane = pane.read(cx);
+    //             pane.nav_history()
+    //                 .for_each_entry(cx, |entry, (project_path, fs_path)| {
+    //                     if let Some(fs_path) = &fs_path {
+    //                         abs_paths_opened
+    //                             .entry(fs_path.clone())
+    //                             .or_default()
+    //                             .insert(project_path.clone());
+    //                     }
+    //                     let timestamp = entry.timestamp;
+    //                     match history.entry(project_path) {
+    //                         hash_map::Entry::Occupied(mut entry) => {
+    //                             let (_, old_timestamp) = entry.get();
+    //                             if &timestamp > old_timestamp {
+    //                                 entry.insert((fs_path, timestamp));
+    //                             }
+    //                         }
+    //                         hash_map::Entry::Vacant(entry) => {
+    //                             entry.insert((fs_path, timestamp));
+    //                         }
+    //                     }
+    //                 });
+    //         }
+
+    //         history
+    //             .into_iter()
+    //             .sorted_by_key(|(_, (_, timestamp))| *timestamp)
+    //             .map(|(project_path, (fs_path, _))| (project_path, fs_path))
+    //             .rev()
+    //             .filter(|(history_path, abs_path)| {
+    //                 let latest_project_path_opened = abs_path
+    //                     .as_ref()
+    //                     .and_then(|abs_path| abs_paths_opened.get(abs_path))
+    //                     .and_then(|project_paths| {
+    //                         project_paths
+    //                             .iter()
+    //                             .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
+    //                     });
+
+    //                 match latest_project_path_opened {
+    //                     Some(latest_project_path_opened) => latest_project_path_opened == history_path,
+    //                     None => true,
+    //                 }
+    //             })
+    //             .take(limit.unwrap_or(usize::MAX))
+    //             .collect()
+    //     }
+
+    //     fn navigate_history(
+    //         &mut self,
+    //         pane: WeakViewHandle<Pane>,
+    //         mode: NavigationMode,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Task<Result<()>> {
+    //         let to_load = if let Some(pane) = pane.upgrade(cx) {
+    //             cx.focus(&pane);
+
+    //             pane.update(cx, |pane, cx| {
+    //                 loop {
+    //                     // Retrieve the weak item handle from the history.
+    //                     let entry = pane.nav_history_mut().pop(mode, cx)?;
+
+    //                     // If the item is still present in this pane, then activate it.
+    //                     if let Some(index) = entry
+    //                         .item
+    //                         .upgrade(cx)
+    //                         .and_then(|v| pane.index_for_item(v.as_ref()))
+    //                     {
+    //                         let prev_active_item_index = pane.active_item_index();
+    //                         pane.nav_history_mut().set_mode(mode);
+    //                         pane.activate_item(index, true, true, cx);
+    //                         pane.nav_history_mut().set_mode(NavigationMode::Normal);
+
+    //                         let mut navigated = prev_active_item_index != pane.active_item_index();
+    //                         if let Some(data) = entry.data {
+    //                             navigated |= pane.active_item()?.navigate(data, cx);
+    //                         }
+
+    //                         if navigated {
+    //                             break None;
+    //                         }
+    //                     }
+    //                     // If the item is no longer present in this pane, then retrieve its
+    //                     // project path in order to reopen it.
+    //                     else {
+    //                         break pane
+    //                             .nav_history()
+    //                             .path_for_item(entry.item.id())
+    //                             .map(|(project_path, _)| (project_path, entry));
+    //                     }
+    //                 }
+    //             })
+    //         } else {
+    //             None
+    //         };
+
+    //         if let Some((project_path, entry)) = to_load {
+    //             // If the item was no longer present, then load it again from its previous path.
+    //             let task = self.load_path(project_path, cx);
+    //             cx.spawn(|workspace, mut cx| async move {
+    //                 let task = task.await;
+    //                 let mut navigated = false;
+    //                 if let Some((project_entry_id, build_item)) = task.log_err() {
+    //                     let prev_active_item_id = pane.update(&mut cx, |pane, _| {
+    //                         pane.nav_history_mut().set_mode(mode);
+    //                         pane.active_item().map(|p| p.id())
+    //                     })?;
+
+    //                     pane.update(&mut cx, |pane, cx| {
+    //                         let item = pane.open_item(project_entry_id, true, cx, build_item);
+    //                         navigated |= Some(item.id()) != prev_active_item_id;
+    //                         pane.nav_history_mut().set_mode(NavigationMode::Normal);
+    //                         if let Some(data) = entry.data {
+    //                             navigated |= item.navigate(data, cx);
+    //                         }
+    //                     })?;
+    //                 }
+
+    //                 if !navigated {
+    //                     workspace
+    //                         .update(&mut cx, |workspace, cx| {
+    //                             Self::navigate_history(workspace, pane, mode, cx)
+    //                         })?
+    //                         .await?;
+    //                 }
+
+    //                 Ok(())
+    //             })
+    //         } else {
+    //             Task::ready(Ok(()))
+    //         }
+    //     }
+
+    //     pub fn go_back(
+    //         &mut self,
+    //         pane: WeakViewHandle<Pane>,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Task<Result<()>> {
+    //         self.navigate_history(pane, NavigationMode::GoingBack, cx)
+    //     }
+
+    //     pub fn go_forward(
+    //         &mut self,
+    //         pane: WeakViewHandle<Pane>,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Task<Result<()>> {
+    //         self.navigate_history(pane, NavigationMode::GoingForward, cx)
+    //     }
+
+    //     pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
+    //         self.navigate_history(
+    //             self.active_pane().downgrade(),
+    //             NavigationMode::ReopeningClosedItem,
+    //             cx,
+    //         )
+    //     }
+
+    //     pub fn client(&self) -> &Client {
+    //         &self.app_state.client
+    //     }
+
+    //     pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    //         self.titlebar_item = Some(item);
+    //         cx.notify();
+    //     }
+
+    //     pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
+    //         self.titlebar_item.clone()
+    //     }
+
+    //     /// Call the given callback with a workspace whose project is local.
+    //     ///
+    //     /// If the given workspace has a local project, then it will be passed
+    //     /// to the callback. Otherwise, a new empty window will be created.
+    //     pub fn with_local_workspace<T, F>(
+    //         &mut self,
+    //         cx: &mut ViewContext<Self>,
+    //         callback: F,
+    //     ) -> Task<Result<T>>
+    //     where
+    //         T: 'static,
+    //         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+    //     {
+    //         if self.project.read(cx).is_local() {
+    //             Task::Ready(Some(Ok(callback(self, cx))))
+    //         } else {
+    //             let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
+    //             cx.spawn(|_vh, mut cx| async move {
+    //                 let (workspace, _) = task.await;
+    //                 workspace.update(&mut cx, callback)
+    //             })
+    //         }
+    //     }
+
+    //     pub fn worktrees<'a>(
+    //         &self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+    //         self.project.read(cx).worktrees(cx)
+    //     }
+
+    //     pub fn visible_worktrees<'a>(
+    //         &self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+    //         self.project.read(cx).visible_worktrees(cx)
+    //     }
+
+    //     pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
+    //         let futures = self
+    //             .worktrees(cx)
+    //             .filter_map(|worktree| worktree.read(cx).as_local())
+    //             .map(|worktree| worktree.scan_complete())
+    //             .collect::<Vec<_>>();
+    //         async move {
+    //             for future in futures {
+    //                 future.await;
+    //             }
+    //         }
+    //     }
+
+    //     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
+    //         cx.spawn(|mut cx| async move {
+    //             let window = cx
+    //                 .windows()
+    //                 .into_iter()
+    //                 .find(|window| window.is_active(&cx).unwrap_or(false));
+    //             if let Some(window) = window {
+    //                 //This can only get called when the window's project connection has been lost
+    //                 //so we don't need to prompt the user for anything and instead just close the window
+    //                 window.remove(&mut cx);
+    //             }
+    //         })
+    //         .detach();
+    //     }
+
+    //     pub fn close(
+    //         &mut self,
+    //         _: &CloseWindow,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let window = cx.window();
+    //         let prepare = self.prepare_to_close(false, cx);
+    //         Some(cx.spawn(|_, mut cx| async move {
+    //             if prepare.await? {
+    //                 window.remove(&mut cx);
+    //             }
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn prepare_to_close(
+    //         &mut self,
+    //         quitting: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<bool>> {
+    //         let active_call = self.active_call().cloned();
+    //         let window = cx.window();
+
+    //         cx.spawn(|this, mut cx| async move {
+    //             let workspace_count = cx
+    //                 .windows()
+    //                 .into_iter()
+    //                 .filter(|window| window.root_is::<Workspace>())
+    //                 .count();
+
+    //             if let Some(active_call) = active_call {
+    //                 if !quitting
+    //                     && workspace_count == 1
+    //                     && active_call.read_with(&cx, |call, _| call.room().is_some())
+    //                 {
+    //                     let answer = window.prompt(
+    //                         PromptLevel::Warning,
+    //                         "Do you want to leave the current call?",
+    //                         &["Close window and hang up", "Cancel"],
+    //                         &mut cx,
+    //                     );
+
+    //                     if let Some(mut answer) = answer {
+    //                         if answer.next().await == Some(1) {
+    //                             return anyhow::Ok(false);
+    //                         } else {
+    //                             active_call
+    //                                 .update(&mut cx, |call, cx| call.hang_up(cx))
+    //                                 .await
+    //                                 .log_err();
+    //                         }
+    //                     }
+    //                 }
+    //             }
+
+    //             Ok(this
+    //                 .update(&mut cx, |this, cx| {
+    //                     this.save_all_internal(SaveIntent::Close, cx)
+    //                 })?
+    //                 .await?)
+    //         })
+    //     }
+
+    //     fn save_all(
+    //         &mut self,
+    //         action: &SaveAll,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let save_all =
+    //             self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
+    //         Some(cx.foreground().spawn(async move {
+    //             save_all.await?;
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     fn save_all_internal(
+    //         &mut self,
+    //         mut save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<bool>> {
+    //         if self.project.read(cx).is_read_only() {
+    //             return Task::ready(Ok(true));
+    //         }
+    //         let dirty_items = self
+    //             .panes
+    //             .iter()
+    //             .flat_map(|pane| {
+    //                 pane.read(cx).items().filter_map(|item| {
+    //                     if item.is_dirty(cx) {
+    //                         Some((pane.downgrade(), item.boxed_clone()))
+    //                     } else {
+    //                         None
+    //                     }
+    //                 })
+    //             })
+    //             .collect::<Vec<_>>();
+
+    //         let project = self.project.clone();
+    //         cx.spawn(|workspace, mut cx| async move {
+    //             // Override save mode and display "Save all files" prompt
+    //             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+    //                 let mut answer = workspace.update(&mut cx, |_, cx| {
+    //                     let prompt = Pane::file_names_for_prompt(
+    //                         &mut dirty_items.iter().map(|(_, handle)| handle),
+    //                         dirty_items.len(),
+    //                         cx,
+    //                     );
+    //                     cx.prompt(
+    //                         PromptLevel::Warning,
+    //                         &prompt,
+    //                         &["Save all", "Discard all", "Cancel"],
+    //                     )
+    //                 })?;
+    //                 match answer.next().await {
+    //                     Some(0) => save_intent = SaveIntent::SaveAll,
+    //                     Some(1) => save_intent = SaveIntent::Skip,
+    //                     _ => {}
+    //                 }
+    //             }
+    //             for (pane, item) in dirty_items {
+    //                 let (singleton, project_entry_ids) =
+    //                     cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
+    //                 if singleton || !project_entry_ids.is_empty() {
+    //                     if let Some(ix) =
+    //                         pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
+    //                     {
+    //                         if !Pane::save_item(
+    //                             project.clone(),
+    //                             &pane,
+    //                             ix,
+    //                             &*item,
+    //                             save_intent,
+    //                             &mut cx,
+    //                         )
+    //                         .await?
+    //                         {
+    //                             return Ok(false);
+    //                         }
+    //                     }
+    //                 }
+    //             }
+    //             Ok(true)
+    //         })
+    //     }
+
+    //     pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+    //         let mut paths = cx.prompt_for_paths(PathPromptOptions {
+    //             files: true,
+    //             directories: true,
+    //             multiple: true,
+    //         });
+
+    //         Some(cx.spawn(|this, mut cx| async move {
+    //             if let Some(paths) = paths.recv().await.flatten() {
+    //                 if let Some(task) = this
+    //                     .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
+    //                     .log_err()
+    //                 {
+    //                     task.await?
+    //                 }
+    //             }
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn open_workspace_for_paths(
+    //         &mut self,
+    //         paths: Vec<PathBuf>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let window = cx.window().downcast::<Self>();
+    //         let is_remote = self.project.read(cx).is_remote();
+    //         let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
+    //         let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
+    //         let close_task = if is_remote || has_worktree || has_dirty_items {
+    //             None
+    //         } else {
+    //             Some(self.prepare_to_close(false, cx))
+    //         };
+    //         let app_state = self.app_state.clone();
+
+    //         cx.spawn(|_, mut cx| async move {
+    //             let window_to_replace = if let Some(close_task) = close_task {
+    //                 if !close_task.await? {
+    //                     return Ok(());
+    //                 }
+    //                 window
+    //             } else {
+    //                 None
+    //             };
+    //             cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
+    //                 .await?;
+    //             Ok(())
+    //         })
+    //     }
+
+    #[allow(clippy::type_complexity)]
+    pub fn open_paths(
+        &mut self,
+        mut abs_paths: Vec<PathBuf>,
+        visible: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
+        log::info!("open paths {:?}", abs_paths);
+
+        let fs = self.app_state.fs.clone();
+
+        // Sort the paths to ensure we add worktrees for parents before their children.
+        abs_paths.sort_unstable();
+        cx.spawn(|this, mut cx| async move {
+            let mut tasks = Vec::with_capacity(abs_paths.len());
+            for abs_path in &abs_paths {
+                let project_path = match this
+                    .update(&mut cx, |this, cx| {
+                        Workspace::project_path_for_path(
+                            this.project.clone(),
+                            abs_path,
+                            visible,
+                            cx,
+                        )
+                    })
+                    .log_err()
+                {
+                    Some(project_path) => project_path.await.log_err(),
+                    None => None,
+                };
+
+                let this = this.clone();
+                let task = cx.spawn(|mut cx| {
+                    let fs = fs.clone();
+                    let abs_path = abs_path.clone();
+                    async move {
+                        let (worktree, project_path) = project_path?;
+                        if fs.is_file(&abs_path).await {
+                            Some(
+                                this.update(&mut cx, |this, cx| {
+                                    this.open_path(project_path, None, true, cx)
+                                })
+                                .log_err()?
+                                .await,
+                            )
+                        } else {
+                            this.update(&mut cx, |workspace, cx| {
+                                let worktree = worktree.read(cx);
+                                let worktree_abs_path = worktree.abs_path();
+                                let entry_id = if abs_path == worktree_abs_path.as_ref() {
+                                    worktree.root_entry()
+                                } else {
+                                    abs_path
+                                        .strip_prefix(worktree_abs_path.as_ref())
+                                        .ok()
+                                        .and_then(|relative_path| {
+                                            worktree.entry_for_path(relative_path)
+                                        })
+                                }
+                                .map(|entry| entry.id);
+                                if let Some(entry_id) = entry_id {
+                                    workspace.project.update(cx, |_, cx| {
+                                        cx.emit(project2::Event::ActiveEntryChanged(Some(
+                                            entry_id,
+                                        )));
+                                    })
+                                }
+                            })
+                            .log_err()?;
+                            None
+                        }
+                    }
+                });
+                tasks.push(task);
+            }
+
+            futures::future::join_all(tasks).await
+        })
+    }
+
+    //     fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
+    //         let mut paths = cx.prompt_for_paths(PathPromptOptions {
+    //             files: false,
+    //             directories: true,
+    //             multiple: true,
+    //         });
+    //         cx.spawn(|this, mut cx| async move {
+    //             if let Some(paths) = paths.recv().await.flatten() {
+    //                 let results = this
+    //                     .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
+    //                     .await;
+    //                 for result in results.into_iter().flatten() {
+    //                     result.log_err();
+    //                 }
+    //             }
+    //             anyhow::Ok(())
+    //         })
+    //         .detach_and_log_err(cx);
+    //     }
+
+    fn project_path_for_path(
+        project: Handle<Project>,
+        abs_path: &Path,
+        visible: bool,
+        cx: &mut AppContext,
+    ) -> Task<Result<(Handle<Worktree>, ProjectPath)>> {
+        let entry = project.update(cx, |project, cx| {
+            project.find_or_create_local_worktree(abs_path, visible, cx)
+        });
+        cx.spawn(|cx| async move {
+            let (worktree, path) = entry.await?;
+            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
+            Ok((
+                worktree,
+                ProjectPath {
+                    worktree_id,
+                    path: path.into(),
+                },
+            ))
+        })
+    }
+
+    //     /// Returns the modal that was toggled closed if it was open.
+    //     pub fn toggle_modal<V, F>(
+    //         &mut self,
+    //         cx: &mut ViewContext<Self>,
+    //         add_view: F,
+    //     ) -> Option<ViewHandle<V>>
+    //     where
+    //         V: 'static + Modal,
+    //         F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
+    //     {
+    //         cx.notify();
+    //         // Whatever modal was visible is getting clobbered. If its the same type as V, then return
+    //         // it. Otherwise, create a new modal and set it as active.
+    //         if let Some(already_open_modal) = self
+    //             .dismiss_modal(cx)
+    //             .and_then(|modal| modal.downcast::<V>())
+    //         {
+    //             cx.focus_self();
+    //             Some(already_open_modal)
+    //         } else {
+    //             let modal = add_view(self, cx);
+    //             cx.subscribe(&modal, |this, _, event, cx| {
+    //                 if V::dismiss_on_event(event) {
+    //                     this.dismiss_modal(cx);
+    //                 }
+    //             })
+    //             .detach();
+    //             let previously_focused_view_id = cx.focused_view_id();
+    //             cx.focus(&modal);
+    //             self.modal = Some(ActiveModal {
+    //                 view: Box::new(modal),
+    //                 previously_focused_view_id,
+    //             });
+    //             None
+    //         }
+    //     }
+
+    //     pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
+    //         self.modal
+    //             .as_ref()
+    //             .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
+    //     }
+
+    //     pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
+    //         if let Some(modal) = self.modal.take() {
+    //             if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
+    //                 if modal.view.has_focus(cx) {
+    //                     cx.window_context().focus(Some(previously_focused_view_id));
+    //                 }
+    //             }
+    //             cx.notify();
+    //             Some(modal.view.as_any().clone())
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     pub fn items<'a>(
+    //         &'a self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
+    //         self.panes.iter().flat_map(|pane| pane.read(cx).items())
+    //     }
+
+    //     pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+    //         self.items_of_type(cx).max_by_key(|item| item.id())
+    //     }
+
+    //     pub fn items_of_type<'a, T: Item>(
+    //         &'a self,
+    //         cx: &'a AppContext,
+    //     ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
+    //         self.panes
+    //             .iter()
+    //             .flat_map(|pane| pane.read(cx).items_of_type())
+    //     }
+
+    //     pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+    //         self.active_pane().read(cx).active_item()
+    //     }
+
+    //     fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
+    //         self.active_item(cx).and_then(|item| item.project_path(cx))
+    //     }
+
+    //     pub fn save_active_item(
+    //         &mut self,
+    //         save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<()>> {
+    //         let project = self.project.clone();
+    //         let pane = self.active_pane();
+    //         let item_ix = pane.read(cx).active_item_index();
+    //         let item = pane.read(cx).active_item();
+    //         let pane = pane.downgrade();
+
+    //         cx.spawn(|_, mut cx| async move {
+    //             if let Some(item) = item {
+    //                 Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
+    //                     .await
+    //                     .map(|_| ())
+    //             } else {
+    //                 Ok(())
+    //             }
+    //         })
+    //     }
+
+    //     pub fn close_inactive_items_and_panes(
+    //         &mut self,
+    //         _: &CloseInactiveTabsAndPanes,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         self.close_all_internal(true, SaveIntent::Close, cx)
+    //     }
+
+    //     pub fn close_all_items_and_panes(
+    //         &mut self,
+    //         action: &CloseAllItemsAndPanes,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
+    //     }
+
+    //     fn close_all_internal(
+    //         &mut self,
+    //         retain_active_pane: bool,
+    //         save_intent: SaveIntent,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let current_pane = self.active_pane();
+
+    //         let mut tasks = Vec::new();
+
+    //         if retain_active_pane {
+    //             if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
+    //                 pane.close_inactive_items(&CloseInactiveItems, cx)
+    //             }) {
+    //                 tasks.push(current_pane_close);
+    //             };
+    //         }
+
+    //         for pane in self.panes() {
+    //             if retain_active_pane && pane.id() == current_pane.id() {
+    //                 continue;
+    //             }
+
+    //             if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
+    //                 pane.close_all_items(
+    //                     &CloseAllItems {
+    //                         save_intent: Some(save_intent),
+    //                     },
+    //                     cx,
+    //                 )
+    //             }) {
+    //                 tasks.push(close_pane_items)
+    //             }
+    //         }
+
+    //         if tasks.is_empty() {
+    //             None
+    //         } else {
+    //             Some(cx.spawn(|_, _| async move {
+    //                 for task in tasks {
+    //                     task.await?
+    //                 }
+    //                 Ok(())
+    //             }))
+    //         }
+    //     }
+
+    //     pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
+    //         let dock = match dock_side {
+    //             DockPosition::Left => &self.left_dock,
+    //             DockPosition::Bottom => &self.bottom_dock,
+    //             DockPosition::Right => &self.right_dock,
+    //         };
+    //         let mut focus_center = false;
+    //         let mut reveal_dock = false;
+    //         dock.update(cx, |dock, cx| {
+    //             let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
+    //             let was_visible = dock.is_open() && !other_is_zoomed;
+    //             dock.set_open(!was_visible, cx);
+
+    //             if let Some(active_panel) = dock.active_panel() {
+    //                 if was_visible {
+    //                     if active_panel.has_focus(cx) {
+    //                         focus_center = true;
+    //                     }
+    //                 } else {
+    //                     cx.focus(active_panel.as_any());
+    //                     reveal_dock = true;
+    //                 }
+    //             }
+    //         });
+
+    //         if reveal_dock {
+    //             self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
+    //         }
+
+    //         if focus_center {
+    //             cx.focus_self();
+    //         }
+
+    //         cx.notify();
+    //         self.serialize_workspace(cx);
+    //     }
+
+    //     pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
+    //         let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
+
+    //         for dock in docks {
+    //             dock.update(cx, |dock, cx| {
+    //                 dock.set_open(false, cx);
+    //             });
+    //         }
+
+    //         cx.focus_self();
+    //         cx.notify();
+    //         self.serialize_workspace(cx);
+    //     }
+
+    //     /// Transfer focus to the panel of the given type.
+    //     pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
+    //         self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
+    //             .as_any()
+    //             .clone()
+    //             .downcast()
+    //     }
+
+    //     /// Focus the panel of the given type if it isn't already focused. If it is
+    //     /// already focused, then transfer focus back to the workspace center.
+    //     pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
+    //     }
+
+    //     /// Focus or unfocus the given panel type, depending on the given callback.
+    //     fn focus_or_unfocus_panel<T: Panel>(
+    //         &mut self,
+    //         cx: &mut ViewContext<Self>,
+    //         should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
+    //     ) -> Option<Rc<dyn PanelHandle>> {
+    //         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+    //             if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
+    //                 let mut focus_center = false;
+    //                 let mut reveal_dock = false;
+    //                 let panel = dock.update(cx, |dock, cx| {
+    //                     dock.activate_panel(panel_index, cx);
+
+    //                     let panel = dock.active_panel().cloned();
+    //                     if let Some(panel) = panel.as_ref() {
+    //                         if should_focus(&**panel, cx) {
+    //                             dock.set_open(true, cx);
+    //                             cx.focus(panel.as_any());
+    //                             reveal_dock = true;
+    //                         } else {
+    //                             // if panel.is_zoomed(cx) {
+    //                             //     dock.set_open(false, cx);
+    //                             // }
+    //                             focus_center = true;
+    //                         }
+    //                     }
+    //                     panel
+    //                 });
+
+    //                 if focus_center {
+    //                     cx.focus_self();
+    //                 }
+
+    //                 self.serialize_workspace(cx);
+    //                 cx.notify();
+    //                 return panel;
+    //             }
+    //         }
+    //         None
+    //     }
+
+    //     pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
+    //         for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+    //             let dock = dock.read(cx);
+    //             if let Some(panel) = dock.panel::<T>() {
+    //                 return Some(panel);
+    //             }
+    //         }
+    //         None
+    //     }
+
+    //     fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
+    //         for pane in &self.panes {
+    //             pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+    //         }
+
+    //         self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+    //         self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+    //         self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+    //         self.zoomed = None;
+    //         self.zoomed_position = None;
+
+    //         cx.notify();
+    //     }
+
+    //     #[cfg(any(test, feature = "test-support"))]
+    //     pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
+    //         self.zoomed.and_then(|view| view.upgrade(cx))
+    //     }
+
+    //     fn dismiss_zoomed_items_to_reveal(
+    //         &mut self,
+    //         dock_to_reveal: Option<DockPosition>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         // If a center pane is zoomed, unzoom it.
+    //         for pane in &self.panes {
+    //             if pane != &self.active_pane || dock_to_reveal.is_some() {
+    //                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+    //             }
+    //         }
+
+    //         // If another dock is zoomed, hide it.
+    //         let mut focus_center = false;
+    //         for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
+    //             dock.update(cx, |dock, cx| {
+    //                 if Some(dock.position()) != dock_to_reveal {
+    //                     if let Some(panel) = dock.active_panel() {
+    //                         if panel.is_zoomed(cx) {
+    //                             focus_center |= panel.has_focus(cx);
+    //                             dock.set_open(false, cx);
+    //                         }
+    //                     }
+    //                 }
+    //             });
+    //         }
+
+    //         if focus_center {
+    //             cx.focus_self();
+    //         }
+
+    //         if self.zoomed_position != dock_to_reveal {
+    //             self.zoomed = None;
+    //             self.zoomed_position = None;
+    //         }
+
+    //         cx.notify();
+    //     }
+
+    //     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
+    //         let pane = cx.add_view(|cx| {
+    //             Pane::new(
+    //                 self.weak_handle(),
+    //                 self.project.clone(),
+    //                 self.pane_history_timestamp.clone(),
+    //                 cx,
+    //             )
+    //         });
+    //         cx.subscribe(&pane, Self::handle_pane_event).detach();
+    //         self.panes.push(pane.clone());
+    //         cx.focus(&pane);
+    //         cx.emit(Event::PaneAdded(pane.clone()));
+    //         pane
+    //     }
+
+    //     pub fn add_item_to_center(
+    //         &mut self,
+    //         item: Box<dyn ItemHandle>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> bool {
+    //         if let Some(center_pane) = self.last_active_center_pane.clone() {
+    //             if let Some(center_pane) = center_pane.upgrade(cx) {
+    //                 center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
+    //                 true
+    //             } else {
+    //                 false
+    //             }
+    //         } else {
+    //             false
+    //         }
+    //     }
+
+    //     pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+    //         self.active_pane
+    //             .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
+    //     }
+
+    //     pub fn split_item(
+    //         &mut self,
+    //         split_direction: SplitDirection,
+    //         item: Box<dyn ItemHandle>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
+    //         new_pane.update(cx, move |new_pane, cx| {
+    //             new_pane.add_item(item, true, true, None, cx)
+    //         })
+    //     }
+
+    //     pub fn open_abs_path(
+    //         &mut self,
+    //         abs_path: PathBuf,
+    //         visible: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+    //         cx.spawn(|workspace, mut cx| async move {
+    //             let open_paths_task_result = workspace
+    //                 .update(&mut cx, |workspace, cx| {
+    //                     workspace.open_paths(vec![abs_path.clone()], visible, cx)
+    //                 })
+    //                 .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
+    //                 .await;
+    //             anyhow::ensure!(
+    //                 open_paths_task_result.len() == 1,
+    //                 "open abs path {abs_path:?} task returned incorrect number of results"
+    //             );
+    //             match open_paths_task_result
+    //                 .into_iter()
+    //                 .next()
+    //                 .expect("ensured single task result")
+    //             {
+    //                 Some(open_result) => {
+    //                     open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
+    //                 }
+    //                 None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
+    //             }
+    //         })
+    //     }
+
+    //     pub fn split_abs_path(
+    //         &mut self,
+    //         abs_path: PathBuf,
+    //         visible: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+    //         let project_path_task =
+    //             Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
+    //         cx.spawn(|this, mut cx| async move {
+    //             let (_, path) = project_path_task.await?;
+    //             this.update(&mut cx, |this, cx| this.split_path(path, cx))?
+    //                 .await
+    //         })
+    //     }
+
+    pub fn open_path(
+        &mut self,
+        path: impl Into<ProjectPath>,
+        pane: Option<WeakView<Pane>>,
+        focus_item: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
+        let pane = pane.unwrap_or_else(|| {
+            self.last_active_center_pane.clone().unwrap_or_else(|| {
+                self.panes
+                    .first()
+                    .expect("There must be an active pane")
+                    .downgrade()
+            })
+        });
+
+        let task = self.load_path(path.into(), cx);
+        cx.spawn(|_, mut cx| async move {
+            let (project_entry_id, build_item) = task.await?;
+            pane.update(&mut cx, |pane, cx| {
+                pane.open_item(project_entry_id, focus_item, cx, build_item)
+            })
+        })
+    }
+
+    //     pub fn split_path(
+    //         &mut self,
+    //         path: impl Into<ProjectPath>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
+    //         let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
+    //             self.panes
+    //                 .first()
+    //                 .expect("There must be an active pane")
+    //                 .downgrade()
+    //         });
+
+    //         if let Member::Pane(center_pane) = &self.center.root {
+    //             if center_pane.read(cx).items_len() == 0 {
+    //                 return self.open_path(path, Some(pane), true, cx);
+    //             }
+    //         }
+
+    //         let task = self.load_path(path.into(), cx);
+    //         cx.spawn(|this, mut cx| async move {
+    //             let (project_entry_id, build_item) = task.await?;
+    //             this.update(&mut cx, move |this, cx| -> Option<_> {
+    //                 let pane = pane.upgrade(cx)?;
+    //                 let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
+    //                 new_pane.update(cx, |new_pane, cx| {
+    //                     Some(new_pane.open_item(project_entry_id, true, cx, build_item))
+    //                 })
+    //             })
+    //             .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
+    //         })
+    //     }
+
+    pub(crate) fn load_path(
+        &mut self,
+        path: ProjectPath,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<
+        Result<(
+            ProjectEntryId,
+            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
+        )>,
+    > {
+        let project = self.project().clone();
+        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
+        cx.spawn(|_, mut cx| async move {
+            let (project_entry_id, project_item) = project_item.await?;
+            let build_item = cx.update(|cx| {
+                cx.default_global::<ProjectItemBuilders>()
+                    .get(&project_item.model_type())
+                    .ok_or_else(|| anyhow!("no item builder for project item"))
+                    .cloned()
+            })?;
+            let build_item =
+                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
+            Ok((project_entry_id, build_item))
+        })
+    }
+
+    //     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 _;
+
+    //         let entry_id = project_item.read(cx).entry_id(cx);
+    //         if let Some(item) = entry_id
+    //             .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
+    //             .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 split_project_item<T>(
+    //         &mut self,
+    //         project_item: ModelHandle<T::Item>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> ViewHandle<T>
+    //     where
+    //         T: ProjectItem,
+    //     {
+    //         use project::Item as _;
+
+    //         let entry_id = project_item.read(cx).entry_id(cx);
+    //         if let Some(item) = entry_id
+    //             .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
+    //             .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.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
+    //         item
+    //     }
+
+    //     pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+    //         if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
+    //             self.active_pane.update(cx, |pane, cx| {
+    //                 pane.add_item(Box::new(shared_screen), false, true, None, cx)
+    //             });
+    //         }
+    //     }
+
+    //     pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
+    //         let result = self.panes.iter().find_map(|pane| {
+    //             pane.read(cx)
+    //                 .index_for_item(item)
+    //                 .map(|ix| (pane.clone(), ix))
+    //         });
+    //         if let Some((pane, ix)) = result {
+    //             pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
+    //             true
+    //         } else {
+    //             false
+    //         }
+    //     }
+
+    //     fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
+    //         let panes = self.center.panes();
+    //         if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
+    //             cx.focus(&pane);
+    //         } else {
+    //             self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
+    //         }
+    //     }
+
+    //     pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
+    //         let panes = self.center.panes();
+    //         if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
+    //             let next_ix = (ix + 1) % panes.len();
+    //             let next_pane = panes[next_ix].clone();
+    //             cx.focus(&next_pane);
+    //         }
+    //     }
+
+    //     pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
+    //         let panes = self.center.panes();
+    //         if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
+    //             let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
+    //             let prev_pane = panes[prev_ix].clone();
+    //             cx.focus(&prev_pane);
+    //         }
+    //     }
+
+    //     pub fn activate_pane_in_direction(
+    //         &mut self,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         if let Some(pane) = self.find_pane_in_direction(direction, cx) {
+    //             cx.focus(pane);
+    //         }
+    //     }
+
+    //     pub fn swap_pane_in_direction(
+    //         &mut self,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         if let Some(to) = self
+    //             .find_pane_in_direction(direction, cx)
+    //             .map(|pane| pane.clone())
+    //         {
+    //             self.center.swap(&self.active_pane.clone(), &to);
+    //             cx.notify();
+    //         }
+    //     }
+
+    //     fn find_pane_in_direction(
+    //         &mut self,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<&ViewHandle<Pane>> {
+    //         let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
+    //             return None;
+    //         };
+    //         let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
+    //         let center = match cursor {
+    //             Some(cursor) if bounding_box.contains_point(cursor) => cursor,
+    //             _ => bounding_box.center(),
+    //         };
+
+    //         let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
+
+    //         let target = match direction {
+    //             SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
+    //             SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
+    //             SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
+    //             SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
+    //         };
+    //         self.center.pane_at_pixel_position(target)
+    //     }
+
+    //     fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+    //         if self.active_pane != pane {
+    //             self.active_pane = pane.clone();
+    //             self.status_bar.update(cx, |status_bar, cx| {
+    //                 status_bar.set_active_pane(&self.active_pane, cx);
+    //             });
+    //             self.active_item_path_changed(cx);
+    //             self.last_active_center_pane = Some(pane.downgrade());
+    //         }
+
+    //         self.dismiss_zoomed_items_to_reveal(None, cx);
+    //         if pane.read(cx).is_zoomed() {
+    //             self.zoomed = Some(pane.downgrade().into_any());
+    //         } else {
+    //             self.zoomed = None;
+    //         }
+    //         self.zoomed_position = None;
+    //         self.update_active_view_for_followers(cx);
+
+    //         cx.notify();
+    //     }
+
+    //     fn handle_pane_event(
+    //         &mut self,
+    //         pane: ViewHandle<Pane>,
+    //         event: &pane::Event,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         match event {
+    //             pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
+    //             pane::Event::Split(direction) => {
+    //                 self.split_and_clone(pane, *direction, cx);
+    //             }
+    //             pane::Event::Remove => self.remove_pane(pane, cx),
+    //             pane::Event::ActivateItem { local } => {
+    //                 if *local {
+    //                     self.unfollow(&pane, cx);
+    //                 }
+    //                 if &pane == self.active_pane() {
+    //                     self.active_item_path_changed(cx);
+    //                 }
+    //             }
+    //             pane::Event::ChangeItemTitle => {
+    //                 if pane == self.active_pane {
+    //                     self.active_item_path_changed(cx);
+    //                 }
+    //                 self.update_window_edited(cx);
+    //             }
+    //             pane::Event::RemoveItem { item_id } => {
+    //                 self.update_window_edited(cx);
+    //                 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
+    //                     if entry.get().id() == pane.id() {
+    //                         entry.remove();
+    //                     }
+    //                 }
+    //             }
+    //             pane::Event::Focus => {
+    //                 self.handle_pane_focused(pane.clone(), cx);
+    //             }
+    //             pane::Event::ZoomIn => {
+    //                 if pane == self.active_pane {
+    //                     pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
+    //                     if pane.read(cx).has_focus() {
+    //                         self.zoomed = Some(pane.downgrade().into_any());
+    //                         self.zoomed_position = None;
+    //                     }
+    //                     cx.notify();
+    //                 }
+    //             }
+    //             pane::Event::ZoomOut => {
+    //                 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+    //                 if self.zoomed_position.is_none() {
+    //                     self.zoomed = None;
+    //                 }
+    //                 cx.notify();
+    //             }
+    //         }
+
+    //         self.serialize_workspace(cx);
+    //     }
+
+    //     pub fn split_pane(
+    //         &mut self,
+    //         pane_to_split: ViewHandle<Pane>,
+    //         split_direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> ViewHandle<Pane> {
+    //         let new_pane = self.add_pane(cx);
+    //         self.center
+    //             .split(&pane_to_split, &new_pane, split_direction)
+    //             .unwrap();
+    //         cx.notify();
+    //         new_pane
+    //     }
+
+    //     pub fn split_and_clone(
+    //         &mut self,
+    //         pane: ViewHandle<Pane>,
+    //         direction: SplitDirection,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<ViewHandle<Pane>> {
+    //         let item = pane.read(cx).active_item()?;
+    //         let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
+    //             let new_pane = self.add_pane(cx);
+    //             new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
+    //             self.center.split(&pane, &new_pane, direction).unwrap();
+    //             Some(new_pane)
+    //         } else {
+    //             None
+    //         };
+    //         cx.notify();
+    //         maybe_pane_handle
+    //     }
+
+    //     pub fn split_pane_with_item(
+    //         &mut self,
+    //         pane_to_split: WeakViewHandle<Pane>,
+    //         split_direction: SplitDirection,
+    //         from: WeakViewHandle<Pane>,
+    //         item_id_to_move: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
+    //             return;
+    //         };
+    //         let Some(from) = from.upgrade(cx) else {
+    //             return;
+    //         };
+
+    //         let new_pane = self.add_pane(cx);
+    //         self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
+    //         self.center
+    //             .split(&pane_to_split, &new_pane, split_direction)
+    //             .unwrap();
+    //         cx.notify();
+    //     }
+
+    //     pub fn split_pane_with_project_entry(
+    //         &mut self,
+    //         pane_to_split: WeakViewHandle<Pane>,
+    //         split_direction: SplitDirection,
+    //         project_entry: ProjectEntryId,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let pane_to_split = pane_to_split.upgrade(cx)?;
+    //         let new_pane = self.add_pane(cx);
+    //         self.center
+    //             .split(&pane_to_split, &new_pane, split_direction)
+    //             .unwrap();
+
+    //         let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
+    //         let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
+    //         Some(cx.foreground().spawn(async move {
+    //             task.await?;
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn move_item(
+    //         &mut self,
+    //         source: ViewHandle<Pane>,
+    //         destination: ViewHandle<Pane>,
+    //         item_id_to_move: usize,
+    //         destination_index: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let item_to_move = source
+    //             .read(cx)
+    //             .items()
+    //             .enumerate()
+    //             .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
+
+    //         if item_to_move.is_none() {
+    //             log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
+    //             return;
+    //         }
+    //         let (item_ix, item_handle) = item_to_move.unwrap();
+    //         let item_handle = item_handle.clone();
+
+    //         if source != destination {
+    //             // Close item from previous pane
+    //             source.update(cx, |source, cx| {
+    //                 source.remove_item(item_ix, false, cx);
+    //             });
+    //         }
+
+    //         // This automatically removes duplicate items in the pane
+    //         destination.update(cx, |destination, cx| {
+    //             destination.add_item(item_handle, true, true, Some(destination_index), cx);
+    //             cx.focus_self();
+    //         });
+    //     }
+
+    //     fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+    //         if self.center.remove(&pane).unwrap() {
+    //             self.force_remove_pane(&pane, cx);
+    //             self.unfollow(&pane, cx);
+    //             self.last_leaders_by_pane.remove(&pane.downgrade());
+    //             for removed_item in pane.read(cx).items() {
+    //                 self.panes_by_item.remove(&removed_item.id());
+    //             }
+
+    //             cx.notify();
+    //         } else {
+    //             self.active_item_path_changed(cx);
+    //         }
+    //     }
+
+    //     pub fn panes(&self) -> &[ViewHandle<Pane>] {
+    //         &self.panes
+    //     }
+
+    //     pub fn active_pane(&self) -> &ViewHandle<Pane> {
+    //         &self.active_pane
+    //     }
+
+    //     fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+    //         self.follower_states.retain(|_, state| {
+    //             if state.leader_id == peer_id {
+    //                 for item in state.items_by_leader_view_id.values() {
+    //                     item.set_leader_peer_id(None, cx);
+    //                 }
+    //                 false
+    //             } else {
+    //                 true
+    //             }
+    //         });
+    //         cx.notify();
+    //     }
+
+    //     fn start_following(
+    //         &mut self,
+    //         leader_id: PeerId,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let pane = self.active_pane().clone();
+
+    //         self.last_leaders_by_pane
+    //             .insert(pane.downgrade(), leader_id);
+    //         self.unfollow(&pane, cx);
+    //         self.follower_states.insert(
+    //             pane.clone(),
+    //             FollowerState {
+    //                 leader_id,
+    //                 active_view_id: None,
+    //                 items_by_leader_view_id: Default::default(),
+    //             },
+    //         );
+    //         cx.notify();
+
+    //         let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+    //         let project_id = self.project.read(cx).remote_id();
+    //         let request = self.app_state.client.request(proto::Follow {
+    //             room_id,
+    //             project_id,
+    //             leader_id: Some(leader_id),
+    //         });
+
+    //         Some(cx.spawn(|this, mut cx| async move {
+    //             let response = request.await?;
+    //             this.update(&mut cx, |this, _| {
+    //                 let state = this
+    //                     .follower_states
+    //                     .get_mut(&pane)
+    //                     .ok_or_else(|| anyhow!("following interrupted"))?;
+    //                 state.active_view_id = if let Some(active_view_id) = response.active_view_id {
+    //                     Some(ViewId::from_proto(active_view_id)?)
+    //                 } else {
+    //                     None
+    //                 };
+    //                 Ok::<_, anyhow::Error>(())
+    //             })??;
+    //             Self::add_views_from_leader(
+    //                 this.clone(),
+    //                 leader_id,
+    //                 vec![pane],
+    //                 response.views,
+    //                 &mut cx,
+    //             )
+    //             .await?;
+    //             this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
+    //             Ok(())
+    //         }))
+    //     }
+
+    //     pub fn follow_next_collaborator(
+    //         &mut self,
+    //         _: &FollowNextCollaborator,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let collaborators = self.project.read(cx).collaborators();
+    //         let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
+    //             let mut collaborators = collaborators.keys().copied();
+    //             for peer_id in collaborators.by_ref() {
+    //                 if peer_id == leader_id {
+    //                     break;
+    //                 }
+    //             }
+    //             collaborators.next()
+    //         } else if let Some(last_leader_id) =
+    //             self.last_leaders_by_pane.get(&self.active_pane.downgrade())
+    //         {
+    //             if collaborators.contains_key(last_leader_id) {
+    //                 Some(*last_leader_id)
+    //             } else {
+    //                 None
+    //             }
+    //         } else {
+    //             None
+    //         };
+
+    //         let pane = self.active_pane.clone();
+    //         let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
+    //         else {
+    //             return None;
+    //         };
+    //         if Some(leader_id) == self.unfollow(&pane, cx) {
+    //             return None;
+    //         }
+    //         self.follow(leader_id, cx)
+    //     }
+
+    //     pub fn follow(
+    //         &mut self,
+    //         leader_id: PeerId,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<Task<Result<()>>> {
+    //         let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
+    //         let project = self.project.read(cx);
+
+    //         let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
+    //             return None;
+    //         };
+
+    //         let other_project_id = match remote_participant.location {
+    //             call::ParticipantLocation::External => None,
+    //             call::ParticipantLocation::UnsharedProject => None,
+    //             call::ParticipantLocation::SharedProject { project_id } => {
+    //                 if Some(project_id) == project.remote_id() {
+    //                     None
+    //                 } else {
+    //                     Some(project_id)
+    //                 }
+    //             }
+    //         };
+
+    //         // if they are active in another project, follow there.
+    //         if let Some(project_id) = other_project_id {
+    //             let app_state = self.app_state.clone();
+    //             return Some(crate::join_remote_project(
+    //                 project_id,
+    //                 remote_participant.user.id,
+    //                 app_state,
+    //                 cx,
+    //             ));
+    //         }
+
+    //         // if you're already following, find the right pane and focus it.
+    //         for (pane, state) in &self.follower_states {
+    //             if leader_id == state.leader_id {
+    //                 cx.focus(pane);
+    //                 return None;
+    //             }
+    //         }
+
+    //         // Otherwise, follow.
+    //         self.start_following(leader_id, cx)
+    //     }
+
+    //     pub fn unfollow(
+    //         &mut self,
+    //         pane: &ViewHandle<Pane>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<PeerId> {
+    //         let state = self.follower_states.remove(pane)?;
+    //         let leader_id = state.leader_id;
+    //         for (_, item) in state.items_by_leader_view_id {
+    //             item.set_leader_peer_id(None, cx);
+    //         }
+
+    //         if self
+    //             .follower_states
+    //             .values()
+    //             .all(|state| state.leader_id != state.leader_id)
+    //         {
+    //             let project_id = self.project.read(cx).remote_id();
+    //             let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+    //             self.app_state
+    //                 .client
+    //                 .send(proto::Unfollow {
+    //                     room_id,
+    //                     project_id,
+    //                     leader_id: Some(leader_id),
+    //                 })
+    //                 .log_err();
+    //         }
+
+    //         cx.notify();
+    //         Some(leader_id)
+    //     }
+
+    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
+    //         self.follower_states
+    //             .values()
+    //             .any(|state| state.leader_id == peer_id)
+    //     }
+
+    //     fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+    //         // TODO: There should be a better system in place for this
+    //         // (https://github.com/zed-industries/zed/issues/1290)
+    //         let is_fullscreen = cx.window_is_fullscreen();
+    //         let container_theme = if is_fullscreen {
+    //             let mut container_theme = theme.titlebar.container;
+    //             container_theme.padding.left = container_theme.padding.right;
+    //             container_theme
+    //         } else {
+    //             theme.titlebar.container
+    //         };
+
+    //         enum TitleBar {}
+    //         MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
+    //             Stack::new()
+    //                 .with_children(
+    //                     self.titlebar_item
+    //                         .as_ref()
+    //                         .map(|item| ChildView::new(item, cx)),
+    //                 )
+    //                 .contained()
+    //                 .with_style(container_theme)
+    //         })
+    //         .on_click(MouseButton::Left, |event, _, cx| {
+    //             if event.click_count == 2 {
+    //                 cx.zoom_window();
+    //             }
+    //         })
+    //         .constrained()
+    //         .with_height(theme.titlebar.height)
+    //         .into_any_named("titlebar")
+    //     }
+
+    //     fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
+    //         let active_entry = self.active_project_path(cx);
+    //         self.project
+    //             .update(cx, |project, cx| project.set_active_path(active_entry, cx));
+    //         self.update_window_title(cx);
+    //     }
+
+    //     fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
+    //         let project = self.project().read(cx);
+    //         let mut title = String::new();
+
+    //         if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
+    //             let filename = path
+    //                 .path
+    //                 .file_name()
+    //                 .map(|s| s.to_string_lossy())
+    //                 .or_else(|| {
+    //                     Some(Cow::Borrowed(
+    //                         project
+    //                             .worktree_for_id(path.worktree_id, cx)?
+    //                             .read(cx)
+    //                             .root_name(),
+    //                     ))
+    //                 });
+
+    //             if let Some(filename) = filename {
+    //                 title.push_str(filename.as_ref());
+    //                 title.push_str(" — ");
+    //             }
+    //         }
+
+    //         for (i, name) in project.worktree_root_names(cx).enumerate() {
+    //             if i > 0 {
+    //                 title.push_str(", ");
+    //             }
+    //             title.push_str(name);
+    //         }
+
+    //         if title.is_empty() {
+    //             title = "empty project".to_string();
+    //         }
+
+    //         if project.is_remote() {
+    //             title.push_str(" ↙");
+    //         } else if project.is_shared() {
+    //             title.push_str(" ↗");
+    //         }
+
+    //         cx.set_window_title(&title);
+    //     }
+
+    //     fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
+    //         let is_edited = !self.project.read(cx).is_read_only()
+    //             && self
+    //                 .items(cx)
+    //                 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
+    //         if is_edited != self.window_edited {
+    //             self.window_edited = is_edited;
+    //             cx.set_window_edited(self.window_edited)
+    //         }
+    //     }
+
+    //     fn render_disconnected_overlay(
+    //         &self,
+    //         cx: &mut ViewContext<Workspace>,
+    //     ) -> Option<AnyElement<Workspace>> {
+    //         if self.project.read(cx).is_read_only() {
+    //             enum DisconnectedOverlay {}
+    //             Some(
+    //                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
+    //                     let theme = &theme::current(cx);
+    //                     Label::new(
+    //                         "Your connection to the remote project has been lost.",
+    //                         theme.workspace.disconnected_overlay.text.clone(),
+    //                     )
+    //                     .aligned()
+    //                     .contained()
+    //                     .with_style(theme.workspace.disconnected_overlay.container)
+    //                 })
+    //                 .with_cursor_style(CursorStyle::Arrow)
+    //                 .capture_all()
+    //                 .into_any_named("disconnected overlay"),
+    //             )
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     fn render_notifications(
+    //         &self,
+    //         theme: &theme::Workspace,
+    //         cx: &AppContext,
+    //     ) -> Option<AnyElement<Workspace>> {
+    //         if self.notifications.is_empty() {
+    //             None
+    //         } else {
+    //             Some(
+    //                 Flex::column()
+    //                     .with_children(self.notifications.iter().map(|(_, _, notification)| {
+    //                         ChildView::new(notification.as_any(), cx)
+    //                             .contained()
+    //                             .with_style(theme.notification)
+    //                     }))
+    //                     .constrained()
+    //                     .with_width(theme.notifications.width)
+    //                     .contained()
+    //                     .with_style(theme.notifications.container)
+    //                     .aligned()
+    //                     .bottom()
+    //                     .right()
+    //                     .into_any(),
+    //             )
+    //         }
+    //     }
+
+    //     // RPC handlers
+
+    //     fn handle_follow(
+    //         &mut self,
+    //         follower_project_id: Option<u64>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> proto::FollowResponse {
+    //         let client = &self.app_state.client;
+    //         let project_id = self.project.read(cx).remote_id();
+
+    //         let active_view_id = self.active_item(cx).and_then(|i| {
+    //             Some(
+    //                 i.to_followable_item_handle(cx)?
+    //                     .remote_id(client, cx)?
+    //                     .to_proto(),
+    //             )
+    //         });
+
+    //         cx.notify();
+
+    //         self.last_active_view_id = active_view_id.clone();
+    //         proto::FollowResponse {
+    //             active_view_id,
+    //             views: self
+    //                 .panes()
+    //                 .iter()
+    //                 .flat_map(|pane| {
+    //                     let leader_id = self.leader_for_pane(pane);
+    //                     pane.read(cx).items().filter_map({
+    //                         let cx = &cx;
+    //                         move |item| {
+    //                             let item = item.to_followable_item_handle(cx)?;
+    //                             if (project_id.is_none() || project_id != follower_project_id)
+    //                                 && item.is_project_item(cx)
+    //                             {
+    //                                 return None;
+    //                             }
+    //                             let id = item.remote_id(client, cx)?.to_proto();
+    //                             let variant = item.to_state_proto(cx)?;
+    //                             Some(proto::View {
+    //                                 id: Some(id),
+    //                                 leader_id,
+    //                                 variant: Some(variant),
+    //                             })
+    //                         }
+    //                     })
+    //                 })
+    //                 .collect(),
+    //         }
+    //     }
+
+    //     fn handle_update_followers(
+    //         &mut self,
+    //         leader_id: PeerId,
+    //         message: proto::UpdateFollowers,
+    //         _cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.leader_updates_tx
+    //             .unbounded_send((leader_id, message))
+    //             .ok();
+    //     }
+
+    //     async fn process_leader_update(
+    //         this: &WeakViewHandle<Self>,
+    //         leader_id: PeerId,
+    //         update: proto::UpdateFollowers,
+    //         cx: &mut AsyncAppContext,
+    //     ) -> Result<()> {
+    //         match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
+    //             proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
+    //                 this.update(cx, |this, _| {
+    //                     for (_, state) in &mut this.follower_states {
+    //                         if state.leader_id == leader_id {
+    //                             state.active_view_id =
+    //                                 if let Some(active_view_id) = update_active_view.id.clone() {
+    //                                     Some(ViewId::from_proto(active_view_id)?)
+    //                                 } else {
+    //                                     None
+    //                                 };
+    //                         }
+    //                     }
+    //                     anyhow::Ok(())
+    //                 })??;
+    //             }
+    //             proto::update_followers::Variant::UpdateView(update_view) => {
+    //                 let variant = update_view
+    //                     .variant
+    //                     .ok_or_else(|| anyhow!("missing update view variant"))?;
+    //                 let id = update_view
+    //                     .id
+    //                     .ok_or_else(|| anyhow!("missing update view id"))?;
+    //                 let mut tasks = Vec::new();
+    //                 this.update(cx, |this, cx| {
+    //                     let project = this.project.clone();
+    //                     for (_, state) in &mut this.follower_states {
+    //                         if state.leader_id == leader_id {
+    //                             let view_id = ViewId::from_proto(id.clone())?;
+    //                             if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
+    //                                 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
+    //                             }
+    //                         }
+    //                     }
+    //                     anyhow::Ok(())
+    //                 })??;
+    //                 try_join_all(tasks).await.log_err();
+    //             }
+    //             proto::update_followers::Variant::CreateView(view) => {
+    //                 let panes = this.read_with(cx, |this, _| {
+    //                     this.follower_states
+    //                         .iter()
+    //                         .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
+    //                         .cloned()
+    //                         .collect()
+    //                 })?;
+    //                 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
+    //             }
+    //         }
+    //         this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
+    //         Ok(())
+    //     }
+
+    //     async fn add_views_from_leader(
+    //         this: WeakViewHandle<Self>,
+    //         leader_id: PeerId,
+    //         panes: Vec<ViewHandle<Pane>>,
+    //         views: Vec<proto::View>,
+    //         cx: &mut AsyncAppContext,
+    //     ) -> Result<()> {
+    //         let this = this
+    //             .upgrade(cx)
+    //             .ok_or_else(|| anyhow!("workspace dropped"))?;
+
+    //         let item_builders = cx.update(|cx| {
+    //             cx.default_global::<FollowableItemBuilders>()
+    //                 .values()
+    //                 .map(|b| b.0)
+    //                 .collect::<Vec<_>>()
+    //         });
+
+    //         let mut item_tasks_by_pane = HashMap::default();
+    //         for pane in panes {
+    //             let mut item_tasks = Vec::new();
+    //             let mut leader_view_ids = Vec::new();
+    //             for view in &views {
+    //                 let Some(id) = &view.id else { continue };
+    //                 let id = ViewId::from_proto(id.clone())?;
+    //                 let mut variant = view.variant.clone();
+    //                 if variant.is_none() {
+    //                     Err(anyhow!("missing view variant"))?;
+    //                 }
+    //                 for build_item in &item_builders {
+    //                     let task = cx
+    //                         .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
+    //                     if let Some(task) = task {
+    //                         item_tasks.push(task);
+    //                         leader_view_ids.push(id);
+    //                         break;
+    //                     } else {
+    //                         assert!(variant.is_some());
+    //                     }
+    //                 }
+    //             }
+
+    //             item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
+    //         }
+
+    //         for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
+    //             let items = futures::future::try_join_all(item_tasks).await?;
+    //             this.update(cx, |this, cx| {
+    //                 let state = this.follower_states.get_mut(&pane)?;
+    //                 for (id, item) in leader_view_ids.into_iter().zip(items) {
+    //                     item.set_leader_peer_id(Some(leader_id), cx);
+    //                     state.items_by_leader_view_id.insert(id, item);
+    //                 }
+
+    //                 Some(())
+    //             });
+    //         }
+    //         Ok(())
+    //     }
+
+    //     fn update_active_view_for_followers(&mut self, cx: &AppContext) {
+    //         let mut is_project_item = true;
+    //         let mut update = proto::UpdateActiveView::default();
+    //         if self.active_pane.read(cx).has_focus() {
+    //             let item = self
+    //                 .active_item(cx)
+    //                 .and_then(|item| item.to_followable_item_handle(cx));
+    //             if let Some(item) = item {
+    //                 is_project_item = item.is_project_item(cx);
+    //                 update = proto::UpdateActiveView {
+    //                     id: item
+    //                         .remote_id(&self.app_state.client, cx)
+    //                         .map(|id| id.to_proto()),
+    //                     leader_id: self.leader_for_pane(&self.active_pane),
+    //                 };
+    //             }
+    //         }
+
+    //         if update.id != self.last_active_view_id {
+    //             self.last_active_view_id = update.id.clone();
+    //             self.update_followers(
+    //                 is_project_item,
+    //                 proto::update_followers::Variant::UpdateActiveView(update),
+    //                 cx,
+    //             );
+    //         }
+    //     }
+
+    //     fn update_followers(
+    //         &self,
+    //         project_only: bool,
+    //         update: proto::update_followers::Variant,
+    //         cx: &AppContext,
+    //     ) -> Option<()> {
+    //         let project_id = if project_only {
+    //             self.project.read(cx).remote_id()
+    //         } else {
+    //             None
+    //         };
+    //         self.app_state().workspace_store.read_with(cx, |store, cx| {
+    //             store.update_followers(project_id, update, cx)
+    //         })
+    //     }
+
+    //     pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
+    //         self.follower_states.get(pane).map(|state| state.leader_id)
+    //     }
+
+    //     fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
+    //         cx.notify();
+
+    //         let call = self.active_call()?;
+    //         let room = call.read(cx).room()?.read(cx);
+    //         let participant = room.remote_participant_for_peer_id(leader_id)?;
+    //         let mut items_to_activate = Vec::new();
+
+    //         let leader_in_this_app;
+    //         let leader_in_this_project;
+    //         match participant.location {
+    //             call::ParticipantLocation::SharedProject { project_id } => {
+    //                 leader_in_this_app = true;
+    //                 leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
+    //             }
+    //             call::ParticipantLocation::UnsharedProject => {
+    //                 leader_in_this_app = true;
+    //                 leader_in_this_project = false;
+    //             }
+    //             call::ParticipantLocation::External => {
+    //                 leader_in_this_app = false;
+    //                 leader_in_this_project = false;
+    //             }
+    //         };
+
+    //         for (pane, state) in &self.follower_states {
+    //             if state.leader_id != leader_id {
+    //                 continue;
+    //             }
+    //             if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
+    //                 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
+    //                     if leader_in_this_project || !item.is_project_item(cx) {
+    //                         items_to_activate.push((pane.clone(), item.boxed_clone()));
+    //                     }
+    //                 } else {
+    //                     log::warn!(
+    //                         "unknown view id {:?} for leader {:?}",
+    //                         active_view_id,
+    //                         leader_id
+    //                     );
+    //                 }
+    //                 continue;
+    //             }
+    //             if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
+    //                 items_to_activate.push((pane.clone(), Box::new(shared_screen)));
+    //             }
+    //         }
+
+    //         for (pane, item) in items_to_activate {
+    //             let pane_was_focused = pane.read(cx).has_focus();
+    //             if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
+    //                 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
+    //             } else {
+    //                 pane.update(cx, |pane, cx| {
+    //                     pane.add_item(item.boxed_clone(), false, false, None, cx)
+    //                 });
+    //             }
+
+    //             if pane_was_focused {
+    //                 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
+    //             }
+    //         }
+
+    //         None
+    //     }
+
+    //     fn shared_screen_for_peer(
+    //         &self,
+    //         peer_id: PeerId,
+    //         pane: &ViewHandle<Pane>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> Option<ViewHandle<SharedScreen>> {
+    //         let call = self.active_call()?;
+    //         let room = call.read(cx).room()?.read(cx);
+    //         let participant = room.remote_participant_for_peer_id(peer_id)?;
+    //         let track = participant.video_tracks.values().next()?.clone();
+    //         let user = participant.user.clone();
+
+    //         for item in pane.read(cx).items_of_type::<SharedScreen>() {
+    //             if item.read(cx).peer_id == peer_id {
+    //                 return Some(item);
+    //             }
+    //         }
+
+    //         Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
+    //     }
+
+    //     pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
+    //         if active {
+    //             self.update_active_view_for_followers(cx);
+    //             cx.background()
+    //                 .spawn(persistence::DB.update_timestamp(self.database_id()))
+    //                 .detach();
+    //         } else {
+    //             for pane in &self.panes {
+    //                 pane.update(cx, |pane, cx| {
+    //                     if let Some(item) = pane.active_item() {
+    //                         item.workspace_deactivated(cx);
+    //                     }
+    //                     if matches!(
+    //                         settings::get::<WorkspaceSettings>(cx).autosave,
+    //                         AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
+    //                     ) {
+    //                         for item in pane.items() {
+    //                             Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
+    //                                 .detach_and_log_err(cx);
+    //                         }
+    //                     }
+    //                 });
+    //             }
+    //         }
+    //     }
+
+    //     fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
+    //         self.active_call.as_ref().map(|(call, _)| call)
+    //     }
+
+    //     fn on_active_call_event(
+    //         &mut self,
+    //         _: ModelHandle<ActiveCall>,
+    //         event: &call::room::Event,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         match event {
+    //             call::room::Event::ParticipantLocationChanged { participant_id }
+    //             | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
+    //                 self.leader_updated(*participant_id, cx);
+    //             }
+    //             _ => {}
+    //         }
+    //     }
+
+    //     pub fn database_id(&self) -> WorkspaceId {
+    //         self.database_id
+    //     }
+
+    //     fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
+    //         let project = self.project().read(cx);
+
+    //         if project.is_local() {
+    //             Some(
+    //                 project
+    //                     .visible_worktrees(cx)
+    //                     .map(|worktree| worktree.read(cx).abs_path())
+    //                     .collect::<Vec<_>>()
+    //                     .into(),
+    //             )
+    //         } else {
+    //             None
+    //         }
+    //     }
+
+    //     fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
+    //         match member {
+    //             Member::Axis(PaneAxis { members, .. }) => {
+    //                 for child in members.iter() {
+    //                     self.remove_panes(child.clone(), cx)
+    //                 }
+    //             }
+    //             Member::Pane(pane) => {
+    //                 self.force_remove_pane(&pane, cx);
+    //             }
+    //         }
+    //     }
+
+    //     fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
+    //         self.panes.retain(|p| p != pane);
+    //         cx.focus(self.panes.last().unwrap());
+    //         if self.last_active_center_pane == Some(pane.downgrade()) {
+    //             self.last_active_center_pane = None;
+    //         }
+    //         cx.notify();
+    //     }
+
+    //     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
+    //         self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
+    //             cx.background().timer(Duration::from_millis(100)).await;
+    //             this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
+    //                 .ok();
+    //         }));
+    //     }
+
+    //     fn serialize_workspace(&self, cx: &ViewContext<Self>) {
+    //         fn serialize_pane_handle(
+    //             pane_handle: &ViewHandle<Pane>,
+    //             cx: &AppContext,
+    //         ) -> SerializedPane {
+    //             let (items, active) = {
+    //                 let pane = pane_handle.read(cx);
+    //                 let active_item_id = pane.active_item().map(|item| item.id());
+    //                 (
+    //                     pane.items()
+    //                         .filter_map(|item_handle| {
+    //                             Some(SerializedItem {
+    //                                 kind: Arc::from(item_handle.serialized_item_kind()?),
+    //                                 item_id: item_handle.id(),
+    //                                 active: Some(item_handle.id()) == active_item_id,
+    //                             })
+    //                         })
+    //                         .collect::<Vec<_>>(),
+    //                     pane.has_focus(),
+    //                 )
+    //             };
+
+    //             SerializedPane::new(items, active)
+    //         }
+
+    //         fn build_serialized_pane_group(
+    //             pane_group: &Member,
+    //             cx: &AppContext,
+    //         ) -> SerializedPaneGroup {
+    //             match pane_group {
+    //                 Member::Axis(PaneAxis {
+    //                     axis,
+    //                     members,
+    //                     flexes,
+    //                     bounding_boxes: _,
+    //                 }) => SerializedPaneGroup::Group {
+    //                     axis: *axis,
+    //                     children: members
+    //                         .iter()
+    //                         .map(|member| build_serialized_pane_group(member, cx))
+    //                         .collect::<Vec<_>>(),
+    //                     flexes: Some(flexes.borrow().clone()),
+    //                 },
+    //                 Member::Pane(pane_handle) => {
+    //                     SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
+    //                 }
+    //             }
+    //         }
+
+    //         fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
+    //             let left_dock = this.left_dock.read(cx);
+    //             let left_visible = left_dock.is_open();
+    //             let left_active_panel = left_dock.visible_panel().and_then(|panel| {
+    //                 Some(
+    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
+    //                         .to_string(),
+    //                 )
+    //             });
+    //             let left_dock_zoom = left_dock
+    //                 .visible_panel()
+    //                 .map(|panel| panel.is_zoomed(cx))
+    //                 .unwrap_or(false);
+
+    //             let right_dock = this.right_dock.read(cx);
+    //             let right_visible = right_dock.is_open();
+    //             let right_active_panel = right_dock.visible_panel().and_then(|panel| {
+    //                 Some(
+    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
+    //                         .to_string(),
+    //                 )
+    //             });
+    //             let right_dock_zoom = right_dock
+    //                 .visible_panel()
+    //                 .map(|panel| panel.is_zoomed(cx))
+    //                 .unwrap_or(false);
+
+    //             let bottom_dock = this.bottom_dock.read(cx);
+    //             let bottom_visible = bottom_dock.is_open();
+    //             let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
+    //                 Some(
+    //                     cx.view_ui_name(panel.as_any().window(), panel.id())?
+    //                         .to_string(),
+    //                 )
+    //             });
+    //             let bottom_dock_zoom = bottom_dock
+    //                 .visible_panel()
+    //                 .map(|panel| panel.is_zoomed(cx))
+    //                 .unwrap_or(false);
+
+    //             DockStructure {
+    //                 left: DockData {
+    //                     visible: left_visible,
+    //                     active_panel: left_active_panel,
+    //                     zoom: left_dock_zoom,
+    //                 },
+    //                 right: DockData {
+    //                     visible: right_visible,
+    //                     active_panel: right_active_panel,
+    //                     zoom: right_dock_zoom,
+    //                 },
+    //                 bottom: DockData {
+    //                     visible: bottom_visible,
+    //                     active_panel: bottom_active_panel,
+    //                     zoom: bottom_dock_zoom,
+    //                 },
+    //             }
+    //         }
+
+    //         if let Some(location) = self.location(cx) {
+    //             // Load bearing special case:
+    //             //  - with_local_workspace() relies on this to not have other stuff open
+    //             //    when you open your log
+    //             if !location.paths().is_empty() {
+    //                 let center_group = build_serialized_pane_group(&self.center.root, cx);
+    //                 let docks = build_serialized_docks(self, cx);
+
+    //                 let serialized_workspace = SerializedWorkspace {
+    //                     id: self.database_id,
+    //                     location,
+    //                     center_group,
+    //                     bounds: Default::default(),
+    //                     display: Default::default(),
+    //                     docks,
+    //                 };
+
+    //                 cx.background()
+    //                     .spawn(persistence::DB.save_workspace(serialized_workspace))
+    //                     .detach();
+    //             }
+    //         }
+    //     }
+
+    //     pub(crate) fn load_workspace(
+    //         workspace: WeakViewHandle<Workspace>,
+    //         serialized_workspace: SerializedWorkspace,
+    //         paths_to_open: Vec<Option<ProjectPath>>,
+    //         cx: &mut AppContext,
+    //     ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
+    //         cx.spawn(|mut cx| async move {
+    //             let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
+    //                 (
+    //                     workspace.project().clone(),
+    //                     workspace.last_active_center_pane.clone(),
+    //                 )
+    //             })?;
+
+    //             let mut center_group = None;
+    //             let mut center_items = None;
+    //             // Traverse the splits tree and add to things
+    //             if let Some((group, active_pane, items)) = serialized_workspace
+    //                 .center_group
+    //                 .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
+    //                 .await
+    //             {
+    //                 center_items = Some(items);
+    //                 center_group = Some((group, active_pane))
+    //             }
+
+    //             let mut items_by_project_path = cx.read(|cx| {
+    //                 center_items
+    //                     .unwrap_or_default()
+    //                     .into_iter()
+    //                     .filter_map(|item| {
+    //                         let item = item?;
+    //                         let project_path = item.project_path(cx)?;
+    //                         Some((project_path, item))
+    //                     })
+    //                     .collect::<HashMap<_, _>>()
+    //             });
+
+    //             let opened_items = paths_to_open
+    //                 .into_iter()
+    //                 .map(|path_to_open| {
+    //                     path_to_open
+    //                         .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
+    //                 })
+    //                 .collect::<Vec<_>>();
+
+    //             // Remove old panes from workspace panes list
+    //             workspace.update(&mut cx, |workspace, cx| {
+    //                 if let Some((center_group, active_pane)) = center_group {
+    //                     workspace.remove_panes(workspace.center.root.clone(), cx);
+
+    //                     // Swap workspace center group
+    //                     workspace.center = PaneGroup::with_root(center_group);
+
+    //                     // Change the focus to the workspace first so that we retrigger focus in on the pane.
+    //                     cx.focus_self();
+
+    //                     if let Some(active_pane) = active_pane {
+    //                         cx.focus(&active_pane);
+    //                     } else {
+    //                         cx.focus(workspace.panes.last().unwrap());
+    //                     }
+    //                 } else {
+    //                     let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
+    //                     if let Some(old_center_handle) = old_center_handle {
+    //                         cx.focus(&old_center_handle)
+    //                     } else {
+    //                         cx.focus_self()
+    //                     }
+    //                 }
+
+    //                 let docks = serialized_workspace.docks;
+    //                 workspace.left_dock.update(cx, |dock, cx| {
+    //                     dock.set_open(docks.left.visible, cx);
+    //                     if let Some(active_panel) = docks.left.active_panel {
+    //                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+    //                             dock.activate_panel(ix, cx);
+    //                         }
+    //                     }
+    //                     dock.active_panel()
+    //                         .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
+    //                     if docks.left.visible && docks.left.zoom {
+    //                         cx.focus_self()
+    //                     }
+    //                 });
+    //                 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
+    //                 workspace.right_dock.update(cx, |dock, cx| {
+    //                     dock.set_open(docks.right.visible, cx);
+    //                     if let Some(active_panel) = docks.right.active_panel {
+    //                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+    //                             dock.activate_panel(ix, cx);
+    //                         }
+    //                     }
+    //                     dock.active_panel()
+    //                         .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
+
+    //                     if docks.right.visible && docks.right.zoom {
+    //                         cx.focus_self()
+    //                     }
+    //                 });
+    //                 workspace.bottom_dock.update(cx, |dock, cx| {
+    //                     dock.set_open(docks.bottom.visible, cx);
+    //                     if let Some(active_panel) = docks.bottom.active_panel {
+    //                         if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+    //                             dock.activate_panel(ix, cx);
+    //                         }
+    //                     }
+
+    //                     dock.active_panel()
+    //                         .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
+
+    //                     if docks.bottom.visible && docks.bottom.zoom {
+    //                         cx.focus_self()
+    //                     }
+    //                 });
+
+    //                 cx.notify();
+    //             })?;
+
+    //             // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
+    //             workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
+
+    //             Ok(opened_items)
+    //         })
+    //     }
+
+    //     #[cfg(any(test, feature = "test-support"))]
+    //     pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+    //         use node_runtime::FakeNodeRuntime;
+
+    //         let client = project.read(cx).client();
+    //         let user_store = project.read(cx).user_store();
+
+    //         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+    //         let app_state = Arc::new(AppState {
+    //             languages: project.read(cx).languages().clone(),
+    //             workspace_store,
+    //             client,
+    //             user_store,
+    //             fs: project.read(cx).fs().clone(),
+    //             build_window_options: |_, _, _| Default::default(),
+    //             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+    //             node_runtime: FakeNodeRuntime::new(),
+    //         });
+    //         Self::new(0, project, app_state, cx)
+    //     }
+
+    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
+    //         let dock = match position {
+    //             DockPosition::Left => &self.left_dock,
+    //             DockPosition::Right => &self.right_dock,
+    //             DockPosition::Bottom => &self.bottom_dock,
+    //         };
+    //         let active_panel = dock.read(cx).visible_panel()?;
+    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
+    //             dock.read(cx).render_placeholder(cx)
+    //         } else {
+    //             ChildView::new(dock, cx).into_any()
+    //         };
+
+    //         Some(
+    //             element
+    //                 .constrained()
+    //                 .dynamically(move |constraint, _, cx| match position {
+    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
+    //                         Vector2F::new(20., constraint.min.y()),
+    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
+    //                     ),
+    //                     DockPosition::Bottom => SizeConstraint::new(
+    //                         Vector2F::new(constraint.min.x(), 20.),
+    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
+    //                     ),
+    //                 })
+    //                 .into_any(),
+    //         )
+    //     }
+    // }
+
+    // fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
+    //     ZED_WINDOW_POSITION
+    //         .zip(*ZED_WINDOW_SIZE)
+    //         .map(|(position, size)| {
+    //             WindowBounds::Fixed(RectF::new(
+    //                 cx.platform().screens()[0].bounds().origin() + position,
+    //                 size,
+    //             ))
+    //         })
+    // }
+
+    // async fn open_items(
+    //     serialized_workspace: Option<SerializedWorkspace>,
+    //     workspace: &WeakViewHandle<Workspace>,
+    //     mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
+    //     app_state: Arc<AppState>,
+    //     mut cx: AsyncAppContext,
+    // ) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
+    //     let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
+
+    //     if let Some(serialized_workspace) = serialized_workspace {
+    //         let workspace = workspace.clone();
+    //         let restored_items = cx
+    //             .update(|cx| {
+    //                 Workspace::load_workspace(
+    //                     workspace,
+    //                     serialized_workspace,
+    //                     project_paths_to_open
+    //                         .iter()
+    //                         .map(|(_, project_path)| project_path)
+    //                         .cloned()
+    //                         .collect(),
+    //                     cx,
+    //                 )
+    //             })
+    //             .await?;
+
+    //         let restored_project_paths = cx.read(|cx| {
+    //             restored_items
+    //                 .iter()
+    //                 .filter_map(|item| item.as_ref()?.project_path(cx))
+    //                 .collect::<HashSet<_>>()
+    //         });
+
+    //         for restored_item in restored_items {
+    //             opened_items.push(restored_item.map(Ok));
+    //         }
+
+    //         project_paths_to_open
+    //             .iter_mut()
+    //             .for_each(|(_, project_path)| {
+    //                 if let Some(project_path_to_open) = project_path {
+    //                     if restored_project_paths.contains(project_path_to_open) {
+    //                         *project_path = None;
+    //                     }
+    //                 }
+    //             });
+    //     } else {
+    //         for _ in 0..project_paths_to_open.len() {
+    //             opened_items.push(None);
+    //         }
+    //     }
+    //     assert!(opened_items.len() == project_paths_to_open.len());
+
+    //     let tasks =
+    //         project_paths_to_open
+    //             .into_iter()
+    //             .enumerate()
+    //             .map(|(i, (abs_path, project_path))| {
+    //                 let workspace = workspace.clone();
+    //                 cx.spawn(|mut cx| {
+    //                     let fs = app_state.fs.clone();
+    //                     async move {
+    //                         let file_project_path = project_path?;
+    //                         if fs.is_file(&abs_path).await {
+    //                             Some((
+    //                                 i,
+    //                                 workspace
+    //                                     .update(&mut cx, |workspace, cx| {
+    //                                         workspace.open_path(file_project_path, None, true, cx)
+    //                                     })
+    //                                     .log_err()?
+    //                                     .await,
+    //                             ))
+    //                         } else {
+    //                             None
+    //                         }
+    //                     }
+    //                 })
+    //             });
+
+    //     for maybe_opened_path in futures::future::join_all(tasks.into_iter())
+    //         .await
+    //         .into_iter()
+    //     {
+    //         if let Some((i, path_open_result)) = maybe_opened_path {
+    //             opened_items[i] = Some(path_open_result);
+    //         }
+    //     }
+
+    //     Ok(opened_items)
+    // }
+
+    // fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
+    //     const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
+    //     const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
+    //     const MESSAGE_ID: usize = 2;
+
+    //     if workspace
+    //         .read_with(cx, |workspace, cx| {
+    //             workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
+    //         })
+    //         .unwrap_or(false)
+    //     {
+    //         return;
+    //     }
+
+    //     if db::kvp::KEY_VALUE_STORE
+    //         .read_kvp(NEW_DOCK_HINT_KEY)
+    //         .ok()
+    //         .flatten()
+    //         .is_some()
+    //     {
+    //         if !workspace
+    //             .read_with(cx, |workspace, cx| {
+    //                 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
+    //             })
+    //             .unwrap_or(false)
+    //         {
+    //             cx.update(|cx| {
+    //                 cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
+    //                     let entry = tracker
+    //                         .entry(TypeId::of::<MessageNotification>())
+    //                         .or_default();
+    //                     if !entry.contains(&MESSAGE_ID) {
+    //                         entry.push(MESSAGE_ID);
+    //                     }
+    //                 });
+    //             });
+    //         }
+
+    //         return;
+    //     }
+
+    //     cx.spawn(|_| async move {
+    //         db::kvp::KEY_VALUE_STORE
+    //             .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
+    //             .await
+    //             .ok();
+    //     })
+    //     .detach();
+
+    //     workspace
+    //         .update(cx, |workspace, cx| {
+    //             workspace.show_notification_once(2, cx, |cx| {
+    //                 cx.add_view(|_| {
+    //                     MessageNotification::new_element(|text, _| {
+    //                         Text::new(
+    //                             "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
+    //                             text,
+    //                         )
+    //                         .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
+    //                             let code_span_background_color = settings::get::<ThemeSettings>(cx)
+    //                                 .theme
+    //                                 .editor
+    //                                 .document_highlight_read_background;
+
+    //                             cx.scene().push_quad(gpui::Quad {
+    //                                 bounds,
+    //                                 background: Some(code_span_background_color),
+    //                                 border: Default::default(),
+    //                                 corner_radii: (2.0).into(),
+    //                             })
+    //                         })
+    //                         .into_any()
+    //                     })
+    //                     .with_click_message("Read more about the new panel system")
+    //                     .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
+    //                 })
+    //             })
+    //         })
+    //         .ok();
+}
+
 // fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
 //     const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";