From a003a91212bc420c7e60348a90c59116519844c5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 30 Nov 2023 14:44:26 -0800 Subject: [PATCH] Restore auto-save on focus change, re-enable workspace tests --- 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 | 566 +++--- 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 | 1960 +++++++++---------- crates/workspace2/src/workspace_settings.rs | 2 +- 13 files changed, 1350 insertions(+), 1304 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9637720a67..31917e9bf3 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/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> { diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 3027c05fbd..7375f47939 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow { fn draw(&self, scene: Scene); fn sprite_atlas(&self) -> Arc; + + #[cfg(any(test, feature = "test-support"))] + fn as_test(&self) -> Option<&TestWindow> { + None + } } pub trait PlatformDispatcher: Send + Sync { diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index df1ed9b2a6..4532b33f50 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -189,13 +189,9 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_become_active(&self, _callback: Box) { - unimplemented!() - } + fn on_become_active(&self, _callback: Box) {} - fn on_resign_active(&self, _callback: Box) { - unimplemented!() - } + fn on_resign_active(&self, _callback: Box) {} fn on_quit(&self, _callback: Box) {} diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs index e355c3aa4b..e7ca7a1616 100644 --- a/crates/gpui2/src/platform/test/window.rs +++ b/crates/gpui2/src/platform/test/window.rs @@ -11,11 +11,11 @@ use std::{ }; #[derive(Default)] -struct Handlers { - active_status_change: Vec>, - input: Vec bool>>, - moved: Vec>, - resize: Vec, f32)>>, +pub(crate) struct TestWindowHandlers { + pub(crate) active_status_change: Vec>, + pub(crate) input: Vec bool>>, + pub(crate) moved: Vec>, + pub(crate) resize: Vec, f32)>>, } pub struct TestWindow { @@ -23,7 +23,7 @@ pub struct TestWindow { current_scene: Mutex>, display: Rc, pub(crate) input_handler: Option>>>, - handlers: Mutex, + pub(crate) handlers: Arc>, platform: Weak, sprite_atlas: Arc, } @@ -167,6 +167,10 @@ impl PlatformWindow for TestWindow { fn sprite_atlas(&self) -> sync::Arc { self.sprite_atlas.clone() } + + fn as_test(&self) -> Option<&TestWindow> { + Some(self) + } } pub struct TestAtlasState { diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index c327132a78..2dd7c6468e 100644 --- a/crates/workspace2/Cargo.toml +++ b/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 diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 2cd2ac0bd6..73de7d05c3 100644 --- a/crates/workspace2/src/item.rs +++ b/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 { 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; @@ -347,7 +347,7 @@ impl ItemHandle for View { 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 ItemHandle for View { pane: View, cx: &mut ViewContext, ) { + 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 ItemHandle for View { } })); - // 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 WeakItemHandle for WeakView { } pub trait ProjectItem: Item { - type Item: project2::Item; + type Item: project::Item; fn for_project_item( project: Model, @@ -759,300 +759,310 @@ impl FollowableItemHandle for View { } } -// #[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}; +#[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, -// pub project_path: Option, -// } + pub struct TestProjectItem { + pub entry_id: Option, + pub project_path: Option, + } -// 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>, -// pub nav_history: Option, -// pub tab_descriptions: Option>, -// pub tab_detail: Cell>, -// } + 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>, + pub nav_history: Option, + pub tab_descriptions: Option>, + pub tab_detail: Cell>, + focus_handle: gpui::FocusHandle, + } -// impl Entity for TestProjectItem { -// type Event = (); -// } + impl project::Item for TestProjectItem { + fn entry_id(&self, _: &AppContext) -> Option { + self.entry_id + } -// impl project2::Item for TestProjectItem { -// fn entry_id(&self, _: &AppContext) -> Option { -// self.entry_id -// } + fn project_path(&self, _: &AppContext) -> Option { + self.project_path.clone() + } + } -// fn project_path(&self, _: &AppContext) -> Option { -// self.project_path.clone() -// } -// } + pub enum TestItemEvent { + Edit, + } -// 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 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 { + 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, + }) + } -// impl TestProjectItem { -// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model { -// 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 { + cx.build_model(|_| Self { + project_path: None, + entry_id: None, + }) + } + } -// pub fn new_untitled(cx: &mut AppContext) -> Model { -// cx.add_model(|_| Self { -// project_path: None, -// entry_id: None, -// }) -// } -// } + impl TestItem { + pub fn new(cx: &mut ViewContext) -> 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(), + } + } -// 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, cx: &mut ViewContext) -> Self { + let mut this = Self::new(cx); + this.workspace_id = id; + this + } -// 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_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_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_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_conflict(mut self, has_conflict: bool) -> Self { -// self.has_conflict = has_conflict; -// self -// } + pub fn with_project_items(mut self, items: &[Model]) -> Self { + self.project_items.clear(); + self.project_items.extend(items.iter().cloned()); + self + } -// pub fn with_project_items(mut self, items: &[Model]) -> 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.push_to_nav_history(cx); + self.state = state; + } -// pub fn set_state(&mut self, state: String, cx: &mut ViewContext) { -// self.push_to_nav_history(cx); -// self.state = state; -// } + fn push_to_nav_history(&mut self, cx: &mut ViewContext) { + if let Some(history) = &mut self.nav_history { + history.push(Some(Box::new(self.state.clone())), cx); + } + } + } -// fn push_to_nav_history(&mut self, cx: &mut ViewContext) { -// if let Some(history) = &mut self.nav_history { -// history.push(Some(Box::new(self.state.clone())), cx); -// } -// } -// } + impl Render for TestItem { + type Element = Div; -// impl Entity for TestItem { -// type Event = TestItemEvent; -// } + fn render(&mut self, _: &mut ViewContext) -> Self::Element { + gpui::div() + } + } -// impl View for TestItem { -// fn ui_name() -> &'static str { -// "TestItem" -// } + impl EventEmitter for TestItem {} -// fn render(&mut self, _: &mut ViewContext) -> AnyElement { -// Empty::new().into_any() -// } -// } + 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> { -// self.tab_descriptions.as_ref().and_then(|descriptions| { -// let description = *descriptions.get(detail).or_else(|| descriptions.last())?; -// Some(description.into()) -// }) -// } + impl Item for TestItem { + fn tab_description(&self, detail: usize, _: &AppContext) -> Option { + 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, -// _: &theme2::Tab, -// _: &AppContext, -// ) -> AnyElement { -// self.tab_detail.set(detail); -// Empty::new().into_any() -// } + fn tab_content( + &self, + detail: Option, + 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(usize, &dyn project2::Item), -// ) { -// self.project_items -// .iter() -// .for_each(|item| f(item.id(), item.read(cx))) -// } + 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 is_singleton(&self, _: &AppContext) -> bool { + self.is_singleton + } -// fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { -// self.nav_history = Some(history); -// } + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); + } -// fn navigate(&mut self, state: Box, _: &mut ViewContext) -> bool { -// let state = *state.downcast::().unwrap_or_default(); -// if state != self.state { -// self.state = state; -// true -// } else { -// false -// } -// } + fn navigate(&mut self, state: Box, _: &mut ViewContext) -> bool { + let state = *state.downcast::().unwrap_or_default(); + if state != self.state { + self.state = state; + true + } else { + false + } + } -// fn deactivated(&mut self, cx: &mut ViewContext) { -// self.push_to_nav_history(cx); -// } + fn deactivated(&mut self, cx: &mut ViewContext) { + self.push_to_nav_history(cx); + } -// fn clone_on_split( -// &self, -// _workspace_id: WorkspaceId, -// _: &mut ViewContext, -// ) -> Option -// where -// Self: Sized, -// { -// Some(self.clone()) -// } + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option> + 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 is_dirty(&self, _: &AppContext) -> bool { + self.is_dirty + } -// fn has_conflict(&self, _: &AppContext) -> bool { -// self.has_conflict -// } + 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 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, -// _: &mut ViewContext, -// ) -> Task> { -// self.save_count += 1; -// self.is_dirty = false; -// Task::ready(Ok(())) -// } + fn save( + &mut self, + _: Model, + _: &mut ViewContext, + ) -> Task> { + self.save_count += 1; + self.is_dirty = false; + Task::ready(Ok(())) + } -// fn save_as( -// &mut self, -// _: Model, -// _: std::path::PathBuf, -// _: &mut ViewContext, -// ) -> Task> { -// self.save_as_count += 1; -// self.is_dirty = false; -// Task::ready(Ok(())) -// } + fn save_as( + &mut self, + _: Model, + _: std::path::PathBuf, + _: &mut ViewContext, + ) -> Task> { + self.save_as_count += 1; + self.is_dirty = false; + Task::ready(Ok(())) + } -// fn reload( -// &mut self, -// _: Model, -// _: &mut ViewContext, -// ) -> Task> { -// self.reload_count += 1; -// self.is_dirty = false; -// Task::ready(Ok(())) -// } + fn reload( + &mut self, + _: Model, + _: &mut ViewContext, + ) -> Task> { + 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 serialized_item_kind() -> Option<&'static str> { -// Some("TestItem") -// } - -// fn deserialize( -// _project: Model, -// _workspace: WeakViewHandle, -// workspace_id: WorkspaceId, -// _item_id: ItemId, -// cx: &mut ViewContext, -// ) -> Task>> { -// let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id)); -// Task::Ready(Some(anyhow::Ok(view))) -// } -// } -// } + fn deserialize( + _project: Model, + _workspace: WeakView, + workspace_id: WorkspaceId, + _item_id: ItemId, + cx: &mut ViewContext, + ) -> Task>> { + let view = cx.build_view(|cx| Self::new_deserialized(workspace_id, cx)); + Task::Ready(Some(anyhow::Ok(view))) + } + } +} diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index aa91cfc91b..f16a4fcf09 100644 --- a/crates/workspace2/src/pane.rs +++ b/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, diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 7562c00aeb..c98fac00c6 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/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::*; diff --git a/crates/workspace2/src/persistence.rs b/crates/workspace2/src/persistence.rs index 9790495087..b842355991 100644 --- a/crates/workspace2/src/persistence.rs +++ b/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] diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index fde052706b..74304d3c8e 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/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, diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index 6d1c112b71..eadd602c84 100644 --- a/crates/workspace2/src/searchable.rs +++ b/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}, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index cbd3e4309c..597b448267 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/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, pub user_store: Model, pub workspace_store: Model, - pub fs: Arc, + pub fs: Arc, pub call_factory: CallFactory, pub build_window_options: fn(Option, Option, &mut AppContext) -> WindowOptions, @@ -312,7 +312,7 @@ pub struct WorkspaceStore { workspaces: HashSet>, followers: Vec, client: Arc, - _subscriptions: Vec, + _subscriptions: Vec, } #[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 { use node_runtime::FakeNodeRuntime; - use settings2::SettingsStore; + use settings::SettingsStore; if !cx.has_global::() { 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, 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> { 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) -// ); -// }); - -// // 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) -// ); -// }); - -// // 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")); -// } - -// #[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()); -// } - -// #[gpui::test] -// async fn test_close_pane_items(cx: &mut TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); - -// let project = Project::test(fs, None, cx).await; -// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); -// let workspace = window.root(cx); - -// 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() -// }); - -// 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()); -// }); -// } - -// #[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::>(); -// 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(), -// ]) -// }); - -// // 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(); - -// left_pane -// }); - -// //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); -// }); - -// // 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. - -// 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); - -// 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); - -// cx.foreground().run_until_parked(); -// close.await.unwrap(); -// left_pane.read_with(cx, |pane, _| { -// assert_eq!(pane.items_len(), 0); -// }); -// } - -// #[gpui::test] -// async fn test_autosave(deterministic: Arc, 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 pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - -// 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); -// }); - -// // Autosave on window change. -// item.update(cx, |item, cx| { -// cx.update_global(|settings: &mut SettingsStore, cx| { -// settings.update_user_settings::(cx, |settings| { -// settings.autosave = Some(AutosaveSetting::OnWindowChange); -// }) -// }); -// item.is_dirty = true; -// }); - -// // 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::(cx, |settings| { -// settings.autosave = Some(AutosaveSetting::OnFocusChange); -// }) -// }); -// item.is_dirty = true; -// }); - -// // 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)); - -// // 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); - -// deterministic.run_until_parked(); -// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); - -// // Autosave after delay. -// item.update(cx, |item, cx| { -// cx.update_global(|settings: &mut SettingsStore, cx| { -// settings.update_user_settings::(cx, |settings| { -// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); -// }) -// }); -// item.is_dirty = true; -// cx.emit(TestItemEvent::Edit); -// }); - -// // 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)); - -// // 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)); - -// // 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::(cx, |settings| { -// settings.autosave = Some(AutosaveSetting::OnFocusChange); -// }) -// }); -// item.is_dirty = true; -// }); - -// 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)); - -// // 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)); -// } - -// #[gpui::test] -// async fn test_pane_navigation(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 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(); -// }); - -// pane.read_with(cx, |pane, _| { -// assert!(!pane.can_navigate_backward()); -// assert!(!pane.can_navigate_forward()); -// }); - -// item.update(cx, |item, cx| { -// item.set_state("one".to_string(), cx); -// }); - -// // Toolbar must be notified to re-render the navigation buttons -// assert_eq!(*toolbar_notify_count.borrow(), 1); - -// pane.read_with(cx, |pane, _| { -// assert!(pane.can_navigate_backward()); -// assert!(!pane.can_navigate_forward()); -// }); - -// workspace -// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx)) -// .await -// .unwrap(); - -// assert_eq!(*toolbar_notify_count.borrow(), 3); -// pane.read_with(cx, |pane, _| { -// assert!(!pane.can_navigate_backward()); -// assert!(pane.can_navigate_forward()); -// }); -// } - -// #[gpui::test] -// async fn test_toggle_docks_and_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 = workspace.update(cx, |workspace, cx| { -// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right)); -// workspace.add_panel(panel.clone(), cx); - -// workspace -// .right_dock() -// .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - -// panel -// }); - -// 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); -// }); - -// // Transfer focus from center to panel -// workspace.update(cx, |workspace, cx| { -// workspace.toggle_panel_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 from panel to center -// workspace.update(cx, |workspace, cx| { -// workspace.toggle_panel_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 -// 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)); -// }); - -// // 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)); -// }); - -// // 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!(panel.has_focus(cx)); -// }); - -// // Transfer focus to the center closes the dock -// workspace.update(cx, |workspace, cx| { -// workspace.toggle_panel_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::(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) -// }); - -// 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)); -// }); - -// // Opening the dock, when it's zoomed, retains focus -// 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!(workspace.zoomed.is_some()); -// 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)); - -// // 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()); -// }); -// } - -// #[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 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 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); -// }); - -// 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() -// ); -// }); - -// // Move panel_1 back to the left -// panel_1.update(cx, |panel_1, cx| { -// panel_1.set_position(DockPosition::Left, 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()); - -// // Now we move panel_1 to the bottom -// panel_1.set_position(DockPosition::Bottom, 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 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)); -// }); - -// // 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)); -// }); - -// // 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 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); -// }); - -// // 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); -// }); - -// // 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()); -// }); -// } - -// 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); -// }); -// } -// } +#[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() + // }); + + // 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) + // ); + // }); + + // // 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) + // ); + // }); + + // // 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")); + // } + + // #[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()); + // } + + // #[gpui::test] + // async fn test_close_pane_items(cx: &mut TestAppContext) { + // init_test(cx); + + // let fs = FakeFs::new(cx.background()); + + // let project = Project::test(fs, None, cx).await; + // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + // let workspace = window.root(cx); + + // 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() + // }); + + // 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()); + // }); + // } + + #[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::>(); + 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(), + ]) + }); + + // 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 right_pane = workspace + .split_and_clone(left_pane.clone(), SplitDirection::Right, cx) + .unwrap(); + + right_pane.update(cx, |pane, cx| { + pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx); + }); + + left_pane + }); + + cx.focus_view(&left_pane); + + // 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 close = left_pane.update(cx, |pane, cx| { + pane.close_all_items(&CloseAllItems::default(), cx).unwrap() + }); + cx.executor().run_until_parked(); + + // Discard "Save all" prompt + cx.simulate_prompt_answer(2); + + 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); + + 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); + + cx.executor().run_until_parked(); + close.await.unwrap(); + left_pane.update(cx, |pane, _| { + assert_eq!(pane.items_len(), 0); + }); + } + + #[gpui::test] + async fn test_autosave(cx: &mut gpui::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)); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + + 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); + }); + + // Autosave on window change. + item.update(cx, |item, cx| { + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnWindowChange); + }) + }); + item.is_dirty = true; + }); + + // 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::(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)); + + // 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(); + + cx.executor().run_until_parked(); + item.update(cx, |item, _| assert_eq!(item.save_count, 3)); + + // Autosave after delay. + item.update(cx, |item, cx| { + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); + }) + }); + item.is_dirty = true; + cx.emit(ItemEvent::Edit); + }); + + // 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)); + + // 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)); + + // 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::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) + }); + item.is_dirty = true; + }); + + 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)); + + // 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)); + } + + #[gpui::test] + async fn test_pane_navigation(cx: &mut gpui::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)); + + 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(); + }); + + pane.update(cx, |pane, _| { + assert!(!pane.can_navigate_backward()); + assert!(!pane.can_navigate_forward()); + }); + + item.update(cx, |item, cx| { + item.set_state("one".to_string(), cx); + }); + + // Toolbar must be notified to re-render the navigation buttons + assert_eq!(*toolbar_notify_count.borrow(), 1); + + pane.update(cx, |pane, _| { + assert!(pane.can_navigate_backward()); + assert!(!pane.can_navigate_forward()); + }); + + workspace + .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx)) + .await + .unwrap(); + + assert_eq!(*toolbar_notify_count.borrow(), 2); + pane.update(cx, |pane, _| { + assert!(!pane.can_navigate_backward()); + assert!(pane.can_navigate_forward()); + }); + } + + // #[gpui::test] + // async fn test_toggle_docks_and_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 = workspace.update(cx, |workspace, cx| { + // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right)); + // workspace.add_panel(panel.clone(), cx); + + // workspace + // .right_dock() + // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + // panel + // }); + + // 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); + // }); + + // // Transfer focus from center to panel + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_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 from panel to center + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_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 + // 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)); + // }); + + // // 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)); + // }); + + // // 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!(panel.has_focus(cx)); + // }); + + // // Transfer focus to the center closes the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_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::(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) + // }); + + // 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)); + // }); + + // // Opening the dock, when it's zoomed, retains focus + // 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!(workspace.zoomed.is_some()); + // 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)); + + // // 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()); + // }); + // } + + // #[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 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 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); + // }); + + // 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() + // ); + // }); + + // // Move panel_1 back to the left + // panel_1.update(cx, |panel_1, cx| { + // panel_1.set_position(DockPosition::Left, 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()); + + // // Now we move panel_1 to the bottom + // panel_1.set_position(DockPosition::Bottom, 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 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)); + // }); + + // // 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)); + // }); + + // // 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 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); + // }); + + // // 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); + // }); + + // // 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()); + // }); + // } + + 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); + }); + } +} diff --git a/crates/workspace2/src/workspace_settings.rs b/crates/workspace2/src/workspace_settings.rs index c4d1bb41cd..f3882a9dbd 100644 --- a/crates/workspace2/src/workspace_settings.rs +++ b/crates/workspace2/src/workspace_settings.rs @@ -1,6 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; #[derive(Deserialize)] pub struct WorkspaceSettings {