Restore auto-save on focus change, re-enable workspace tests

Max Brunsfeld created

Change summary

crates/gpui2/src/app/test_context.rs        |   37 
crates/gpui2/src/platform.rs                |    5 
crates/gpui2/src/platform/test/platform.rs  |    8 
crates/gpui2/src/platform/test/window.rs    |   16 
crates/workspace2/Cargo.toml                |   42 
crates/workspace2/src/item.rs               |  640 ++++----
crates/workspace2/src/pane.rs               |    4 
crates/workspace2/src/pane_group.rs         |    4 
crates/workspace2/src/persistence.rs        |    4 
crates/workspace2/src/persistence/model.rs  |    4 
crates/workspace2/src/searchable.rs         |    2 
crates/workspace2/src/workspace2.rs         | 1776 +++++++++++-----------
crates/workspace2/src/workspace_settings.rs |    2 
13 files changed, 1,295 insertions(+), 1,249 deletions(-)

Detailed changes

crates/gpui2/src/app/test_context.rs πŸ”—

@@ -2,8 +2,8 @@ use crate::{
     div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
     BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
     KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
-    TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
-    WindowOptions,
+    TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
@@ -509,6 +509,39 @@ impl<'a> VisualTestContext<'a> {
     pub fn simulate_input(&mut self, input: &str) {
         self.cx.simulate_input(self.window, input)
     }
+
+    pub fn simulate_activation(&mut self) {
+        self.simulate_window_events(&mut |handlers| {
+            handlers
+                .active_status_change
+                .iter_mut()
+                .for_each(|f| f(true));
+        })
+    }
+
+    pub fn simulate_deactivation(&mut self) {
+        self.simulate_window_events(&mut |handlers| {
+            handlers
+                .active_status_change
+                .iter_mut()
+                .for_each(|f| f(false));
+        })
+    }
+
+    fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
+        let handlers = self
+            .cx
+            .update_window(self.window, |_, cx| {
+                cx.window
+                    .platform_window
+                    .as_test()
+                    .unwrap()
+                    .handlers
+                    .clone()
+            })
+            .unwrap();
+        f(&mut *handlers.lock());
+    }
 }
 
 impl<'a> Context for VisualTestContext<'a> {

crates/gpui2/src/platform.rs πŸ”—

@@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow {
     fn draw(&self, scene: Scene);
 
     fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+    #[cfg(any(test, feature = "test-support"))]
+    fn as_test(&self) -> Option<&TestWindow> {
+        None
+    }
 }
 
 pub trait PlatformDispatcher: Send + Sync {

crates/gpui2/src/platform/test/platform.rs πŸ”—

@@ -189,13 +189,9 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {}
 
-    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {}
 
     fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
 

crates/gpui2/src/platform/test/window.rs πŸ”—

@@ -11,11 +11,11 @@ use std::{
 };
 
 #[derive(Default)]
-struct Handlers {
-    active_status_change: Vec<Box<dyn FnMut(bool)>>,
-    input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
-    moved: Vec<Box<dyn FnMut()>>,
-    resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
+pub(crate) struct TestWindowHandlers {
+    pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
+    pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
+    pub(crate) moved: Vec<Box<dyn FnMut()>>,
+    pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
 }
 
 pub struct TestWindow {
@@ -23,7 +23,7 @@ pub struct TestWindow {
     current_scene: Mutex<Option<Scene>>,
     display: Rc<dyn PlatformDisplay>,
     pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
-    handlers: Mutex<Handlers>,
+    pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
     platform: Weak<TestPlatform>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
 }
@@ -167,6 +167,10 @@ impl PlatformWindow for TestWindow {
     fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
         self.sprite_atlas.clone()
     }
+
+    fn as_test(&self) -> Option<&TestWindow> {
+        Some(self)
+    }
 }
 
 pub struct TestAtlasState {

crates/workspace2/Cargo.toml πŸ”—

@@ -10,29 +10,29 @@ doctest = false
 
 [features]
 test-support = [
-    "call2/test-support",
-    "client2/test-support",
-    "project2/test-support",
-    "settings2/test-support",
+    "call/test-support",
+    "client/test-support",
+    "project/test-support",
+    "settings/test-support",
     "gpui/test-support",
-    "fs2/test-support"
+    "fs/test-support"
 ]
 
 [dependencies]
-db2 = { path = "../db2" }
-client2 = { path = "../client2" }
+db = { path = "../db2", package = "db2" }
+client = { path = "../client2", package = "client2" }
 collections = { path = "../collections" }
 # context_menu = { path = "../context_menu" }
-fs2 = { path = "../fs2" }
+fs = { path = "../fs2", package = "fs2" }
 gpui = { package = "gpui2", path = "../gpui2" }
-install_cli2 = { path = "../install_cli2" }
-language2 = { path = "../language2" }
+install_cli = { path = "../install_cli2", package = "install_cli2" }
+language = { path = "../language2", package = "language2" }
 #menu = { path = "../menu" }
 node_runtime = { path = "../node_runtime" }
-project2 = { path = "../project2" }
-settings2 = { path = "../settings2" }
-terminal2 = { path = "../terminal2" }
-theme2 = { path = "../theme2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+terminal = { path = "../terminal2", package = "terminal2" }
+theme = { path = "../theme2", package = "theme2" }
 util = { path = "../util" }
 ui = { package = "ui2", path = "../ui2" }
 
@@ -54,13 +54,13 @@ smallvec.workspace = true
 uuid.workspace = true
 
 [dev-dependencies]
-call2 = { path = "../call2", features = ["test-support"] }
-client2 = { path = "../client2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-project2 = { path = "../project2", features = ["test-support"] }
-settings2 = { path = "../settings2", features = ["test-support"] }
-fs2 = { path = "../fs2", features = ["test-support"] }
-db2 = { path = "../db2", features = ["test-support"] }
+call = { path = "../call2", package = "call2", features = ["test-support"] }
+client = { path = "../client2", package = "client2", features = ["test-support"] }
+gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+project = { path = "../project2", package = "project2", features = ["test-support"] }
+settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
+fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
+db = { path = "../db2", package = "db2", features = ["test-support"] }
 
 indoc.workspace = true
 env_logger.workspace = true

crates/workspace2/src/item.rs πŸ”—

@@ -7,7 +7,7 @@ use crate::{
     ViewId, Workspace, WorkspaceId,
 };
 use anyhow::Result;
-use client2::{
+use client::{
     proto::{self, PeerId},
     Client,
 };
@@ -16,10 +16,10 @@ use gpui::{
     HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
     WindowContext,
 };
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -33,7 +33,7 @@ use std::{
     },
     time::Duration,
 };
-use theme2::Theme;
+use theme::Theme;
 
 #[derive(Deserialize)]
 pub struct ItemSettings {
@@ -110,7 +110,7 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
     fn for_each_project_item(
         &self,
         _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
     ) {
     }
     fn is_singleton(&self, _cx: &AppContext) -> bool {
@@ -222,7 +222,7 @@ pub trait ItemHandle: 'static + Send {
     fn for_each_project_item(
         &self,
         _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
     );
     fn is_singleton(&self, cx: &AppContext) -> bool;
     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
@@ -347,7 +347,7 @@ impl<T: Item> ItemHandle for View<T> {
     fn for_each_project_item(
         &self,
         cx: &AppContext,
-        f: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        f: &mut dyn FnMut(EntityId, &dyn project::Item),
     ) {
         self.read(cx).for_each_project_item(cx, f)
     }
@@ -375,6 +375,7 @@ impl<T: Item> ItemHandle for View<T> {
         pane: View<Pane>,
         cx: &mut ViewContext<Workspace>,
     ) {
+        let weak_item = self.downgrade();
         let history = pane.read(cx).nav_history_for_item(self);
         self.update(cx, |this, cx| {
             this.set_nav_history(history, cx);
@@ -491,16 +492,15 @@ impl<T: Item> ItemHandle for View<T> {
                     }
                 }));
 
-            // todo!()
-            // cx.observe_focus(self, move |workspace, item, focused, cx| {
-            //     if !focused
-            //         && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
-            //     {
-            //         Pane::autosave_item(&item, workspace.project.clone(), cx)
-            //             .detach_and_log_err(cx);
-            //     }
-            // })
-            // .detach();
+            cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
+                if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
+                    if let Some(item) = weak_item.upgrade() {
+                        Pane::autosave_item(&item, workspace.project.clone(), cx)
+                            .detach_and_log_err(cx);
+                    }
+                }
+            })
+            .detach();
 
             let item_id = self.item_id();
             cx.observe_release(self, move |workspace, _, _| {
@@ -640,7 +640,7 @@ impl<T: Item> WeakItemHandle for WeakView<T> {
 }
 
 pub trait ProjectItem: Item {
-    type Item: project2::Item;
+    type Item: project::Item;
 
     fn for_project_item(
         project: Model<Project>,
@@ -759,300 +759,310 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
     }
 }
 
-// #[cfg(any(test, feature = "test-support"))]
-// pub mod test {
-//     use super::{Item, ItemEvent};
-//     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
-//     use gpui::{
-//         elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
-//         ViewContext, View, WeakViewHandle,
-//     };
-//     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
-//     use smallvec::SmallVec;
-//     use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
-
-//     pub struct TestProjectItem {
-//         pub entry_id: Option<ProjectEntryId>,
-//         pub project_path: Option<ProjectPath>,
-//     }
-
-//     pub struct TestItem {
-//         pub workspace_id: WorkspaceId,
-//         pub state: String,
-//         pub label: String,
-//         pub save_count: usize,
-//         pub save_as_count: usize,
-//         pub reload_count: usize,
-//         pub is_dirty: bool,
-//         pub is_singleton: bool,
-//         pub has_conflict: bool,
-//         pub project_items: Vec<Model<TestProjectItem>>,
-//         pub nav_history: Option<ItemNavHistory>,
-//         pub tab_descriptions: Option<Vec<&'static str>>,
-//         pub tab_detail: Cell<Option<usize>>,
-//     }
-
-//     impl Entity for TestProjectItem {
-//         type Event = ();
-//     }
-
-//     impl project2::Item for TestProjectItem {
-//         fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-//             self.entry_id
-//         }
-
-//         fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
-//             self.project_path.clone()
-//         }
-//     }
-
-//     pub enum TestItemEvent {
-//         Edit,
-//     }
-
-//     impl Clone for TestItem {
-//         fn clone(&self) -> Self {
-//             Self {
-//                 state: self.state.clone(),
-//                 label: self.label.clone(),
-//                 save_count: self.save_count,
-//                 save_as_count: self.save_as_count,
-//                 reload_count: self.reload_count,
-//                 is_dirty: self.is_dirty,
-//                 is_singleton: self.is_singleton,
-//                 has_conflict: self.has_conflict,
-//                 project_items: self.project_items.clone(),
-//                 nav_history: None,
-//                 tab_descriptions: None,
-//                 tab_detail: Default::default(),
-//                 workspace_id: self.workspace_id,
-//             }
-//         }
-//     }
-
-//     impl TestProjectItem {
-//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
-//             let entry_id = Some(ProjectEntryId::from_proto(id));
-//             let project_path = Some(ProjectPath {
-//                 worktree_id: WorktreeId::from_usize(0),
-//                 path: Path::new(path).into(),
-//             });
-//             cx.add_model(|_| Self {
-//                 entry_id,
-//                 project_path,
-//             })
-//         }
-
-//         pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
-//             cx.add_model(|_| Self {
-//                 project_path: None,
-//                 entry_id: None,
-//             })
-//         }
-//     }
-
-//     impl TestItem {
-//         pub fn new() -> Self {
-//             Self {
-//                 state: String::new(),
-//                 label: String::new(),
-//                 save_count: 0,
-//                 save_as_count: 0,
-//                 reload_count: 0,
-//                 is_dirty: false,
-//                 has_conflict: false,
-//                 project_items: Vec::new(),
-//                 is_singleton: true,
-//                 nav_history: None,
-//                 tab_descriptions: None,
-//                 tab_detail: Default::default(),
-//                 workspace_id: 0,
-//             }
-//         }
-
-//         pub fn new_deserialized(id: WorkspaceId) -> Self {
-//             let mut this = Self::new();
-//             this.workspace_id = id;
-//             this
-//         }
-
-//         pub fn with_label(mut self, state: &str) -> Self {
-//             self.label = state.to_string();
-//             self
-//         }
-
-//         pub fn with_singleton(mut self, singleton: bool) -> Self {
-//             self.is_singleton = singleton;
-//             self
-//         }
-
-//         pub fn with_dirty(mut self, dirty: bool) -> Self {
-//             self.is_dirty = dirty;
-//             self
-//         }
-
-//         pub fn with_conflict(mut self, has_conflict: bool) -> Self {
-//             self.has_conflict = has_conflict;
-//             self
-//         }
-
-//         pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
-//             self.project_items.clear();
-//             self.project_items.extend(items.iter().cloned());
-//             self
-//         }
-
-//         pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
-//             self.push_to_nav_history(cx);
-//             self.state = state;
-//         }
-
-//         fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
-//             if let Some(history) = &mut self.nav_history {
-//                 history.push(Some(Box::new(self.state.clone())), cx);
-//             }
-//         }
-//     }
-
-//     impl Entity for TestItem {
-//         type Event = TestItemEvent;
-//     }
-
-//     impl View for TestItem {
-//         fn ui_name() -> &'static str {
-//             "TestItem"
-//         }
-
-//         fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-//             Empty::new().into_any()
-//         }
-//     }
-
-//     impl Item for TestItem {
-//         fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
-//             self.tab_descriptions.as_ref().and_then(|descriptions| {
-//                 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
-//                 Some(description.into())
-//             })
-//         }
-
-//         fn tab_content<V: 'static>(
-//             &self,
-//             detail: Option<usize>,
-//             _: &theme2::Tab,
-//             _: &AppContext,
-//         ) -> AnyElement<V> {
-//             self.tab_detail.set(detail);
-//             Empty::new().into_any()
-//         }
-
-//         fn for_each_project_item(
-//             &self,
-//             cx: &AppContext,
-//             f: &mut dyn FnMut(usize, &dyn project2::Item),
-//         ) {
-//             self.project_items
-//                 .iter()
-//                 .for_each(|item| f(item.id(), item.read(cx)))
-//         }
-
-// 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);
-//         }
-
-//         fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-//             let state = *state.downcast::<String>().unwrap_or_default();
-//             if state != self.state {
-//                 self.state = state;
-//                 true
-//             } else {
-//                 false
-//             }
-//         }
-
-//         fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-//             self.push_to_nav_history(cx);
-//         }
-
-//         fn clone_on_split(
-//             &self,
-//             _workspace_id: WorkspaceId,
-//             _: &mut ViewContext<Self>,
-//         ) -> Option<Self>
-//         where
-//             Self: Sized,
-//         {
-//             Some(self.clone())
-//         }
-
-//         fn is_dirty(&self, _: &AppContext) -> bool {
-//             self.is_dirty
-//         }
-
-//         fn has_conflict(&self, _: &AppContext) -> bool {
-//             self.has_conflict
-//         }
-
-//         fn can_save(&self, cx: &AppContext) -> bool {
-//             !self.project_items.is_empty()
-//                 && self
-//                     .project_items
-//                     .iter()
-//                     .all(|item| item.read(cx).entry_id.is_some())
-//         }
-
-//         fn save(
-//             &mut self,
-//             _: Model<Project>,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.save_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn save_as(
-//             &mut self,
-//             _: Model<Project>,
-//             _: std::path::PathBuf,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.save_as_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn reload(
-//             &mut self,
-//             _: Model<Project>,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.reload_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-//             [ItemEvent::UpdateTab, ItemEvent::Edit].into()
-//         }
-
-//         fn serialized_item_kind() -> Option<&'static str> {
-//             Some("TestItem")
-//         }
-
-//         fn deserialize(
-//             _project: Model<Project>,
-//             _workspace: WeakViewHandle<Workspace>,
-//             workspace_id: WorkspaceId,
-//             _item_id: ItemId,
-//             cx: &mut ViewContext<Pane>,
-//         ) -> Task<anyhow::Result<View<Self>>> {
-//             let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
-//             Task::Ready(Some(anyhow::Ok(view)))
-//         }
-//     }
-// }
+#[cfg(any(test, feature = "test-support"))]
+pub mod test {
+    use super::{Item, ItemEvent};
+    use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
+    use gpui::{
+        AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView,
+        IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView,
+    };
+    use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
+    use std::{any::Any, cell::Cell, path::Path};
+
+    pub struct TestProjectItem {
+        pub entry_id: Option<ProjectEntryId>,
+        pub project_path: Option<ProjectPath>,
+    }
+
+    pub struct TestItem {
+        pub workspace_id: WorkspaceId,
+        pub state: String,
+        pub label: String,
+        pub save_count: usize,
+        pub save_as_count: usize,
+        pub reload_count: usize,
+        pub is_dirty: bool,
+        pub is_singleton: bool,
+        pub has_conflict: bool,
+        pub project_items: Vec<Model<TestProjectItem>>,
+        pub nav_history: Option<ItemNavHistory>,
+        pub tab_descriptions: Option<Vec<&'static str>>,
+        pub tab_detail: Cell<Option<usize>>,
+        focus_handle: gpui::FocusHandle,
+    }
+
+    impl project::Item for TestProjectItem {
+        fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
+            self.entry_id
+        }
+
+        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+            self.project_path.clone()
+        }
+    }
+
+    pub enum TestItemEvent {
+        Edit,
+    }
+
+    // impl Clone for TestItem {
+    //     fn clone(&self) -> Self {
+    //         Self {
+    //             state: self.state.clone(),
+    //             label: self.label.clone(),
+    //             save_count: self.save_count,
+    //             save_as_count: self.save_as_count,
+    //             reload_count: self.reload_count,
+    //             is_dirty: self.is_dirty,
+    //             is_singleton: self.is_singleton,
+    //             has_conflict: self.has_conflict,
+    //             project_items: self.project_items.clone(),
+    //             nav_history: None,
+    //             tab_descriptions: None,
+    //             tab_detail: Default::default(),
+    //             workspace_id: self.workspace_id,
+    //             focus_handle: self.focus_handle.clone(),
+    //         }
+    //     }
+    // }
+
+    impl TestProjectItem {
+        pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
+            let entry_id = Some(ProjectEntryId::from_proto(id));
+            let project_path = Some(ProjectPath {
+                worktree_id: WorktreeId::from_usize(0),
+                path: Path::new(path).into(),
+            });
+            cx.build_model(|_| Self {
+                entry_id,
+                project_path,
+            })
+        }
+
+        pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
+            cx.build_model(|_| Self {
+                project_path: None,
+                entry_id: None,
+            })
+        }
+    }
+
+    impl TestItem {
+        pub fn new(cx: &mut ViewContext<Self>) -> Self {
+            Self {
+                state: String::new(),
+                label: String::new(),
+                save_count: 0,
+                save_as_count: 0,
+                reload_count: 0,
+                is_dirty: false,
+                has_conflict: false,
+                project_items: Vec::new(),
+                is_singleton: true,
+                nav_history: None,
+                tab_descriptions: None,
+                tab_detail: Default::default(),
+                workspace_id: 0,
+                focus_handle: cx.focus_handle(),
+            }
+        }
+
+        pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
+            let mut this = Self::new(cx);
+            this.workspace_id = id;
+            this
+        }
+
+        pub fn with_label(mut self, state: &str) -> Self {
+            self.label = state.to_string();
+            self
+        }
+
+        pub fn with_singleton(mut self, singleton: bool) -> Self {
+            self.is_singleton = singleton;
+            self
+        }
+
+        pub fn with_dirty(mut self, dirty: bool) -> Self {
+            self.is_dirty = dirty;
+            self
+        }
+
+        pub fn with_conflict(mut self, has_conflict: bool) -> Self {
+            self.has_conflict = has_conflict;
+            self
+        }
+
+        pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
+            self.project_items.clear();
+            self.project_items.extend(items.iter().cloned());
+            self
+        }
+
+        pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+            self.push_to_nav_history(cx);
+            self.state = state;
+        }
+
+        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+            if let Some(history) = &mut self.nav_history {
+                history.push(Some(Box::new(self.state.clone())), cx);
+            }
+        }
+    }
+
+    impl Render for TestItem {
+        type Element = Div;
+
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+            gpui::div()
+        }
+    }
+
+    impl EventEmitter<ItemEvent> for TestItem {}
+
+    impl FocusableView for TestItem {
+        fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+            self.focus_handle.clone()
+        }
+    }
+
+    impl Item for TestItem {
+        fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
+            self.tab_descriptions.as_ref().and_then(|descriptions| {
+                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+                Some(description.into())
+            })
+        }
+
+        fn tab_content(
+            &self,
+            detail: Option<usize>,
+            cx: &ui::prelude::WindowContext,
+        ) -> AnyElement {
+            self.tab_detail.set(detail);
+            gpui::div().into_any_element()
+        }
+
+        fn for_each_project_item(
+            &self,
+            cx: &AppContext,
+            f: &mut dyn FnMut(EntityId, &dyn project::Item),
+        ) {
+            self.project_items
+                .iter()
+                .for_each(|item| f(item.entity_id(), item.read(cx)))
+        }
+
+        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);
+        }
+
+        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+            let state = *state.downcast::<String>().unwrap_or_default();
+            if state != self.state {
+                self.state = state;
+                true
+            } else {
+                false
+            }
+        }
+
+        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+            self.push_to_nav_history(cx);
+        }
+
+        fn clone_on_split(
+            &self,
+            _workspace_id: WorkspaceId,
+            cx: &mut ViewContext<Self>,
+        ) -> Option<View<Self>>
+        where
+            Self: Sized,
+        {
+            Some(cx.build_view(|cx| Self {
+                state: self.state.clone(),
+                label: self.label.clone(),
+                save_count: self.save_count,
+                save_as_count: self.save_as_count,
+                reload_count: self.reload_count,
+                is_dirty: self.is_dirty,
+                is_singleton: self.is_singleton,
+                has_conflict: self.has_conflict,
+                project_items: self.project_items.clone(),
+                nav_history: None,
+                tab_descriptions: None,
+                tab_detail: Default::default(),
+                workspace_id: self.workspace_id,
+                focus_handle: cx.focus_handle(),
+            }))
+        }
+
+        fn is_dirty(&self, _: &AppContext) -> bool {
+            self.is_dirty
+        }
+
+        fn has_conflict(&self, _: &AppContext) -> bool {
+            self.has_conflict
+        }
+
+        fn can_save(&self, cx: &AppContext) -> bool {
+            !self.project_items.is_empty()
+                && self
+                    .project_items
+                    .iter()
+                    .all(|item| item.read(cx).entry_id.is_some())
+        }
+
+        fn save(
+            &mut self,
+            _: Model<Project>,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.save_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn save_as(
+            &mut self,
+            _: Model<Project>,
+            _: std::path::PathBuf,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.save_as_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn reload(
+            &mut self,
+            _: Model<Project>,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.reload_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn serialized_item_kind() -> Option<&'static str> {
+            Some("TestItem")
+        }
+
+        fn deserialize(
+            _project: Model<Project>,
+            _workspace: WeakView<Workspace>,
+            workspace_id: WorkspaceId,
+            _item_id: ItemId,
+            cx: &mut ViewContext<Pane>,
+        ) -> Task<anyhow::Result<View<Self>>> {
+            let view = cx.build_view(|cx| Self::new_deserialized(workspace_id, cx));
+            Task::Ready(Some(anyhow::Ok(view)))
+        }
+    }
+}

crates/workspace2/src/pane.rs πŸ”—

@@ -12,9 +12,9 @@ use gpui::{
     ViewContext, VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
 use std::{
     any::Any,
     cmp, fmt, mem,

crates/workspace2/src/pane_group.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, bail, Result};
 use collections::HashMap;
-use db2::sqlez::{
+use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
@@ -9,7 +9,7 @@ use gpui::{
     point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
 };
 use parking_lot::Mutex;
-use project2::Project;
+use project::Project;
 use serde::Deserialize;
 use std::sync::Arc;
 use ui::prelude::*;

crates/workspace2/src/persistence.rs πŸ”—

@@ -5,7 +5,7 @@ pub mod model;
 use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
-use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
 use gpui::WindowBounds;
 
 use util::{unzip_option, ResultExt};
@@ -552,7 +552,7 @@ impl WorkspaceDb {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use db2::open_test_db;
+    use db::open_test_db;
     use gpui;
 
     #[gpui::test]

crates/workspace2/src/persistence/model.rs πŸ”—

@@ -3,12 +3,12 @@ use crate::{
 };
 use anyhow::{Context, Result};
 use async_recursion::async_recursion;
-use db2::sqlez::{
+use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
 use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
-use project2::Project;
+use project::Project;
 use std::{
     path::{Path, PathBuf},
     sync::Arc,

crates/workspace2/src/searchable.rs πŸ”—

@@ -4,7 +4,7 @@ use gpui::{
     AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
     WindowContext,
 };
-use project2::search::SearchQuery;
+use project::search::SearchQuery;
 
 use crate::{
     item::{Item, WeakItemHandle},

crates/workspace2/src/workspace2.rs πŸ”—

@@ -16,7 +16,7 @@ mod workspace_settings;
 
 use anyhow::{anyhow, Context as _, Result};
 use async_trait::async_trait;
-use client2::{
+use client::{
     proto::{self, PeerId},
     Client, TypedEnvelope, User, UserStore,
 };
@@ -37,7 +37,7 @@ use gpui::{
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
-use language2::{LanguageRegistry, Rope};
+use language::{LanguageRegistry, Rope};
 use lazy_static::lazy_static;
 pub use modal_layer::*;
 use node_runtime::NodeRuntime;
@@ -49,9 +49,9 @@ pub use persistence::{
     WorkspaceDb, DB,
 };
 use postage::stream::Stream;
-use project2::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
+use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
 use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
 use std::{
@@ -62,7 +62,7 @@ use std::{
     sync::{atomic::AtomicUsize, Arc},
     time::Duration,
 };
-use theme2::{ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, ThemeSettings};
 pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 pub use ui;
 use util::ResultExt;
@@ -301,7 +301,7 @@ pub struct AppState {
     pub client: Arc<Client>,
     pub user_store: Model<UserStore>,
     pub workspace_store: Model<WorkspaceStore>,
-    pub fs: Arc<dyn fs2::Fs>,
+    pub fs: Arc<dyn fs::Fs>,
     pub call_factory: CallFactory,
     pub build_window_options:
         fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
@@ -312,7 +312,7 @@ pub struct WorkspaceStore {
     workspaces: HashSet<WindowHandle<Workspace>>,
     followers: Vec<Follower>,
     client: Arc<Client>,
-    _subscriptions: Vec<client2::Subscription>,
+    _subscriptions: Vec<client::Subscription>,
 }
 
 #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -388,22 +388,22 @@ impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
         use node_runtime::FakeNodeRuntime;
-        use settings2::SettingsStore;
+        use settings::SettingsStore;
 
         if !cx.has_global::<SettingsStore>() {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
         }
 
-        let fs = fs2::FakeFs::new(cx.background_executor().clone());
+        let fs = fs::FakeFs::new(cx.background_executor().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.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
         let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
-        theme2::init(theme2::LoadThemes::JustBase, cx);
-        client2::init(&client, cx);
+        theme::init(theme::LoadThemes::JustBase, cx);
+        client::init(&client, cx);
         crate::init_settings(cx);
 
         Arc::new(Self {
@@ -567,29 +567,29 @@ impl Workspace {
         cx.observe(&project, |_, _, cx| cx.notify()).detach();
         cx.subscribe(&project, move |this, _, event, cx| {
             match event {
-                project2::Event::RemoteIdChanged(_) => {
+                project::Event::RemoteIdChanged(_) => {
                     this.update_window_title(cx);
                 }
 
-                project2::Event::CollaboratorLeft(peer_id) => {
+                project::Event::CollaboratorLeft(peer_id) => {
                     this.collaborator_left(*peer_id, cx);
                 }
 
-                project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
                     this.update_window_title(cx);
                     this.serialize_workspace(cx);
                 }
 
-                project2::Event::DisconnectedFromHost => {
+                project::Event::DisconnectedFromHost => {
                     this.update_window_edited(cx);
                     cx.blur();
                 }
 
-                project2::Event::Closed => {
+                project::Event::Closed => {
                     cx.remove_window();
                 }
 
-                project2::Event::DeletedEntry(entry_id) => {
+                project::Event::DeletedEntry(entry_id) => {
                     for pane in this.panes.iter() {
                         pane.update(cx, |pane, cx| {
                             pane.handle_deleted_project_item(*entry_id, cx)
@@ -597,7 +597,7 @@ impl Workspace {
                     }
                 }
 
-                project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
                     cx.build_view(|_| MessageNotification::new(message.clone()))
                 }),
 
@@ -1450,7 +1450,7 @@ impl Workspace {
                             .map(|entry| entry.id);
                             if let Some(entry_id) = entry_id {
                                 workspace.project.update(cx, |_, cx| {
-                                    cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id)));
+                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
                                 })
                             }
                         })
@@ -1812,8 +1812,7 @@ impl Workspace {
         });
         cx.subscribe(&pane, Self::handle_pane_event).detach();
         self.panes.push(pane.clone());
-        // todo!()
-        // cx.focus(&pane);
+        cx.focus_view(&pane);
         cx.emit(Event::PaneAdded(pane.clone()));
         pane
     }
@@ -1988,7 +1987,7 @@ impl Workspace {
     where
         T: ProjectItem,
     {
-        use project2::Item as _;
+        use project::Item as _;
 
         let entry_id = project_item.read(cx).entry_id(cx);
         if let Some(item) = entry_id
@@ -2013,7 +2012,7 @@ impl Workspace {
     where
         T: ProjectItem,
     {
-        use project2::Item as _;
+        use project::Item as _;
 
         let entry_id = project_item.read(cx).entry_id(cx);
         if let Some(item) = entry_id
@@ -3673,7 +3672,7 @@ fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncA
 
     workspace
         .update(cx, |workspace, cx| {
-            if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
+            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
                 workspace.show_notification_once(0, cx, |cx| {
                     cx.build_view(|_| {
                         MessageNotification::new("Failed to load the database file.")
@@ -4554,960 +4553,959 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
     Some(size((width as f64).into(), (height as f64).into()))
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-//     use crate::{
-//         dock::test::TestPanel,
-//         item::test::{TestItem, TestItemEvent, TestProjectItem},
-//     };
-//     use fs::FakeFs;
-//     use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
-//     use project::{Project, ProjectEntryId};
-//     use serde_json::json;
-//     use settings::SettingsStore;
-//     use std::{cell::RefCell, rc::Rc};
-
-//     #[gpui::test]
-//     async fn test_tab_disambiguation(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-
-//         // Adding an item with no ambiguity renders the tab without detail.
-//         let item1 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item1.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
-
-//         // Adding an item that creates ambiguity increases the level of detail on
-//         // both tabs.
-//         let item2 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item2.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-
-//         // Adding an item that creates ambiguity increases the level of detail only
-//         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
-//         // we stop at the highest detail available.
-//         let item3 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item3.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-//         item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-//     }
-
-//     #[gpui::test]
-//     async fn test_tracking_active_path(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         fs.insert_tree(
-//             "/root1",
-//             json!({
-//                 "one.txt": "",
-//                 "two.txt": "",
-//             }),
-//         )
-//         .await;
-//         fs.insert_tree(
-//             "/root2",
-//             json!({
-//                 "three.txt": "",
-//             }),
-//         )
-//         .await;
-
-//         let project = Project::test(fs, ["root1".as_ref()], 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());
-//         let worktree_id = project.read_with(cx, |project, cx| {
-//             project.worktrees().next().unwrap().read(cx).id()
-//         });
-
-//         let item1 = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-//         });
-//         let item2 = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
-//         });
-
-//         // Add an item to an empty pane
-//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
-
-//         // Add a second item to a non-empty pane
-//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
-//         assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "two.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{
+        dock::test::TestPanel,
+        item::{
+            test::{TestItem, TestItemEvent, TestProjectItem},
+            ItemEvent,
+        },
+    };
+    use fs::FakeFs;
+    use gpui::TestAppContext;
+    use project::{Project, ProjectEntryId};
+    use serde_json::json;
+    use settings::SettingsStore;
+    use std::{cell::RefCell, rc::Rc};
+
+    #[gpui::test]
+    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+        // Adding an item with no ambiguity renders the tab without detail.
+        let item1 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item1.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
+
+        // Adding an item that creates ambiguity increases the level of detail on
+        // both tabs.
+        let item2 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item2.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+
+        // Adding an item that creates ambiguity increases the level of detail only
+        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
+        // we stop at the highest detail available.
+        let item3 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item3.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+    }
+
+    //     #[gpui::test]
+    //     async fn test_tracking_active_path(cx: &mut TestAppContext) {
+    //         init_test(cx);
+
+    //         let fs = FakeFs::new(cx.background());
+    //         fs.insert_tree(
+    //             "/root1",
+    //             json!({
+    //                 "one.txt": "",
+    //                 "two.txt": "",
+    //             }),
+    //         )
+    //         .await;
+    //         fs.insert_tree(
+    //             "/root2",
+    //             json!({
+    //                 "three.txt": "",
+    //             }),
+    //         )
+    //         .await;
+
+    //         let project = Project::test(fs, ["root1".as_ref()], 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());
+    //         let worktree_id = project.read_with(cx, |project, cx| {
+    //             project.worktrees().next().unwrap().read(cx).id()
+    //         });
 
-//         // Close the active item
-//         pane.update(cx, |pane, cx| {
-//             pane.close_active_item(&Default::default(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
+    //         let item1 = window.build_view(cx, |cx| {
+    //             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
+    //         });
+    //         let item2 = window.build_view(cx, |cx| {
+    //             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
+    //         });
 
-//         // Add a project folder
-//         project
-//             .update(cx, |project, cx| {
-//                 project.find_or_create_local_worktree("/root2", true, cx)
-//             })
-//             .await
-//             .unwrap();
-//         assert_eq!(
-//             window.current_title(cx).as_deref(),
-//             Some("one.txt β€” root1, root2")
-//         );
-
-//         // Remove a project folder
-//         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
-//     }
+    //         // Add an item to an empty pane
+    //         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
+    //         project.read_with(cx, |project, cx| {
+    //             assert_eq!(
+    //                 project.active_entry(),
+    //                 project
+    //                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+    //                     .map(|e| e.id)
+    //             );
+    //         });
+    //         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
+
+    //         // Add a second item to a non-empty pane
+    //         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
+    //         assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
+    //         project.read_with(cx, |project, cx| {
+    //             assert_eq!(
+    //                 project.active_entry(),
+    //                 project
+    //                     .entry_for_path(&(worktree_id, "two.txt").into(), cx)
+    //                     .map(|e| e.id)
+    //             );
+    //         });
 
-//     #[gpui::test]
-//     async fn test_close_window(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         fs.insert_tree("/root", json!({ "one": "" })).await;
-
-//         let project = Project::test(fs, ["root".as_ref()], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-
-//         // When there are no dirty items, there's nothing to do.
-//         let item1 = window.build_view(cx, |_| TestItem::new());
-//         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
-//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-//         assert!(task.await.unwrap());
-
-//         // When there are dirty untitled items, prompt to save each one. If the user
-//         // cancels any prompt, then abort.
-//         let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
-//         let item3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         workspace.update(cx, |w, cx| {
-//             w.add_item(Box::new(item2.clone()), cx);
-//             w.add_item(Box::new(item3.clone()), cx);
-//         });
-//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-//         cx.foreground().run_until_parked();
-//         window.simulate_prompt_answer(2, cx); // cancel save all
-//         cx.foreground().run_until_parked();
-//         window.simulate_prompt_answer(2, cx); // cancel save all
-//         cx.foreground().run_until_parked();
-//         assert!(!window.has_pending_prompt(cx));
-//         assert!(!task.await.unwrap());
-//     }
+    //         // Close the active item
+    //         pane.update(cx, |pane, cx| {
+    //             pane.close_active_item(&Default::default(), cx).unwrap()
+    //         })
+    //         .await
+    //         .unwrap();
+    //         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
+    //         project.read_with(cx, |project, cx| {
+    //             assert_eq!(
+    //                 project.active_entry(),
+    //                 project
+    //                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+    //                     .map(|e| e.id)
+    //             );
+    //         });
 
-//     #[gpui::test]
-//     async fn test_close_pane_items(cx: &mut TestAppContext) {
-//         init_test(cx);
+    //         // Add a project folder
+    //         project
+    //             .update(cx, |project, cx| {
+    //                 project.find_or_create_local_worktree("/root2", true, cx)
+    //             })
+    //             .await
+    //             .unwrap();
+    //         assert_eq!(
+    //             window.current_title(cx).as_deref(),
+    //             Some("one.txt β€” root1, root2")
+    //         );
 
-//         let fs = FakeFs::new(cx.background());
+    //         // Remove a project folder
+    //         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+    //         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
+    //     }
 
-//         let project = Project::test(fs, None, cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+    //     #[gpui::test]
+    //     async fn test_close_window(cx: &mut TestAppContext) {
+    //         init_test(cx);
+
+    //         let fs = FakeFs::new(cx.background());
+    //         fs.insert_tree("/root", json!({ "one": "" })).await;
+
+    //         let project = Project::test(fs, ["root".as_ref()], cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+    //         let workspace = window.root(cx);
+
+    //         // When there are no dirty items, there's nothing to do.
+    //         let item1 = window.build_view(cx, |_| TestItem::new());
+    //         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
+    //         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+    //         assert!(task.await.unwrap());
+
+    //         // When there are dirty untitled items, prompt to save each one. If the user
+    //         // cancels any prompt, then abort.
+    //         let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
+    //         let item3 = window.build_view(cx, |cx| {
+    //             TestItem::new()
+    //                 .with_dirty(true)
+    //                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+    //         });
+    //         workspace.update(cx, |w, cx| {
+    //             w.add_item(Box::new(item2.clone()), cx);
+    //             w.add_item(Box::new(item3.clone()), cx);
+    //         });
+    //         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+    //         cx.foreground().run_until_parked();
+    //         window.simulate_prompt_answer(2, cx); // cancel save all
+    //         cx.foreground().run_until_parked();
+    //         window.simulate_prompt_answer(2, cx); // cancel save all
+    //         cx.foreground().run_until_parked();
+    //         assert!(!window.has_pending_prompt(cx));
+    //         assert!(!task.await.unwrap());
+    //     }
 
-//         let item1 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let item2 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_conflict(true)
-//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-//         });
-//         let item3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_conflict(true)
-//                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
-//         });
-//         let item4 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
-//         });
-//         let pane = workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item1.clone()), cx);
-//             workspace.add_item(Box::new(item2.clone()), cx);
-//             workspace.add_item(Box::new(item3.clone()), cx);
-//             workspace.add_item(Box::new(item4.clone()), cx);
-//             workspace.active_pane().clone()
-//         });
+    //     #[gpui::test]
+    //     async fn test_close_pane_items(cx: &mut TestAppContext) {
+    //         init_test(cx);
 
-//         let close_items = pane.update(cx, |pane, cx| {
-//             pane.activate_item(1, true, true, cx);
-//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
-//             let item1_id = item1.id();
-//             let item3_id = item3.id();
-//             let item4_id = item4.id();
-//             pane.close_items(cx, SaveIntent::Close, move |id| {
-//                 [item1_id, item3_id, item4_id].contains(&id)
-//             })
-//         });
-//         cx.foreground().run_until_parked();
-
-//         assert!(window.has_pending_prompt(cx));
-//         // Ignore "Save all" prompt
-//         window.simulate_prompt_answer(2, cx);
-//         cx.foreground().run_until_parked();
-//         // There's a prompt to save item 1.
-//         pane.read_with(cx, |pane, _| {
-//             assert_eq!(pane.items_len(), 4);
-//             assert_eq!(pane.active_item().unwrap().id(), item1.id());
-//         });
-//         // Confirm saving item 1.
-//         window.simulate_prompt_answer(0, cx);
-//         cx.foreground().run_until_parked();
-
-//         // Item 1 is saved. There's a prompt to save item 3.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item1.read(cx).save_count, 1);
-//             assert_eq!(item1.read(cx).save_as_count, 0);
-//             assert_eq!(item1.read(cx).reload_count, 0);
-//             assert_eq!(pane.items_len(), 3);
-//             assert_eq!(pane.active_item().unwrap().id(), item3.id());
-//         });
-//         assert!(window.has_pending_prompt(cx));
-
-//         // Cancel saving item 3.
-//         window.simulate_prompt_answer(1, cx);
-//         cx.foreground().run_until_parked();
-
-//         // Item 3 is reloaded. There's a prompt to save item 4.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item3.read(cx).save_count, 0);
-//             assert_eq!(item3.read(cx).save_as_count, 0);
-//             assert_eq!(item3.read(cx).reload_count, 1);
-//             assert_eq!(pane.items_len(), 2);
-//             assert_eq!(pane.active_item().unwrap().id(), item4.id());
-//         });
-//         assert!(window.has_pending_prompt(cx));
-
-//         // Confirm saving item 4.
-//         window.simulate_prompt_answer(0, cx);
-//         cx.foreground().run_until_parked();
-
-//         // There's a prompt for a path for item 4.
-//         cx.simulate_new_path_selection(|_| Some(Default::default()));
-//         close_items.await.unwrap();
-
-//         // The requested items are closed.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item4.read(cx).save_count, 0);
-//             assert_eq!(item4.read(cx).save_as_count, 1);
-//             assert_eq!(item4.read(cx).reload_count, 0);
-//             assert_eq!(pane.items_len(), 1);
-//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
-//         });
-//     }
+    //         let fs = FakeFs::new(cx.background());
 
-//     #[gpui::test]
-//     async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-
-//         // Create several workspace items with single project entries, and two
-//         // workspace items with multiple project entries.
-//         let single_entry_items = (0..=4)
-//             .map(|project_entry_id| {
-//                 window.build_view(cx, |cx| {
-//                     TestItem::new()
-//                         .with_dirty(true)
-//                         .with_project_items(&[TestProjectItem::new(
-//                             project_entry_id,
-//                             &format!("{project_entry_id}.txt"),
-//                             cx,
-//                         )])
-//                 })
-//             })
-//             .collect::<Vec<_>>();
-//         let item_2_3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_singleton(false)
-//                 .with_project_items(&[
-//                     single_entry_items[2].read(cx).project_items[0].clone(),
-//                     single_entry_items[3].read(cx).project_items[0].clone(),
-//                 ])
-//         });
-//         let item_3_4 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_singleton(false)
-//                 .with_project_items(&[
-//                     single_entry_items[3].read(cx).project_items[0].clone(),
-//                     single_entry_items[4].read(cx).project_items[0].clone(),
-//                 ])
-//         });
+    //         let project = Project::test(fs, None, cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    //         let workspace = window.root(cx);
 
-//         // Create two panes that contain the following project entries:
-//         //   left pane:
-//         //     multi-entry items:   (2, 3)
-//         //     single-entry items:  0, 1, 2, 3, 4
-//         //   right pane:
-//         //     single-entry items:  1
-//         //     multi-entry items:   (3, 4)
-//         let left_pane = workspace.update(cx, |workspace, cx| {
-//             let left_pane = workspace.active_pane().clone();
-//             workspace.add_item(Box::new(item_2_3.clone()), cx);
-//             for item in single_entry_items {
-//                 workspace.add_item(Box::new(item), cx);
-//             }
-//             left_pane.update(cx, |pane, cx| {
-//                 pane.activate_item(2, true, true, cx);
-//             });
-
-//             workspace
-//                 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
-//                 .unwrap();
+    //         let item1 = window.build_view(cx, |cx| {
+    //             TestItem::new()
+    //                 .with_dirty(true)
+    //                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+    //         });
+    //         let item2 = window.build_view(cx, |cx| {
+    //             TestItem::new()
+    //                 .with_dirty(true)
+    //                 .with_conflict(true)
+    //                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
+    //         });
+    //         let item3 = window.build_view(cx, |cx| {
+    //             TestItem::new()
+    //                 .with_dirty(true)
+    //                 .with_conflict(true)
+    //                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
+    //         });
+    //         let item4 = window.build_view(cx, |cx| {
+    //             TestItem::new()
+    //                 .with_dirty(true)
+    //                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
+    //         });
+    //         let pane = workspace.update(cx, |workspace, cx| {
+    //             workspace.add_item(Box::new(item1.clone()), cx);
+    //             workspace.add_item(Box::new(item2.clone()), cx);
+    //             workspace.add_item(Box::new(item3.clone()), cx);
+    //             workspace.add_item(Box::new(item4.clone()), cx);
+    //             workspace.active_pane().clone()
+    //         });
 
-//             left_pane
-//         });
+    //         let close_items = pane.update(cx, |pane, cx| {
+    //             pane.activate_item(1, true, true, cx);
+    //             assert_eq!(pane.active_item().unwrap().id(), item2.id());
+    //             let item1_id = item1.id();
+    //             let item3_id = item3.id();
+    //             let item4_id = item4.id();
+    //             pane.close_items(cx, SaveIntent::Close, move |id| {
+    //                 [item1_id, item3_id, item4_id].contains(&id)
+    //             })
+    //         });
+    //         cx.foreground().run_until_parked();
+
+    //         assert!(window.has_pending_prompt(cx));
+    //         // Ignore "Save all" prompt
+    //         window.simulate_prompt_answer(2, cx);
+    //         cx.foreground().run_until_parked();
+    //         // There's a prompt to save item 1.
+    //         pane.read_with(cx, |pane, _| {
+    //             assert_eq!(pane.items_len(), 4);
+    //             assert_eq!(pane.active_item().unwrap().id(), item1.id());
+    //         });
+    //         // Confirm saving item 1.
+    //         window.simulate_prompt_answer(0, cx);
+    //         cx.foreground().run_until_parked();
+
+    //         // Item 1 is saved. There's a prompt to save item 3.
+    //         pane.read_with(cx, |pane, cx| {
+    //             assert_eq!(item1.read(cx).save_count, 1);
+    //             assert_eq!(item1.read(cx).save_as_count, 0);
+    //             assert_eq!(item1.read(cx).reload_count, 0);
+    //             assert_eq!(pane.items_len(), 3);
+    //             assert_eq!(pane.active_item().unwrap().id(), item3.id());
+    //         });
+    //         assert!(window.has_pending_prompt(cx));
+
+    //         // Cancel saving item 3.
+    //         window.simulate_prompt_answer(1, cx);
+    //         cx.foreground().run_until_parked();
+
+    //         // Item 3 is reloaded. There's a prompt to save item 4.
+    //         pane.read_with(cx, |pane, cx| {
+    //             assert_eq!(item3.read(cx).save_count, 0);
+    //             assert_eq!(item3.read(cx).save_as_count, 0);
+    //             assert_eq!(item3.read(cx).reload_count, 1);
+    //             assert_eq!(pane.items_len(), 2);
+    //             assert_eq!(pane.active_item().unwrap().id(), item4.id());
+    //         });
+    //         assert!(window.has_pending_prompt(cx));
+
+    //         // Confirm saving item 4.
+    //         window.simulate_prompt_answer(0, cx);
+    //         cx.foreground().run_until_parked();
+
+    //         // There's a prompt for a path for item 4.
+    //         cx.simulate_new_path_selection(|_| Some(Default::default()));
+    //         close_items.await.unwrap();
+
+    //         // The requested items are closed.
+    //         pane.read_with(cx, |pane, cx| {
+    //             assert_eq!(item4.read(cx).save_count, 0);
+    //             assert_eq!(item4.read(cx).save_as_count, 1);
+    //             assert_eq!(item4.read(cx).reload_count, 0);
+    //             assert_eq!(pane.items_len(), 1);
+    //             assert_eq!(pane.active_item().unwrap().id(), item2.id());
+    //         });
+    //     }
 
-//         //Need to cause an effect flush in order to respect new focus
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item_3_4.clone()), cx);
-//             cx.focus(&left_pane);
-//         });
+    #[gpui::test]
+    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+        // Create several workspace items with single project entries, and two
+        // workspace items with multiple project entries.
+        let single_entry_items = (0..=4)
+            .map(|project_entry_id| {
+                cx.build_view(|cx| {
+                    TestItem::new(cx)
+                        .with_dirty(true)
+                        .with_project_items(&[TestProjectItem::new(
+                            project_entry_id,
+                            &format!("{project_entry_id}.txt"),
+                            cx,
+                        )])
+                })
+            })
+            .collect::<Vec<_>>();
+        let item_2_3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_singleton(false)
+                .with_project_items(&[
+                    single_entry_items[2].read(cx).project_items[0].clone(),
+                    single_entry_items[3].read(cx).project_items[0].clone(),
+                ])
+        });
+        let item_3_4 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_singleton(false)
+                .with_project_items(&[
+                    single_entry_items[3].read(cx).project_items[0].clone(),
+                    single_entry_items[4].read(cx).project_items[0].clone(),
+                ])
+        });
 
-//         // When closing all of the items in the left pane, we should be prompted twice:
-//         // once for project entry 0, and once for project entry 2. After those two
-//         // prompts, the task should complete.
+        // Create two panes that contain the following project entries:
+        //   left pane:
+        //     multi-entry items:   (2, 3)
+        //     single-entry items:  0, 1, 2, 3, 4
+        //   right pane:
+        //     single-entry items:  1
+        //     multi-entry items:   (3, 4)
+        let left_pane = workspace.update(cx, |workspace, cx| {
+            let left_pane = workspace.active_pane().clone();
+            workspace.add_item(Box::new(item_2_3.clone()), cx);
+            for item in single_entry_items {
+                workspace.add_item(Box::new(item), cx);
+            }
+            left_pane.update(cx, |pane, cx| {
+                pane.activate_item(2, true, true, cx);
+            });
 
-//         let close = left_pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |_| true)
-//         });
-//         cx.foreground().run_until_parked();
-//         // Discard "Save all" prompt
-//         window.simulate_prompt_answer(2, cx);
-
-//         cx.foreground().run_until_parked();
-//         left_pane.read_with(cx, |pane, cx| {
-//             assert_eq!(
-//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-//                 &[ProjectEntryId::from_proto(0)]
-//             );
-//         });
-//         window.simulate_prompt_answer(0, cx);
+            let right_pane = workspace
+                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
+                .unwrap();
 
-//         cx.foreground().run_until_parked();
-//         left_pane.read_with(cx, |pane, cx| {
-//             assert_eq!(
-//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-//                 &[ProjectEntryId::from_proto(2)]
-//             );
-//         });
-//         window.simulate_prompt_answer(0, cx);
+            right_pane.update(cx, |pane, cx| {
+                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
+            });
 
-//         cx.foreground().run_until_parked();
-//         close.await.unwrap();
-//         left_pane.read_with(cx, |pane, _| {
-//             assert_eq!(pane.items_len(), 0);
-//         });
-//     }
+            left_pane
+        });
 
-//     #[gpui::test]
-//     async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
+        cx.focus_view(&left_pane);
 
-//         let fs = FakeFs::new(cx.background());
+        // When closing all of the items in the left pane, we should be prompted twice:
+        // once for project entry 0, and once for project entry 2. Project entries 1,
+        // 3, and 4 are all still open in the other paten. After those two
+        // prompts, the task should complete.
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+        let close = left_pane.update(cx, |pane, cx| {
+            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
+        });
+        cx.executor().run_until_parked();
 
-//         let item = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let item_id = item.id();
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//         });
+        // Discard "Save all" prompt
+        cx.simulate_prompt_answer(2);
 
-//         // Autosave on window change.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnWindowChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        cx.executor().run_until_parked();
+        left_pane.update(cx, |pane, cx| {
+            assert_eq!(
+                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+                &[ProjectEntryId::from_proto(0)]
+            );
+        });
+        cx.simulate_prompt_answer(0);
 
-//         // Deactivating the window saves the file.
-//         window.simulate_deactivation(cx);
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
-
-//         // Autosave on focus change.
-//         item.update(cx, |item, cx| {
-//             cx.focus_self();
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        cx.executor().run_until_parked();
+        left_pane.update(cx, |pane, cx| {
+            assert_eq!(
+                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+                &[ProjectEntryId::from_proto(2)]
+            );
+        });
+        cx.simulate_prompt_answer(0);
 
-//         // Blurring the item saves the file.
-//         item.update(cx, |_, cx| cx.blur());
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
+        cx.executor().run_until_parked();
+        close.await.unwrap();
+        left_pane.update(cx, |pane, _| {
+            assert_eq!(pane.items_len(), 0);
+        });
+    }
 
-//         // Deactivating the window still saves the file.
-//         window.simulate_activation(cx);
-//         item.update(cx, |item, cx| {
-//             cx.focus_self();
-//             item.is_dirty = true;
-//         });
-//         window.simulate_deactivation(cx);
+    #[gpui::test]
+    async fn test_autosave(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
 
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
 
-//         // Autosave after delay.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
-//                 })
-//             });
-//             item.is_dirty = true;
-//             cx.emit(TestItemEvent::Edit);
-//         });
+        let item = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let item_id = item.entity_id();
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+        });
 
-//         // Delay hasn't fully expired, so the file is still dirty and unsaved.
-//         deterministic.advance_clock(Duration::from_millis(250));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+        // Autosave on window change.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         // After delay expires, the file is saved.
-//         deterministic.advance_clock(Duration::from_millis(250));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
+        // Deactivating the window saves the file.
+        cx.simulate_deactivation();
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
+
+        // Autosave on focus change.
+        item.update(cx, |item, cx| {
+            cx.focus_self();
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         // Autosave on focus change, ensuring closing the tab counts as such.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        // Blurring the item saves the file.
+        item.update(cx, |_, cx| cx.blur());
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
 
-//         pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-//         })
-//         .await
-//         .unwrap();
-//         assert!(!window.has_pending_prompt(cx));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-
-//         // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//         });
-//         item.update(cx, |item, cx| {
-//             item.project_items[0].update(cx, |item, _| {
-//                 item.entry_id = None;
-//             });
-//             item.is_dirty = true;
-//             cx.blur();
-//         });
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+        // Deactivating the window still saves the file.
+        cx.simulate_activation();
+        item.update(cx, |item, cx| {
+            cx.focus_self();
+            item.is_dirty = true;
+        });
+        cx.simulate_deactivation();
 
-//         // Ensure autosave is prevented for deleted files also when closing the buffer.
-//         let _close_items = pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-//         });
-//         deterministic.run_until_parked();
-//         assert!(window.has_pending_prompt(cx));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-//     }
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 
-//     #[gpui::test]
-//     async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
+        // Autosave after delay.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+                })
+            });
+            item.is_dirty = true;
+            cx.emit(ItemEvent::Edit);
+        });
 
-//         let fs = FakeFs::new(cx.background());
+        // Delay hasn't fully expired, so the file is still dirty and unsaved.
+        cx.executor().advance_clock(Duration::from_millis(250));
+        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        // After delay expires, the file is saved.
+        cx.executor().advance_clock(Duration::from_millis(250));
+        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
 
-//         let item = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
-//         let toolbar_notify_count = Rc::new(RefCell::new(0));
-
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//             let toolbar_notification_count = toolbar_notify_count.clone();
-//             cx.observe(&toolbar, move |_, _, _| {
-//                 *toolbar_notification_count.borrow_mut() += 1
-//             })
-//             .detach();
-//         });
+        // Autosave on focus change, ensuring closing the tab counts as such.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         pane.read_with(cx, |pane, _| {
-//             assert!(!pane.can_navigate_backward());
-//             assert!(!pane.can_navigate_forward());
-//         });
+        pane.update(cx, |pane, cx| {
+            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+        })
+        .await
+        .unwrap();
+        assert!(!cx.has_pending_prompt());
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+
+        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+        });
+        item.update(cx, |item, cx| {
+            item.project_items[0].update(cx, |item, _| {
+                item.entry_id = None;
+            });
+            item.is_dirty = true;
+            cx.blur();
+        });
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
 
-//         item.update(cx, |item, cx| {
-//             item.set_state("one".to_string(), cx);
-//         });
+        // Ensure autosave is prevented for deleted files also when closing the buffer.
+        let _close_items = pane.update(cx, |pane, cx| {
+            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+        });
+        cx.executor().run_until_parked();
+        assert!(cx.has_pending_prompt());
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+    }
 
-//         // Toolbar must be notified to re-render the navigation buttons
-//         assert_eq!(*toolbar_notify_count.borrow(), 1);
+    #[gpui::test]
+    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
 
-//         pane.read_with(cx, |pane, _| {
-//             assert!(pane.can_navigate_backward());
-//             assert!(!pane.can_navigate_forward());
-//         });
+        let fs = FakeFs::new(cx.executor());
 
-//         workspace
-//             .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
-//             .await
-//             .unwrap();
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
 
-//         assert_eq!(*toolbar_notify_count.borrow(), 3);
-//         pane.read_with(cx, |pane, _| {
-//             assert!(!pane.can_navigate_backward());
-//             assert!(pane.can_navigate_forward());
-//         });
-//     }
+        let item = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
+        let toolbar_notify_count = Rc::new(RefCell::new(0));
+
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+            let toolbar_notification_count = toolbar_notify_count.clone();
+            cx.observe(&toolbar, move |_, _, _| {
+                *toolbar_notification_count.borrow_mut() += 1
+            })
+            .detach();
+        });
 
-//     #[gpui::test]
-//     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
-//         let fs = FakeFs::new(cx.background());
+        pane.update(cx, |pane, _| {
+            assert!(!pane.can_navigate_backward());
+            assert!(!pane.can_navigate_forward());
+        });
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        item.update(cx, |item, cx| {
+            item.set_state("one".to_string(), cx);
+        });
 
-//         let panel = workspace.update(cx, |workspace, cx| {
-//             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-//             workspace.add_panel(panel.clone(), cx);
+        // Toolbar must be notified to re-render the navigation buttons
+        assert_eq!(*toolbar_notify_count.borrow(), 1);
 
-//             workspace
-//                 .right_dock()
-//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+        pane.update(cx, |pane, _| {
+            assert!(pane.can_navigate_backward());
+            assert!(!pane.can_navigate_forward());
+        });
 
-//             panel
-//         });
+        workspace
+            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
+            .await
+            .unwrap();
 
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         pane.update(cx, |pane, cx| {
-//             let item = cx.build_view(|_| TestItem::new());
-//             pane.add_item(Box::new(item), true, true, None, cx);
-//         });
+        assert_eq!(*toolbar_notify_count.borrow(), 2);
+        pane.update(cx, |pane, _| {
+            assert!(!pane.can_navigate_backward());
+            assert!(pane.can_navigate_forward());
+        });
+    }
 
-//         // Transfer focus from center to panel
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //     #[gpui::test]
+    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+    //         init_test(cx);
+    //         let fs = FakeFs::new(cx.background());
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         let project = Project::test(fs, [], cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    //         let workspace = window.root(cx);
 
-//         // Transfer focus from panel to center
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         let panel = workspace.update(cx, |workspace, cx| {
+    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+    //             workspace.add_panel(panel.clone(), cx);
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //             workspace
+    //                 .right_dock()
+    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
 
-//         // Close the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx);
-//         });
+    //             panel
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+    //         pane.update(cx, |pane, cx| {
+    //             let item = cx.build_view(|_| TestItem::new());
+    //             pane.add_item(Box::new(item), true, true, None, cx);
+    //         });
 
-//         // Open the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx);
-//         });
+    //         // Transfer focus from center to panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Focus and zoom panel
-//         panel.update(cx, |panel, cx| {
-//             cx.focus_self();
-//             panel.set_zoomed(true, cx)
-//         });
+    //         // Transfer focus from panel to center
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Transfer focus to the center closes the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         // Close the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Transferring focus back to the panel keeps it zoomed
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         // Open the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Close the dock while it is zoomed
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
+    //         // Focus and zoom panel
+    //         panel.update(cx, |panel, cx| {
+    //             cx.focus_self();
+    //             panel.set_zoomed(true, cx)
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(workspace.zoomed.is_none());
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Opening the dock, when it's zoomed, retains focus
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
+    //         // Transfer focus to the center closes the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(workspace.zoomed.is_some());
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Unzoom and close the panel, zoom the active pane.
-//         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
-//         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+    //         // Transferring focus back to the panel keeps it zoomed
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         // Opening a dock unzooms the pane.
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
-//         workspace.read_with(cx, |workspace, cx| {
-//             let pane = pane.read(cx);
-//             assert!(!pane.is_zoomed());
-//             assert!(!pane.has_focus());
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(workspace.zoomed.is_none());
-//         });
-//     }
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//     #[gpui::test]
-//     async fn test_panels(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
-//         let fs = FakeFs::new(cx.background());
-
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-
-//         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
-//             // Add panel_1 on the left, panel_2 on the right.
-//             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
-//             workspace.add_panel(panel_1.clone(), cx);
-//             workspace
-//                 .left_dock()
-//                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
-//             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-//             workspace.add_panel(panel_2.clone(), cx);
-//             workspace
-//                 .right_dock()
-//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-//             let left_dock = workspace.left_dock();
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(
-//                 left_dock.read(cx).active_panel_size(cx).unwrap(),
-//                 panel_1.size(cx)
-//             );
+    //         // Close the dock while it is zoomed
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
 
-//             left_dock.update(cx, |left_dock, cx| {
-//                 left_dock.resize_active_panel(Some(1337.), cx)
-//             });
-//             assert_eq!(
-//                 workspace
-//                     .right_dock()
-//                     .read(cx)
-//                     .visible_panel()
-//                     .unwrap()
-//                     .id(),
-//                 panel_2.id()
-//             );
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(workspace.zoomed.is_none());
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//             (panel_1, panel_2)
-//         });
+    //         // Opening the dock, when it's zoomed, retains focus
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
 
-//         // Move panel_1 to the right
-//         panel_1.update(cx, |panel_1, cx| {
-//             panel_1.set_position(DockPosition::Right, cx)
-//         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(workspace.zoomed.is_some());
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
-//             // Since it was the only panel on the left, the left dock should now be closed.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
-//             let right_dock = workspace.right_dock();
-//             assert_eq!(
-//                 right_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //         // Unzoom and close the panel, zoom the active pane.
+    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
+    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
 
-//             // Now we move panel_2Β to the left
-//             panel_2.set_position(DockPosition::Left, cx);
-//         });
+    //         // Opening a dock unzooms the pane.
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             let pane = pane.read(cx);
+    //             assert!(!pane.is_zoomed());
+    //             assert!(!pane.has_focus());
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(workspace.zoomed.is_none());
+    //         });
+    //     }
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_2 was not visible on the right, we don't open the left dock.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             // And the right dock is unaffected in it's displaying of panel_1
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert_eq!(
-//                 workspace
-//                     .right_dock()
-//                     .read(cx)
-//                     .visible_panel()
-//                     .unwrap()
-//                     .id(),
-//                 panel_1.id()
-//             );
-//         });
+    //     #[gpui::test]
+    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
+    //         init_test(cx);
+    //         let fs = FakeFs::new(cx.background());
+
+    //         let project = Project::test(fs, [], cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    //         let workspace = window.root(cx);
+
+    //         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+    //             // Add panel_1 on the left, panel_2 on the right.
+    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
+    //             workspace.add_panel(panel_1.clone(), cx);
+    //             workspace
+    //                 .left_dock()
+    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+    //             workspace.add_panel(panel_2.clone(), cx);
+    //             workspace
+    //                 .right_dock()
+    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+    //             let left_dock = workspace.left_dock();
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(
+    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
+    //                 panel_1.size(cx)
+    //             );
+
+    //             left_dock.update(cx, |left_dock, cx| {
+    //                 left_dock.resize_active_panel(Some(1337.), cx)
+    //             });
+    //             assert_eq!(
+    //                 workspace
+    //                     .right_dock()
+    //                     .read(cx)
+    //                     .visible_panel()
+    //                     .unwrap()
+    //                     .id(),
+    //                 panel_2.id()
+    //             );
+
+    //             (panel_1, panel_2)
+    //         });
 
-//         // Move panel_1 back to the left
-//         panel_1.update(cx, |panel_1, cx| {
-//             panel_1.set_position(DockPosition::Left, cx)
-//         });
+    //         // Move panel_1 to the right
+    //         panel_1.update(cx, |panel_1, cx| {
+    //             panel_1.set_position(DockPosition::Right, cx)
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-//             // And right the dock should be closed as it no longer has any panels.
-//             assert!(!workspace.right_dock().read(cx).is_open());
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+    //             // Since it was the only panel on the left, the left dock should now be closed.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
+    //             let right_dock = workspace.right_dock();
+    //             assert_eq!(
+    //                 right_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+
+    //             // Now we move panel_2Β to the left
+    //             panel_2.set_position(DockPosition::Left, cx);
+    //         });
 
-//             // Now we move panel_1 to the bottom
-//             panel_1.set_position(DockPosition::Bottom, cx);
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_2 was not visible on the right, we don't open the left dock.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             // And the right dock is unaffected in it's displaying of panel_1
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert_eq!(
+    //                 workspace
+    //                     .right_dock()
+    //                     .read(cx)
+    //                     .visible_panel()
+    //                     .unwrap()
+    //                     .id(),
+    //                 panel_1.id()
+    //             );
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the left, we close the left dock.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             // The bottom dock is sized based on the panel's default size,
-//             // since the panel orientation changed from vertical to horizontal.
-//             let bottom_dock = workspace.bottom_dock();
-//             assert_eq!(
-//                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
-//                 panel_1.size(cx),
-//             );
-//             // Close bottom dock and move panel_1 back to the left.
-//             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
-//             panel_1.set_position(DockPosition::Left, cx);
-//         });
+    //         // Move panel_1 back to the left
+    //         panel_1.update(cx, |panel_1, cx| {
+    //             panel_1.set_position(DockPosition::Left, cx)
+    //         });
 
-//         // Emit activated event on panel 1
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //             // And right the dock should be closed as it no longer has any panels.
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+
+    //             // Now we move panel_1 to the bottom
+    //             panel_1.set_position(DockPosition::Bottom, cx);
+    //         });
 
-//         // Now the left dock is open and panel_1 is active and focused.
-//         workspace.read_with(cx, |workspace, cx| {
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert!(panel_1.is_focused(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the left, we close the left dock.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             // The bottom dock is sized based on the panel's default size,
+    //             // since the panel orientation changed from vertical to horizontal.
+    //             let bottom_dock = workspace.bottom_dock();
+    //             assert_eq!(
+    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
+    //                 panel_1.size(cx),
+    //             );
+    //             // Close bottom dock and move panel_1 back to the left.
+    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
+    //             panel_1.set_position(DockPosition::Left, cx);
+    //         });
 
-//         // Emit closed event on panel 2, which is not active
-//         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+    //         // Emit activated event on panel 1
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+
+    //         // Now the left dock is open and panel_1 is active and focused.
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert!(panel_1.is_focused(cx));
+    //         });
 
-//         // Wo don't close the left dock, because panel_2 wasn't the active panel
-//         workspace.read_with(cx, |workspace, cx| {
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//         });
+    //         // Emit closed event on panel 2, which is not active
+    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //         });
 
-//         // Emitting a ZoomIn event shows the panel as zoomed.
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-//         });
+    //         // Emitting a ZoomIn event shows the panel as zoomed.
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
+    //         });
 
-//         // Move panel to another dock while it is zoomed
-//         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // Move panel to another dock while it is zoomed
+    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // If focus is transferred to another view that's not a panel or another pane, we still show
-//         // the panel as zoomed.
-//         let focus_receiver = window.build_view(cx, |_| EmptyView);
-//         focus_receiver.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // If focus is transferred to another view that's not a panel or another pane, we still show
+    //         // the panel as zoomed.
+    //         let focus_receiver = window.build_view(cx, |_| EmptyView);
+    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
-//         workspace.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
+    //         workspace.update(cx, |_, cx| cx.focus_self());
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // If focus is transferred again to another view that's not a panel or a pane, we won't
-//         // show the panel as zoomed because it wasn't zoomed before.
-//         focus_receiver.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
+    //         // show the panel as zoomed because it wasn't zoomed before.
+    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // When focus is transferred back to the panel, it is zoomed again.
-//         panel_1.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // When focus is transferred back to the panel, it is zoomed again.
+    //         panel_1.update(cx, |_, cx| cx.focus_self());
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // Emitting a ZoomOut event unzooms the panel.
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // Emitting a ZoomOut event unzooms the panel.
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
+    //         workspace.read_with(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // Emit closed event on panel 1, which is active
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+    //         // Emit closed event on panel 1, which is active
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
 
-//         // Now the left dock is closed, because panel_1 was the active panel
-//         workspace.read_with(cx, |workspace, cx| {
-//             let right_dock = workspace.right_dock();
-//             assert!(!right_dock.read(cx).is_open());
-//         });
-//     }
+    //         // Now the left dock is closed, because panel_1 was the active panel
+    //         workspace.read_with(cx, |workspace, cx| {
+    //             let right_dock = workspace.right_dock();
+    //             assert!(!right_dock.read(cx).is_open());
+    //         });
+    //     }
 
-//     pub fn init_test(cx: &mut TestAppContext) {
-//         cx.foreground().forbid_parking();
-//         cx.update(|cx| {
-//             cx.set_global(SettingsStore::test(cx));
-//             theme::init((), cx);
-//             language::init(cx);
-//             crate::init_settings(cx);
-//             Project::init_settings(cx);
-//         });
-//     }
-// }
+    pub fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
+            theme::init(theme::LoadThemes::JustBase, cx);
+            language::init(cx);
+            crate::init_settings(cx);
+            Project::init_settings(cx);
+        });
+    }
+}