From d4d9fcc88c30d85d1181d99177cd1a6591da0663 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 15:39:58 +0100 Subject: [PATCH] WIP --- crates/workspace2/src/item.rs | 546 +-- crates/workspace2/src/pane.rs | 5016 ++++++++++---------- crates/workspace2/src/pane_group.rs | 82 +- crates/workspace2/src/searchable.rs | 56 +- crates/workspace2/src/workspace2.rs | 6738 ++++++++++++++------------- 5 files changed, 6236 insertions(+), 6202 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index e5d778778233b6b0e473ceeaf1c4da369cc69932..06592bffac32eb790c4d639500e8ab8e4ec20b84 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -3,7 +3,12 @@ // ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, // }; // use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; -// use anyhow::Result; +use anyhow::Result; +use client2::{ + proto::{self, PeerId, ViewId}, + Client, +}; +use theme2::Theme; // use client2::{ // proto::{self, PeerId}, // Client, @@ -78,218 +83,227 @@ // } // } -// #[derive(Eq, PartialEq, Hash, Debug)] -// pub enum ItemEvent { -// CloseItem, -// UpdateTab, -// UpdateBreadcrumbs, -// Edit, -// } - -// // TODO: Combine this with existing HighlightedText struct? -// pub struct BreadcrumbText { -// pub text: String, -// pub highlights: Option, HighlightStyle)>>, -// } - -// pub trait Item: View { -// fn deactivated(&mut self, _: &mut ViewContext) {} -// fn workspace_deactivated(&mut self, _: &mut ViewContext) {} -// fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { -// false -// } -// fn tab_tooltip_text(&self, _: &AppContext) -> Option> { -// None -// } -// fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { -// None -// } -// fn tab_content( -// &self, -// detail: Option, -// style: &theme2::Tab, -// cx: &AppContext, -// ) -> AnyElement; -// fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) { -// } // (model id, Item) -// fn is_singleton(&self, _cx: &AppContext) -> bool { -// false -// } -// fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} -// fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option -// where -// Self: Sized, -// { -// None -// } -// fn is_dirty(&self, _: &AppContext) -> bool { -// false -// } -// fn has_conflict(&self, _: &AppContext) -> bool { -// false -// } -// fn can_save(&self, _cx: &AppContext) -> bool { -// false -// } -// fn save( -// &mut self, -// _project: ModelHandle, -// _cx: &mut ViewContext, -// ) -> Task> { -// unimplemented!("save() must be implemented if can_save() returns true") -// } -// fn save_as( -// &mut self, -// _project: ModelHandle, -// _abs_path: PathBuf, -// _cx: &mut ViewContext, -// ) -> Task> { -// unimplemented!("save_as() must be implemented if can_save() returns true") -// } -// fn reload( -// &mut self, -// _project: ModelHandle, -// _cx: &mut ViewContext, -// ) -> Task> { -// unimplemented!("reload() must be implemented if can_save() returns true") -// } -// fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { -// SmallVec::new() -// } -// fn should_close_item_on_event(_: &Self::Event) -> bool { -// false -// } -// fn should_update_tab_on_event(_: &Self::Event) -> bool { -// false -// } - -// fn act_as_type<'a>( -// &'a self, -// type_id: TypeId, -// self_handle: &'a ViewHandle, -// _: &'a AppContext, -// ) -> Option<&AnyViewHandle> { -// if TypeId::of::() == type_id { -// Some(self_handle) -// } else { -// None -// } -// } - -// fn as_searchable(&self, _: &ViewHandle) -> Option> { -// None -// } - -// fn breadcrumb_location(&self) -> ToolbarItemLocation { -// ToolbarItemLocation::Hidden -// } - -// fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { -// None -// } - -// fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} - -// fn serialized_item_kind() -> Option<&'static str> { -// None -// } - -// fn deserialize( -// _project: ModelHandle, -// _workspace: WeakViewHandle, -// _workspace_id: WorkspaceId, -// _item_id: ItemId, -// _cx: &mut ViewContext, -// ) -> Task>> { -// unimplemented!( -// "deserialize() must be implemented if serialized_item_kind() returns Some(_)" -// ) -// } -// fn show_toolbar(&self) -> bool { -// true -// } -// fn pixel_position_of_cursor(&self, _: &AppContext) -> Option { -// None -// } -// } +#[derive(Eq, PartialEq, Hash, Debug)] +pub enum ItemEvent { + CloseItem, + UpdateTab, + UpdateBreadcrumbs, + Edit, +} -use core::fmt; +// TODO: Combine this with existing HighlightedText struct? +pub struct BreadcrumbText { + pub text: String, + pub highlights: Option, HighlightStyle)>>, +} -pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { - // fn subscribe_to_item_events( - // &self, - // cx: &mut WindowContext, - // handler: Box, - // ) -> gpui2::Subscription; - // fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option>; - // fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option>; - // fn tab_content( - // &self, - // detail: Option, - // style: &theme2::Tab, - // cx: &AppContext, - // ) -> AnyElement; - // fn dragged_tab_content( +pub trait Item: EventEmitter { + // fn deactivated(&mut self, _: &mut ViewContext) {} + // fn workspace_deactivated(&mut self, _: &mut ViewContext) {} + // fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { + // false + // } + // fn tab_tooltip_text(&self, _: &AppContext) -> Option> { + // None + // } + // fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { + // None + // } + // fn tab_content( // &self, // detail: Option, // style: &theme2::Tab, // cx: &AppContext, - // ) -> AnyElement; - // fn project_path(&self, cx: &AppContext) -> Option; - // fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; - // fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; - // fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)); - // fn is_singleton(&self, cx: &AppContext) -> bool; - // fn boxed_clone(&self) -> Box; - // fn clone_on_split( - // &self, - // workspace_id: WorkspaceId, - // cx: &mut WindowContext, - // ) -> Option>; - // fn added_to_pane( - // &self, - // workspace: &mut Workspace, - // pane: ViewHandle, - // cx: &mut ViewContext, - // ); - // fn deactivated(&self, cx: &mut WindowContext); - // fn workspace_deactivated(&self, cx: &mut WindowContext); - // fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool; - // fn id(&self) -> usize; - // fn window(&self) -> AnyWindowHandle; - // fn as_any(&self) -> &AnyViewHandle; - // fn is_dirty(&self, cx: &AppContext) -> bool; - // fn has_conflict(&self, cx: &AppContext) -> bool; - // fn can_save(&self, cx: &AppContext) -> bool; - // fn save(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; + // ) -> AnyElement; + // fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) { + // } // (model id, Item) + fn is_singleton(&self, _cx: &AppContext) -> bool { + false + } + // fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} + // fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option + // where + // Self: Sized, + // { + // None + // } + // fn is_dirty(&self, _: &AppContext) -> bool { + // false + // } + // fn has_conflict(&self, _: &AppContext) -> bool { + // false + // } + // fn can_save(&self, _cx: &AppContext) -> bool { + // false + // } + // fn save( + // &mut self, + // _project: ModelHandle, + // _cx: &mut ViewContext, + // ) -> Task> { + // unimplemented!("save() must be implemented if can_save() returns true") + // } // fn save_as( - // &self, - // project: ModelHandle, - // abs_path: PathBuf, - // cx: &mut WindowContext, - // ) -> Task>; - // fn reload(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; - // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; - // fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; - // fn on_release( - // &self, - // cx: &mut AppContext, - // callback: Box, - // ) -> gpui2::Subscription; - // fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; - // fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; - // fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; - // fn serialized_item_kind(&self) -> Option<&'static str>; - // fn show_toolbar(&self, cx: &AppContext) -> bool; - // fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option; + // &mut self, + // _project: ModelHandle, + // _abs_path: PathBuf, + // _cx: &mut ViewContext, + // ) -> Task> { + // unimplemented!("save_as() must be implemented if can_save() returns true") + // } + // fn reload( + // &mut self, + // _project: ModelHandle, + // _cx: &mut ViewContext, + // ) -> Task> { + // unimplemented!("reload() must be implemented if can_save() returns true") + // } + // fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + // SmallVec::new() + // } + // fn should_close_item_on_event(_: &Self::Event) -> bool { + // false + // } + // fn should_update_tab_on_event(_: &Self::Event) -> bool { + // false + // } + + // fn act_as_type<'a>( + // &'a self, + // type_id: TypeId, + // self_handle: &'a ViewHandle, + // _: &'a AppContext, + // ) -> Option<&AnyViewHandle> { + // if TypeId::of::() == type_id { + // Some(self_handle) + // } else { + // None + // } + // } + + // fn as_searchable(&self, _: &ViewHandle) -> Option> { + // None + // } + + // fn breadcrumb_location(&self) -> ToolbarItemLocation { + // ToolbarItemLocation::Hidden + // } + + // fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { + // None + // } + + // fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} + + // fn serialized_item_kind() -> Option<&'static str> { + // None + // } + + // fn deserialize( + // _project: ModelHandle, + // _workspace: WeakViewHandle, + // _workspace_id: WorkspaceId, + // _item_id: ItemId, + // _cx: &mut ViewContext, + // ) -> Task>> { + // unimplemented!( + // "deserialize() must be implemented if serialized_item_kind() returns Some(_)" + // ) + // } + // fn show_toolbar(&self) -> bool { + // true + // } + // fn pixel_position_of_cursor(&self, _: &AppContext) -> Option { + // None + // } } -// pub trait WeakItemHandle { -// fn id(&self) -> usize; -// fn window(&self) -> AnyWindowHandle; -// fn upgrade(&self, cx: &AppContext) -> Option>; -// } +use core::fmt; +use std::{ + any::{Any, TypeId}, + borrow::Cow, + ops::Range, + path::PathBuf, + sync::Arc, +}; + +use gpui2::{ + AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, + Point, Task, View, ViewContext, WindowContext, +}; +use project2::{Project, ProjectEntryId, ProjectPath}; +use smallvec::SmallVec; + +use crate::{ + pane::Pane, searchable::SearchableItemHandle, ToolbarItemLocation, Workspace, WorkspaceId, +}; + +pub trait ItemHandle: 'static + fmt::Debug + Send { + fn subscribe_to_item_events( + &self, + cx: &mut WindowContext, + handler: Box, + ) -> gpui2::Subscription; + fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option>; + fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option>; + fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + fn dragged_tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + fn project_path(&self, cx: &AppContext) -> Option; + fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)); + fn is_singleton(&self, cx: &AppContext) -> bool; + fn boxed_clone(&self) -> Box; + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut WindowContext, + ) -> Option>; + fn added_to_pane( + &self, + workspace: &mut Workspace, + pane: View, + cx: &mut ViewContext, + ); + fn deactivated(&self, cx: &mut WindowContext); + fn workspace_deactivated(&self, cx: &mut WindowContext); + fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool; + fn id(&self) -> usize; + fn window(&self) -> AnyWindowHandle; + // fn as_any(&self) -> &AnyView; todo!() + fn is_dirty(&self, cx: &AppContext) -> bool; + fn has_conflict(&self, cx: &AppContext) -> bool; + fn can_save(&self, cx: &AppContext) -> bool; + fn save(&self, project: Handle, cx: &mut WindowContext) -> Task>; + fn save_as( + &self, + project: Handle, + abs_path: PathBuf, + cx: &mut WindowContext, + ) -> Task>; + fn reload(&self, project: Handle, cx: &mut WindowContext) -> Task>; + // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!() + fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; + fn on_release( + &self, + cx: &mut AppContext, + callback: Box, + ) -> gpui2::Subscription; + fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; + fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; + fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; + fn serialized_item_kind(&self) -> Option<&'static str>; + fn show_toolbar(&self, cx: &AppContext) -> bool; + fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option>; +} + +pub trait WeakItemHandle { + fn id(&self) -> usize; + fn window(&self) -> AnyWindowHandle; + fn upgrade(&self, cx: &AppContext) -> Option>; +} +// todo!() // impl dyn ItemHandle { // pub fn downcast(&self) -> Option> { // self.as_any().clone().downcast() @@ -653,11 +667,11 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { // } // } -// impl Clone for Box { -// fn clone(&self) -> Box { -// self.boxed_clone() -// } -// } +impl Clone for Box { + fn clone(&self) -> Box { + self.boxed_clone() + } +} // impl WeakItemHandle for WeakViewHandle { // fn id(&self) -> usize { @@ -673,63 +687,65 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { // } // } -// pub trait ProjectItem: Item { -// type Item: project2::Item + gpui2::Entity; +pub trait ProjectItem: Item { + type Item: project2::Item; -// fn for_project_item( -// project: ModelHandle, -// item: ModelHandle, -// cx: &mut ViewContext, -// ) -> Self; -// } - -// pub trait FollowableItem: Item { -// fn remote_id(&self) -> Option; -// fn to_state_proto(&self, cx: &AppContext) -> Option; -// fn from_state_proto( -// pane: ViewHandle, -// project: ViewHandle, -// id: ViewId, -// state: &mut Option, -// cx: &mut AppContext, -// ) -> Option>>>; -// fn add_event_to_update_proto( -// &self, -// event: &Self::Event, -// update: &mut Option, -// cx: &AppContext, -// ) -> bool; -// fn apply_update_proto( -// &mut self, -// project: &ModelHandle, -// message: proto::update_view::Variant, -// cx: &mut ViewContext, -// ) -> Task>; -// fn is_project_item(&self, cx: &AppContext) -> bool; + fn for_project_item( + project: Handle, + item: Handle, + cx: &mut ViewContext, + ) -> Self + where + Self: Sized; +} -// fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); -// fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; -// } +pub trait FollowableItem: Item { + fn remote_id(&self) -> Option; + fn to_state_proto(&self, cx: &AppContext) -> Option; + fn from_state_proto( + pane: View, + project: View, + id: ViewId, + state: &mut Option, + cx: &mut AppContext, + ) -> Option>>>; + fn add_event_to_update_proto( + &self, + event: &Self::Event, + update: &mut Option, + cx: &AppContext, + ) -> bool; + fn apply_update_proto( + &mut self, + project: &Handle, + message: proto::update_view::Variant, + cx: &mut ViewContext, + ) -> Task>; + fn is_project_item(&self, cx: &AppContext) -> bool; + + fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); + fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; +} -// pub trait FollowableItemHandle: ItemHandle { -// fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; -// fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); -// fn to_state_proto(&self, cx: &AppContext) -> Option; -// fn add_event_to_update_proto( -// &self, -// event: &dyn Any, -// update: &mut Option, -// cx: &AppContext, -// ) -> bool; -// fn apply_update_proto( -// &self, -// project: &ModelHandle, -// message: proto::update_view::Variant, -// cx: &mut WindowContext, -// ) -> Task>; -// fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; -// fn is_project_item(&self, cx: &AppContext) -> bool; -// } +pub trait FollowableItemHandle: ItemHandle { + fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; + fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); + fn to_state_proto(&self, cx: &AppContext) -> Option; + fn add_event_to_update_proto( + &self, + event: &dyn Any, + update: &mut Option, + cx: &AppContext, + ) -> bool; + fn apply_update_proto( + &self, + project: &Handle, + message: proto::update_view::Variant, + cx: &mut WindowContext, + ) -> Task>; + fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; + fn is_project_item(&self, cx: &AppContext) -> bool; +} // impl FollowableItemHandle for ViewHandle { // fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { @@ -981,9 +997,9 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { // .for_each(|item| f(item.id(), item.read(cx))) // } -// fn is_singleton(&self, _: &AppContext) -> bool { -// self.is_singleton -// } +// fn is_singleton(&self, _: &AppContext) -> bool { +// self.is_singleton +// } // fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { // self.nav_history = Some(history); diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index b1fbae1987844f4f1fd8c73a310a5c5f4a53ba0a..44420714c72911eb9af30e164edf9d33d10d38b4 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1,150 +1,150 @@ -mod dragged_item_receiver; - -use super::{ItemHandle, SplitDirection}; -pub use crate::toolbar::Toolbar; -use crate::{ - item::{ItemSettings, WeakItemHandle}, - notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom, - Workspace, WorkspaceSettings, -}; -use anyhow::Result; -use collections::{HashMap, HashSet, VecDeque}; -// use context_menu::{ContextMenu, ContextMenuItem}; - -use dragged_item_receiver::dragged_item_receiver; -use fs2::repository::GitFileStatus; -use futures::StreamExt; -use gpui2::{ - actions, - elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, - impl_actions, - keymap_matcher::KeymapContext, - platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel}, - Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, - ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, -}; -use project2::{Project, ProjectEntryId, ProjectPath}; -use serde::Deserialize; -use std::{ - any::Any, - cell::RefCell, - cmp, mem, - path::{Path, PathBuf}, - rc::Rc, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, -}; -use theme2::{Theme, ThemeSettings}; -use util::truncate_and_remove_front; - -#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub enum SaveIntent { - /// write all files (even if unchanged) - /// prompt before overwriting on-disk changes - Save, - /// write any files that have local changes - /// prompt before overwriting on-disk changes - SaveAll, - /// always prompt for a new path - SaveAs, - /// prompt "you have unsaved changes" before writing - Close, - /// write all dirty files, don't prompt on conflict - Overwrite, - /// skip all save-related behavior - Skip, -} - -#[derive(Clone, Deserialize, PartialEq)] -pub struct ActivateItem(pub usize); - -#[derive(Clone, PartialEq)] -pub struct CloseItemById { - pub item_id: usize, - pub pane: WeakViewHandle, -} - -#[derive(Clone, PartialEq)] -pub struct CloseItemsToTheLeftById { - pub item_id: usize, - pub pane: WeakViewHandle, -} - -#[derive(Clone, PartialEq)] -pub struct CloseItemsToTheRightById { - pub item_id: usize, - pub pane: WeakViewHandle, -} - -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct CloseActiveItem { - pub save_intent: Option, -} - -#[derive(Clone, PartialEq, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CloseAllItems { - pub save_intent: Option, -} - -actions!( - pane, - [ - ActivatePrevItem, - ActivateNextItem, - ActivateLastItem, - CloseInactiveItems, - CloseCleanItems, - CloseItemsToTheLeft, - CloseItemsToTheRight, - GoBack, - GoForward, - ReopenClosedItem, - SplitLeft, - SplitUp, - SplitRight, - SplitDown, - ] -); - -impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); - -const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; - -pub fn init(cx: &mut AppContext) { - cx.add_action(Pane::toggle_zoom); - cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { - pane.activate_item(action.0, true, true, cx); - }); - cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { - pane.activate_item(pane.items.len() - 1, true, true, cx); - }); - cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { - pane.activate_prev_item(true, cx); - }); - cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| { - pane.activate_next_item(true, cx); - }); - cx.add_async_action(Pane::close_active_item); - cx.add_async_action(Pane::close_inactive_items); - cx.add_async_action(Pane::close_clean_items); - cx.add_async_action(Pane::close_items_to_the_left); - cx.add_async_action(Pane::close_items_to_the_right); - cx.add_async_action(Pane::close_all_items); - cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)); - cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)); - cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)); - cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)); -} +// mod dragged_item_receiver; + +// use super::{ItemHandle, SplitDirection}; +// pub use crate::toolbar::Toolbar; +// use crate::{ +// item::{ItemSettings, WeakItemHandle}, +// notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom, +// Workspace, WorkspaceSettings, +// }; +// use anyhow::Result; +// use collections::{HashMap, HashSet, VecDeque}; +// // use context_menu::{ContextMenu, ContextMenuItem}; + +// use dragged_item_receiver::dragged_item_receiver; +// use fs2::repository::GitFileStatus; +// use futures::StreamExt; +// use gpui2::{ +// actions, +// elements::*, +// geometry::{ +// rect::RectF, +// vector::{vec2f, Vector2F}, +// }, +// impl_actions, +// keymap_matcher::KeymapContext, +// platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel}, +// Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, +// ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, +// WindowContext, +// }; +// use project2::{Project, ProjectEntryId, ProjectPath}; +// use serde::Deserialize; +// use std::{ +// any::Any, +// cell::RefCell, +// cmp, mem, +// path::{Path, PathBuf}, +// rc::Rc, +// sync::{ +// atomic::{AtomicUsize, Ordering}, +// Arc, +// }, +// }; +// use theme2::{Theme, ThemeSettings}; +// use util::truncate_and_remove_front; + +// #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] +// #[serde(rename_all = "camelCase")] +// pub enum SaveIntent { +// /// write all files (even if unchanged) +// /// prompt before overwriting on-disk changes +// Save, +// /// write any files that have local changes +// /// prompt before overwriting on-disk changes +// SaveAll, +// /// always prompt for a new path +// SaveAs, +// /// prompt "you have unsaved changes" before writing +// Close, +// /// write all dirty files, don't prompt on conflict +// Overwrite, +// /// skip all save-related behavior +// Skip, +// } + +// #[derive(Clone, Deserialize, PartialEq)] +// pub struct ActivateItem(pub usize); + +// #[derive(Clone, PartialEq)] +// pub struct CloseItemById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } + +// #[derive(Clone, PartialEq)] +// pub struct CloseItemsToTheLeftById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } + +// #[derive(Clone, PartialEq)] +// pub struct CloseItemsToTheRightById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize, Default)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseActiveItem { +// pub save_intent: Option, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseAllItems { +// pub save_intent: Option, +// } + +// actions!( +// pane, +// [ +// ActivatePrevItem, +// ActivateNextItem, +// ActivateLastItem, +// CloseInactiveItems, +// CloseCleanItems, +// CloseItemsToTheLeft, +// CloseItemsToTheRight, +// GoBack, +// GoForward, +// ReopenClosedItem, +// SplitLeft, +// SplitUp, +// SplitRight, +// SplitDown, +// ] +// ); + +// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); + +// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; + +// pub fn init(cx: &mut AppContext) { +// cx.add_action(Pane::toggle_zoom); +// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { +// pane.activate_item(action.0, true, true, cx); +// }); +// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { +// pane.activate_item(pane.items.len() - 1, true, true, cx); +// }); +// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { +// pane.activate_prev_item(true, cx); +// }); +// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| { +// pane.activate_next_item(true, cx); +// }); +// cx.add_async_action(Pane::close_active_item); +// cx.add_async_action(Pane::close_inactive_items); +// cx.add_async_action(Pane::close_clean_items); +// cx.add_async_action(Pane::close_items_to_the_left); +// cx.add_async_action(Pane::close_items_to_the_right); +// cx.add_async_action(Pane::close_all_items); +// cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)); +// cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)); +// cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)); +// cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)); +// } #[derive(Debug)] pub enum Event { @@ -159,23 +159,36 @@ pub enum Event { ZoomOut, } +use crate::item::{ItemHandle, WeakItemHandle}; +use collections::{HashMap, VecDeque}; +use gpui2::{Handle, ViewContext, WeakView}; +use project2::{Project, ProjectEntryId, ProjectPath}; +use std::{ + any::Any, + cell::RefCell, + cmp, mem, + path::PathBuf, + rc::Rc, + sync::{atomic::AtomicUsize, Arc}, +}; + pub struct Pane { items: Vec>, - activation_history: Vec, - zoomed: bool, - active_item_index: usize, - last_focused_view_by_item: HashMap, - autoscroll: bool, + // activation_history: Vec, + // zoomed: bool, + // active_item_index: usize, + // last_focused_view_by_item: HashMap, + // autoscroll: bool, nav_history: NavHistory, - toolbar: ViewHandle, - tab_bar_context_menu: TabBarContextMenu, - tab_context_menu: ViewHandle, - workspace: WeakViewHandle, - project: ModelHandle, - has_focus: bool, - can_drop: Rc, &WindowContext) -> bool>, - can_split: bool, - render_tab_bar_buttons: Rc) -> AnyElement>, + // toolbar: ViewHandle, + // tab_bar_context_menu: TabBarContextMenu, + // tab_context_menu: ViewHandle, + // workspace: WeakViewHandle, + project: Handle, + // has_focus: bool, + // can_drop: Rc, &WindowContext) -> bool>, + // can_split: bool, + // render_tab_bar_buttons: Rc) -> AnyElement>, } pub struct ItemNavHistory { @@ -192,7 +205,7 @@ struct NavHistoryState { forward_stack: VecDeque, closed_stack: VecDeque, paths_by_item: HashMap)>, - pane: WeakViewHandle, + pane: WeakView, next_timestamp: Arc, } @@ -218,261 +231,261 @@ pub struct NavigationEntry { pub timestamp: usize, } -pub struct DraggedItem { - pub handle: Box, - pub pane: WeakViewHandle, -} - -pub enum ReorderBehavior { - None, - MoveAfterActive, - MoveToIndex(usize), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TabBarContextMenuKind { - New, - Split, -} - -struct TabBarContextMenu { - kind: TabBarContextMenuKind, - handle: ViewHandle, -} - -impl TabBarContextMenu { - fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { - if self.kind == kind { - return Some(self.handle.clone()); - } - None - } -} - -#[allow(clippy::too_many_arguments)] -fn nav_button)>( - svg_path: &'static str, - style: theme2::Interactive, - nav_button_height: f32, - tooltip_style: TooltipStyle, - enabled: bool, - on_click: F, - tooltip_action: A, - action_name: &str, - cx: &mut ViewContext, -) -> AnyElement { - MouseEventHandler::new::(0, cx, |state, _| { - let style = if enabled { - style.style_for(state) - } else { - style.disabled_style() - }; - Svg::new(svg_path) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .contained() - .with_style(style.container) - .constrained() - .with_width(style.button_width) - .with_height(nav_button_height) - .aligned() - .top() - }) - .with_cursor_style(if enabled { - CursorStyle::PointingHand - } else { - CursorStyle::default() - }) - .on_click(MouseButton::Left, move |_, toolbar, cx| { - on_click(toolbar, cx) - }) - .with_tooltip::( - 0, - action_name.to_string(), - Some(Box::new(tooltip_action)), - tooltip_style, - cx, - ) - .contained() - .into_any_named("nav button") -} +// pub struct DraggedItem { +// pub handle: Box, +// pub pane: WeakViewHandle, +// } + +// pub enum ReorderBehavior { +// None, +// MoveAfterActive, +// MoveToIndex(usize), +// } + +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// enum TabBarContextMenuKind { +// New, +// Split, +// } + +// struct TabBarContextMenu { +// kind: TabBarContextMenuKind, +// handle: ViewHandle, +// } + +// impl TabBarContextMenu { +// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { +// if self.kind == kind { +// return Some(self.handle.clone()); +// } +// None +// } +// } + +// #[allow(clippy::too_many_arguments)] +// fn nav_button)>( +// svg_path: &'static str, +// style: theme2::Interactive, +// nav_button_height: f32, +// tooltip_style: TooltipStyle, +// enabled: bool, +// on_click: F, +// tooltip_action: A, +// action_name: &str, +// cx: &mut ViewContext, +// ) -> AnyElement { +// MouseEventHandler::new::(0, cx, |state, _| { +// let style = if enabled { +// style.style_for(state) +// } else { +// style.disabled_style() +// }; +// Svg::new(svg_path) +// .with_color(style.color) +// .constrained() +// .with_width(style.icon_width) +// .aligned() +// .contained() +// .with_style(style.container) +// .constrained() +// .with_width(style.button_width) +// .with_height(nav_button_height) +// .aligned() +// .top() +// }) +// .with_cursor_style(if enabled { +// CursorStyle::PointingHand +// } else { +// CursorStyle::default() +// }) +// .on_click(MouseButton::Left, move |_, toolbar, cx| { +// on_click(toolbar, cx) +// }) +// .with_tooltip::( +// 0, +// action_name.to_string(), +// Some(Box::new(tooltip_action)), +// tooltip_style, +// cx, +// ) +// .contained() +// .into_any_named("nav button") +// } impl Pane { - pub fn new( - workspace: WeakViewHandle, - project: ModelHandle, - next_timestamp: Arc, - cx: &mut ViewContext, - ) -> Self { - let pane_view_id = cx.view_id(); - let handle = cx.weak_handle(); - let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); - context_menu.update(cx, |menu, _| { - menu.set_position_mode(OverlayPositionMode::Local) - }); - - Self { - items: Vec::new(), - activation_history: Vec::new(), - zoomed: false, - active_item_index: 0, - last_focused_view_by_item: Default::default(), - autoscroll: false, - nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState { - mode: NavigationMode::Normal, - backward_stack: Default::default(), - forward_stack: Default::default(), - closed_stack: Default::default(), - paths_by_item: Default::default(), - pane: handle.clone(), - next_timestamp, - }))), - toolbar: cx.add_view(|_| Toolbar::new()), - tab_bar_context_menu: TabBarContextMenu { - kind: TabBarContextMenuKind::New, - handle: context_menu, - }, - tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), - workspace, - project, - has_focus: false, - can_drop: Rc::new(|_, _| true), - can_split: true, - render_tab_bar_buttons: Rc::new(move |pane, cx| { - Flex::row() - // New menu - .with_child(Self::render_tab_bar_button( - 0, - "icons/plus.svg", - false, - Some(("New...".into(), None)), - cx, - |pane, cx| pane.deploy_new_menu(cx), - |pane, cx| { - pane.tab_bar_context_menu - .handle - .update(cx, |menu, _| menu.delay_cancel()) - }, - pane.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::New), - )) - .with_child(Self::render_tab_bar_button( - 1, - "icons/split.svg", - false, - Some(("Split Pane".into(), None)), - cx, - |pane, cx| pane.deploy_split_menu(cx), - |pane, cx| { - pane.tab_bar_context_menu - .handle - .update(cx, |menu, _| menu.delay_cancel()) - }, - pane.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Split), - )) - .with_child({ - let icon_path; - let tooltip_label; - if pane.is_zoomed() { - icon_path = "icons/minimize.svg"; - tooltip_label = "Zoom In"; - } else { - icon_path = "icons/maximize.svg"; - tooltip_label = "Zoom In"; - } - - Pane::render_tab_bar_button( - 2, - icon_path, - pane.is_zoomed(), - Some((tooltip_label, Some(Box::new(ToggleZoom)))), - cx, - move |pane, cx| pane.toggle_zoom(&Default::default(), cx), - move |_, _| {}, - None, - ) - }) - .into_any() - }), - } - } - - pub(crate) fn workspace(&self) -> &WeakViewHandle { - &self.workspace - } - - pub fn has_focus(&self) -> bool { - self.has_focus - } - - pub fn active_item_index(&self) -> usize { - self.active_item_index - } - - pub fn on_can_drop(&mut self, can_drop: F) - where - F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, - { - self.can_drop = Rc::new(can_drop); - } - - pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { - self.can_split = can_split; - cx.notify(); - } - - pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { - self.toolbar.update(cx, |toolbar, cx| { - toolbar.set_can_navigate(can_navigate, cx); - }); - cx.notify(); - } - - pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) - where - F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, - { - self.render_tab_bar_buttons = Rc::new(render); - cx.notify(); - } - - pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { - ItemNavHistory { - history: self.nav_history.clone(), - item: Rc::new(item.downgrade()), - } - } - - pub fn nav_history(&self) -> &NavHistory { - &self.nav_history - } - - pub fn nav_history_mut(&mut self) -> &mut NavHistory { - &mut self.nav_history - } - - pub fn disable_history(&mut self) { - self.nav_history.disable(); - } - - pub fn enable_history(&mut self) { - self.nav_history.enable(); - } - - pub fn can_navigate_backward(&self) -> bool { - !self.nav_history.0.borrow().backward_stack.is_empty() - } - - pub fn can_navigate_forward(&self) -> bool { - !self.nav_history.0.borrow().forward_stack.is_empty() - } - - fn history_updated(&mut self, cx: &mut ViewContext) { - self.toolbar.update(cx, |_, cx| cx.notify()); - } + // pub fn new( + // workspace: WeakViewHandle, + // project: ModelHandle, + // next_timestamp: Arc, + // cx: &mut ViewContext, + // ) -> Self { + // let pane_view_id = cx.view_id(); + // let handle = cx.weak_handle(); + // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); + // context_menu.update(cx, |menu, _| { + // menu.set_position_mode(OverlayPositionMode::Local) + // }); + + // Self { + // items: Vec::new(), + // activation_history: Vec::new(), + // zoomed: false, + // active_item_index: 0, + // last_focused_view_by_item: Default::default(), + // autoscroll: false, + // nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState { + // mode: NavigationMode::Normal, + // backward_stack: Default::default(), + // forward_stack: Default::default(), + // closed_stack: Default::default(), + // paths_by_item: Default::default(), + // pane: handle.clone(), + // next_timestamp, + // }))), + // toolbar: cx.add_view(|_| Toolbar::new()), + // tab_bar_context_menu: TabBarContextMenu { + // kind: TabBarContextMenuKind::New, + // handle: context_menu, + // }, + // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), + // workspace, + // project, + // has_focus: false, + // can_drop: Rc::new(|_, _| true), + // can_split: true, + // render_tab_bar_buttons: Rc::new(move |pane, cx| { + // Flex::row() + // // New menu + // .with_child(Self::render_tab_bar_button( + // 0, + // "icons/plus.svg", + // false, + // Some(("New...".into(), None)), + // cx, + // |pane, cx| pane.deploy_new_menu(cx), + // |pane, cx| { + // pane.tab_bar_context_menu + // .handle + // .update(cx, |menu, _| menu.delay_cancel()) + // }, + // pane.tab_bar_context_menu + // .handle_if_kind(TabBarContextMenuKind::New), + // )) + // .with_child(Self::render_tab_bar_button( + // 1, + // "icons/split.svg", + // false, + // Some(("Split Pane".into(), None)), + // cx, + // |pane, cx| pane.deploy_split_menu(cx), + // |pane, cx| { + // pane.tab_bar_context_menu + // .handle + // .update(cx, |menu, _| menu.delay_cancel()) + // }, + // pane.tab_bar_context_menu + // .handle_if_kind(TabBarContextMenuKind::Split), + // )) + // .with_child({ + // let icon_path; + // let tooltip_label; + // if pane.is_zoomed() { + // icon_path = "icons/minimize.svg"; + // tooltip_label = "Zoom In"; + // } else { + // icon_path = "icons/maximize.svg"; + // tooltip_label = "Zoom In"; + // } + + // Pane::render_tab_bar_button( + // 2, + // icon_path, + // pane.is_zoomed(), + // Some((tooltip_label, Some(Box::new(ToggleZoom)))), + // cx, + // move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + // move |_, _| {}, + // None, + // ) + // }) + // .into_any() + // }), + // } + // } + + // pub(crate) fn workspace(&self) -> &WeakViewHandle { + // &self.workspace + // } + + // pub fn has_focus(&self) -> bool { + // self.has_focus + // } + + // pub fn active_item_index(&self) -> usize { + // self.active_item_index + // } + + // pub fn on_can_drop(&mut self, can_drop: F) + // where + // F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + // { + // self.can_drop = Rc::new(can_drop); + // } + + // pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + // self.can_split = can_split; + // cx.notify(); + // } + + // pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.set_can_navigate(can_navigate, cx); + // }); + // cx.notify(); + // } + + // pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + // where + // F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + // { + // self.render_tab_bar_buttons = Rc::new(render); + // cx.notify(); + // } + + // pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { + // ItemNavHistory { + // history: self.nav_history.clone(), + // item: Rc::new(item.downgrade()), + // } + // } + + // pub fn nav_history(&self) -> &NavHistory { + // &self.nav_history + // } + + // pub fn nav_history_mut(&mut self) -> &mut NavHistory { + // &mut self.nav_history + // } + + // pub fn disable_history(&mut self) { + // self.nav_history.disable(); + // } + + // pub fn enable_history(&mut self) { + // self.nav_history.enable(); + // } + + // pub fn can_navigate_backward(&self) -> bool { + // !self.nav_history.0.borrow().backward_stack.is_empty() + // } + + // pub fn can_navigate_forward(&self) -> bool { + // !self.nav_history.0.borrow().forward_stack.is_empty() + // } + + // fn history_updated(&mut self, cx: &mut ViewContext) { + // self.toolbar.update(cx, |_, cx| cx.notify()); + // } pub(crate) fn open_item( &mut self, @@ -599,63 +612,63 @@ impl Pane { cx.emit(Event::AddItem { item }); } - pub fn items_len(&self) -> usize { - self.items.len() - } - - pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { - self.items.iter() - } - - pub fn items_of_type(&self) -> impl '_ + Iterator> { - self.items - .iter() - .filter_map(|item| item.as_any().clone().downcast()) - } - - pub fn active_item(&self) -> Option> { - self.items.get(self.active_item_index).cloned() - } - - pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { - self.items - .get(self.active_item_index)? - .pixel_position_of_cursor(cx) - } - - pub fn item_for_entry( - &self, - entry_id: ProjectEntryId, - cx: &AppContext, - ) -> Option> { - self.items.iter().find_map(|item| { - if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { - Some(item.boxed_clone()) - } else { - None - } - }) - } - - pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { - self.items.iter().position(|i| i.id() == item.id()) - } - - pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // Potentially warn the user of the new keybinding - let workspace_handle = self.workspace().clone(); - cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) - .detach(); - - if self.zoomed { - cx.emit(Event::ZoomOut); - } else if !self.items.is_empty() { - if !self.has_focus { - cx.focus_self(); - } - cx.emit(Event::ZoomIn); - } - } + // pub fn items_len(&self) -> usize { + // self.items.len() + // } + + // pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { + // self.items.iter() + // } + + // pub fn items_of_type(&self) -> impl '_ + Iterator> { + // self.items + // .iter() + // .filter_map(|item| item.as_any().clone().downcast()) + // } + + // pub fn active_item(&self) -> Option> { + // self.items.get(self.active_item_index).cloned() + // } + + // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { + // self.items + // .get(self.active_item_index)? + // .pixel_position_of_cursor(cx) + // } + + // pub fn item_for_entry( + // &self, + // entry_id: ProjectEntryId, + // cx: &AppContext, + // ) -> Option> { + // self.items.iter().find_map(|item| { + // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + // Some(item.boxed_clone()) + // } else { + // None + // } + // }) + // } + + // pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { + // self.items.iter().position(|i| i.id() == item.id()) + // } + + // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // // Potentially warn the user of the new keybinding + // let workspace_handle = self.workspace().clone(); + // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) + // .detach(); + + // if self.zoomed { + // cx.emit(Event::ZoomOut); + // } else if !self.items.is_empty() { + // if !self.has_focus { + // cx.focus_self(); + // } + // cx.emit(Event::ZoomIn); + // } + // } pub fn activate_item( &mut self, @@ -699,2039 +712,2040 @@ impl Pane { } } - pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { - let mut index = self.active_item_index; - if index > 0 { - index -= 1; - } else if !self.items.is_empty() { - index = self.items.len() - 1; - } - self.activate_item(index, activate_pane, activate_pane, cx); - } - - pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { - let mut index = self.active_item_index; - if index + 1 < self.items.len() { - index += 1; - } else { - index = 0; - } - self.activate_item(index, activate_pane, activate_pane, cx); - } - - pub fn close_active_item( - &mut self, - action: &CloseActiveItem, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_item_by_id( - active_item_id, - action.save_intent.unwrap_or(SaveIntent::Close), - cx, - )) - } - - pub fn close_item_by_id( - &mut self, - item_id_to_close: usize, - save_intent: SaveIntent, - cx: &mut ViewContext, - ) -> Task> { - self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) - } - - pub fn close_inactive_items( - &mut self, - _: &CloseInactiveItems, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items(cx, SaveIntent::Close, move |item_id| { - item_id != active_item_id - })) - } - - pub fn close_clean_items( - &mut self, - _: &CloseCleanItems, - cx: &mut ViewContext, - ) -> Option>> { - let item_ids: Vec<_> = self - .items() - .filter(|item| !item.is_dirty(cx)) - .map(|item| item.id()) - .collect(); - Some(self.close_items(cx, SaveIntent::Close, move |item_id| { - item_ids.contains(&item_id) - })) - } - - pub fn close_items_to_the_left( - &mut self, - _: &CloseItemsToTheLeft, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items_to_the_left_by_id(active_item_id, cx)) - } - - pub fn close_items_to_the_left_by_id( - &mut self, - item_id: usize, - cx: &mut ViewContext, - ) -> Task> { - let item_ids: Vec<_> = self - .items() - .take_while(|item| item.id() != item_id) - .map(|item| item.id()) - .collect(); - self.close_items(cx, SaveIntent::Close, move |item_id| { - item_ids.contains(&item_id) - }) - } - - pub fn close_items_to_the_right( - &mut self, - _: &CloseItemsToTheRight, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items_to_the_right_by_id(active_item_id, cx)) - } - - pub fn close_items_to_the_right_by_id( - &mut self, - item_id: usize, - cx: &mut ViewContext, - ) -> Task> { - let item_ids: Vec<_> = self - .items() - .rev() - .take_while(|item| item.id() != item_id) - .map(|item| item.id()) - .collect(); - self.close_items(cx, SaveIntent::Close, move |item_id| { - item_ids.contains(&item_id) - }) - } - - pub fn close_all_items( - &mut self, - action: &CloseAllItems, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - - Some( - self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { - true - }), - ) - } - - pub(super) fn file_names_for_prompt( - items: &mut dyn Iterator>, - all_dirty_items: usize, - cx: &AppContext, - ) -> String { - /// Quantity of item paths displayed in prompt prior to cutoff.. - const FILE_NAMES_CUTOFF_POINT: usize = 10; - let mut file_names: Vec<_> = items - .filter_map(|item| { - item.project_path(cx).and_then(|project_path| { - project_path - .path - .file_name() - .and_then(|name| name.to_str().map(ToOwned::to_owned)) - }) - }) - .take(FILE_NAMES_CUTOFF_POINT) - .collect(); - let should_display_followup_text = - all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; - if should_display_followup_text { - let not_shown_files = all_dirty_items - file_names.len(); - if not_shown_files == 1 { - file_names.push(".. 1 file not shown".into()); - } else { - file_names.push(format!(".. {} files not shown", not_shown_files).into()); - } - } - let file_names = file_names.join("\n"); - format!( - "Do you want to save changes to the following {} files?\n{file_names}", - all_dirty_items - ) - } - - pub fn close_items( - &mut self, - cx: &mut ViewContext, - mut save_intent: SaveIntent, - should_close: impl 'static + Fn(usize) -> bool, - ) -> Task> { - // Find the items to close. - let mut items_to_close = Vec::new(); - let mut dirty_items = Vec::new(); - for item in &self.items { - if should_close(item.id()) { - items_to_close.push(item.boxed_clone()); - if item.is_dirty(cx) { - dirty_items.push(item.boxed_clone()); - } - } - } - - // If a buffer is open both in a singleton editor and in a multibuffer, make sure - // to focus the singleton buffer when prompting to save that buffer, as opposed - // to focusing the multibuffer, because this gives the user a more clear idea - // of what content they would be saving. - items_to_close.sort_by_key(|item| !item.is_singleton(cx)); - - let workspace = self.workspace.clone(); - cx.spawn(|pane, mut cx| async move { - if save_intent == SaveIntent::Close && dirty_items.len() > 1 { - let mut answer = pane.update(&mut cx, |_, cx| { - let prompt = - Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); - cx.prompt( - PromptLevel::Warning, - &prompt, - &["Save all", "Discard all", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => save_intent = SaveIntent::SaveAll, - Some(1) => save_intent = SaveIntent::Skip, - _ => {} - } - } - let mut saved_project_items_ids = HashSet::default(); - for item in items_to_close.clone() { - // Find the item's current index and its set of project item models. Avoid - // storing these in advance, in case they have changed since this task - // was started. - let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { - (pane.index_for_item(&*item), item.project_item_model_ids(cx)) - })?; - let item_ix = if let Some(ix) = item_ix { - ix - } else { - continue; - }; - - // Check if this view has any project items that are not open anywhere else - // in the workspace, AND that the user has not already been prompted to save. - // If there are any such project entries, prompt the user to save this item. - let project = workspace.read_with(&cx, |workspace, cx| { - for item in workspace.items(cx) { - if !items_to_close - .iter() - .any(|item_to_close| item_to_close.id() == item.id()) - { - let other_project_item_ids = item.project_item_model_ids(cx); - project_item_ids.retain(|id| !other_project_item_ids.contains(id)); - } - } - workspace.project().clone() - })?; - let should_save = project_item_ids - .iter() - .any(|id| saved_project_items_ids.insert(*id)); - - if should_save - && !Self::save_item( - project.clone(), - &pane, - item_ix, - &*item, - save_intent, - &mut cx, - ) - .await? - { - break; - } - - // Remove the item from the pane. - pane.update(&mut cx, |pane, cx| { - if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { - pane.remove_item(item_ix, false, cx); - } - })?; - } - - pane.update(&mut cx, |_, cx| cx.notify())?; - Ok(()) - }) - } - - pub fn remove_item( - &mut self, - item_index: usize, - activate_pane: bool, - cx: &mut ViewContext, - ) { - self.activation_history - .retain(|&history_entry| history_entry != self.items[item_index].id()); - - if item_index == self.active_item_index { - let index_to_activate = self - .activation_history - .pop() - .and_then(|last_activated_item| { - self.items.iter().enumerate().find_map(|(index, item)| { - (item.id() == last_activated_item).then_some(index) - }) - }) - // We didn't have a valid activation history entry, so fallback - // to activating the item to the left - .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); - - let should_activate = activate_pane || self.has_focus; - self.activate_item(index_to_activate, should_activate, should_activate, cx); - } - - let item = self.items.remove(item_index); - - cx.emit(Event::RemoveItem { item_id: item.id() }); - if self.items.is_empty() { - item.deactivated(cx); - self.update_toolbar(cx); - cx.emit(Event::Remove); - } - - if item_index < self.active_item_index { - self.active_item_index -= 1; - } - - self.nav_history.set_mode(NavigationMode::ClosingItem); - item.deactivated(cx); - self.nav_history.set_mode(NavigationMode::Normal); - - if let Some(path) = item.project_path(cx) { - let abs_path = self - .nav_history - .0 - .borrow() - .paths_by_item - .get(&item.id()) - .and_then(|(_, abs_path)| abs_path.clone()); - - self.nav_history - .0 - .borrow_mut() - .paths_by_item - .insert(item.id(), (path, abs_path)); - } else { - self.nav_history - .0 - .borrow_mut() - .paths_by_item - .remove(&item.id()); - } - - if self.items.is_empty() && self.zoomed { - cx.emit(Event::ZoomOut); - } - - cx.notify(); - } - - pub async fn save_item( - project: ModelHandle, - pane: &WeakViewHandle, - item_ix: usize, - item: &dyn ItemHandle, - save_intent: SaveIntent, - cx: &mut AsyncAppContext, - ) -> Result { - const CONFLICT_MESSAGE: &str = - "This file has changed on disk since you started editing it. Do you want to overwrite it?"; - - if save_intent == SaveIntent::Skip { - return Ok(true); - } - - let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| { - ( - item.has_conflict(cx), - item.is_dirty(cx), - item.can_save(cx), - item.is_singleton(cx), - ) - }); - - // when saving a single buffer, we ignore whether or not it's dirty. - if save_intent == SaveIntent::Save { - is_dirty = true; - } - - if save_intent == SaveIntent::SaveAs { - is_dirty = true; - has_conflict = false; - can_save = false; - } - - if save_intent == SaveIntent::Overwrite { - has_conflict = false; - } - - if has_conflict && can_save { - let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); - cx.prompt( - PromptLevel::Warning, - CONFLICT_MESSAGE, - &["Overwrite", "Discard", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, - Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, - _ => return Ok(false), - } - } else if is_dirty && (can_save || can_save_as) { - if save_intent == SaveIntent::Close { - let will_autosave = cx.read(|cx| { - matches!( - settings::get::(cx).autosave, - AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ) && Self::can_autosave_item(&*item, cx) - }); - if !will_autosave { - let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); - let prompt = dirty_message_for(item.project_path(cx)); - cx.prompt( - PromptLevel::Warning, - &prompt, - &["Save", "Don't Save", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => {} - Some(1) => return Ok(true), // Don't save his file - _ => return Ok(false), // Cancel - } - } - } - - if can_save { - pane.update(cx, |_, cx| item.save(project, cx))?.await?; - } else if can_save_as { - let start_abs_path = project - .read_with(cx, |project, cx| { - let worktree = project.visible_worktrees(cx).next()?; - Some(worktree.read(cx).as_local()?.abs_path().to_path_buf()) - }) - .unwrap_or_else(|| Path::new("").into()); - - let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path)); - if let Some(abs_path) = abs_path.next().await.flatten() { - pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))? - .await?; - } else { - return Ok(false); - } - } - } - Ok(true) - } - - fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool { - let is_deleted = item.project_entry_ids(cx).is_empty(); - item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted - } - - pub fn autosave_item( - item: &dyn ItemHandle, - project: ModelHandle, - cx: &mut WindowContext, - ) -> Task> { - if Self::can_autosave_item(item, cx) { - item.save(project, cx) - } else { - Task::ready(Ok(())) - } - } - - pub fn focus_active_item(&mut self, cx: &mut ViewContext) { - if let Some(active_item) = self.active_item() { - cx.focus(active_item.as_any()); - } - } - - pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { - cx.emit(Event::Split(direction)); - } - - fn deploy_split_menu(&mut self, cx: &mut ViewContext) { - self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.toggle( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("Split Right", SplitRight), - ContextMenuItem::action("Split Left", SplitLeft), - ContextMenuItem::action("Split Up", SplitUp), - ContextMenuItem::action("Split Down", SplitDown), - ], - cx, - ); - }); - - self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; - } - - fn deploy_new_menu(&mut self, cx: &mut ViewContext) { - self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.toggle( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("New File", NewFile), - ContextMenuItem::action("New Terminal", NewCenterTerminal), - ContextMenuItem::action("New Search", NewSearch), - ], - cx, - ); - }); - - self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; - } - - fn deploy_tab_context_menu( - &mut self, - position: Vector2F, - target_item_id: usize, - cx: &mut ViewContext, - ) { - let active_item_id = self.items[self.active_item_index].id(); - let is_active_item = target_item_id == active_item_id; - let target_pane = cx.weak_handle(); - - // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab - - self.tab_context_menu.update(cx, |menu, cx| { - menu.show( - position, - AnchorCorner::TopLeft, - if is_active_item { - vec![ - ContextMenuItem::action( - "Close Active Item", - CloseActiveItem { save_intent: None }, - ), - ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::action("Close Clean Items", CloseCleanItems), - ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), - ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), - ContextMenuItem::action( - "Close All Items", - CloseAllItems { save_intent: None }, - ), - ] - } else { - // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. - vec![ - ContextMenuItem::handler("Close Inactive Item", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_item_by_id( - target_item_id, - SaveIntent::Close, - cx, - ) - .detach_and_log_err(cx); - }) - } - } - }), - ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::action("Close Clean Items", CloseCleanItems), - ContextMenuItem::handler("Close Items To The Left", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_items_to_the_left_by_id(target_item_id, cx) - .detach_and_log_err(cx); - }) - } - } - }), - ContextMenuItem::handler("Close Items To The Right", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_items_to_the_right_by_id(target_item_id, cx) - .detach_and_log_err(cx); - }) - } - } - }), - ContextMenuItem::action( - "Close All Items", - CloseAllItems { save_intent: None }, - ), - ] - }, - cx, - ); - }); - } - - pub fn toolbar(&self) -> &ViewHandle { - &self.toolbar - } - - pub fn handle_deleted_project_item( - &mut self, - entry_id: ProjectEntryId, - cx: &mut ViewContext, - ) -> Option<()> { - let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { - if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { - Some((i, item.id())) - } else { - None - } - })?; - - self.remove_item(item_index_to_delete, false, cx); - self.nav_history.remove_item(item_id); - - Some(()) - } - - fn update_toolbar(&mut self, cx: &mut ViewContext) { - let active_item = self - .items - .get(self.active_item_index) - .map(|item| item.as_ref()); - self.toolbar.update(cx, |toolbar, cx| { - toolbar.set_active_item(active_item, cx); - }); - } - - fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { - let theme = theme::current(cx).clone(); - - let pane = cx.handle().downgrade(); - let autoscroll = if mem::take(&mut self.autoscroll) { - Some(self.active_item_index) - } else { - None - }; - - let pane_active = self.has_focus; - - enum Tabs {} - let mut row = Flex::row().scrollable::(1, autoscroll, cx); - for (ix, (item, detail)) in self - .items - .iter() - .cloned() - .zip(self.tab_details(cx)) - .enumerate() - { - let git_status = item - .project_path(cx) - .and_then(|path| self.project.read(cx).entry_for_path(&path, cx)) - .and_then(|entry| entry.git_status()); - - let detail = if detail == 0 { None } else { Some(detail) }; - let tab_active = ix == self.active_item_index; - - row.add_child({ - enum TabDragReceiver {} - let mut receiver = - dragged_item_receiver::(self, ix, ix, true, None, cx, { - let item = item.clone(); - let pane = pane.clone(); - let detail = detail.clone(); - - let theme = theme::current(cx).clone(); - let mut tooltip_theme = theme.tooltip.clone(); - tooltip_theme.max_text_width = None; - let tab_tooltip_text = - item.tab_tooltip_text(cx).map(|text| text.into_owned()); - - let mut tab_style = theme - .workspace - .tab_bar - .tab_style(pane_active, tab_active) - .clone(); - let should_show_status = settings::get::(cx).git_status; - if should_show_status && git_status != None { - tab_style.label.text.color = match git_status.unwrap() { - GitFileStatus::Added => tab_style.git.inserted, - GitFileStatus::Modified => tab_style.git.modified, - GitFileStatus::Conflict => tab_style.git.conflict, - }; - } - - move |mouse_state, cx| { - let hovered = mouse_state.hovered(); - - enum Tab {} - let mouse_event_handler = - MouseEventHandler::new::(ix, cx, |_, cx| { - Self::render_tab( - &item, - pane.clone(), - ix == 0, - detail, - hovered, - &tab_style, - cx, - ) - }) - .on_down(MouseButton::Left, move |_, this, cx| { - this.activate_item(ix, true, true, cx); - }) - .on_click(MouseButton::Middle, { - let item_id = item.id(); - move |_, pane, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, cx) - .detach_and_log_err(cx); - } - }) - .on_down( - MouseButton::Right, - move |event, pane, cx| { - pane.deploy_tab_context_menu(event.position, item.id(), cx); - }, - ); - - if let Some(tab_tooltip_text) = tab_tooltip_text { - mouse_event_handler - .with_tooltip::( - ix, - tab_tooltip_text, - None, - tooltip_theme, - cx, - ) - .into_any() - } else { - mouse_event_handler.into_any() - } - } - }); - - if !pane_active || !tab_active { - receiver = receiver.with_cursor_style(CursorStyle::PointingHand); - } - - receiver.as_draggable( - DraggedItem { - handle: item, - pane: pane.clone(), - }, - { - let theme = theme::current(cx).clone(); - - let detail = detail.clone(); - move |_, dragged_item: &DraggedItem, cx: &mut ViewContext| { - let tab_style = &theme.workspace.tab_bar.dragged_tab; - Self::render_dragged_tab( - &dragged_item.handle, - dragged_item.pane.clone(), - false, - detail, - false, - &tab_style, - cx, - ) - } - }, - ) - }) - } - - // Use the inactive tab style along with the current pane's active status to decide how to render - // the filler - let filler_index = self.items.len(); - let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); - enum Filler {} - row.add_child( - dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { - Empty::new() - .contained() - .with_style(filler_style.container) - .with_border(filler_style.container.border) - }) - .flex(1., true) - .into_any_named("filler"), - ); - - row - } - - fn tab_details(&self, cx: &AppContext) -> Vec { - let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); - - let mut tab_descriptions = HashMap::default(); - let mut done = false; - while !done { - done = true; - - // Store item indices by their tab description. - for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { - if let Some(description) = item.tab_description(*detail, cx) { - if *detail == 0 - || Some(&description) != item.tab_description(detail - 1, cx).as_ref() - { - tab_descriptions - .entry(description) - .or_insert(Vec::new()) - .push(ix); - } - } - } - - // If two or more items have the same tab description, increase their level - // of detail and try again. - for (_, item_ixs) in tab_descriptions.drain() { - if item_ixs.len() > 1 { - done = false; - for ix in item_ixs { - tab_details[ix] += 1; - } - } - } - } - - tab_details - } - - fn render_tab( - item: &Box, - pane: WeakViewHandle, - first: bool, - detail: Option, - hovered: bool, - tab_style: &theme::Tab, - cx: &mut ViewContext, - ) -> AnyElement { - let title = item.tab_content(detail, &tab_style, cx); - Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) - } - - fn render_dragged_tab( - item: &Box, - pane: WeakViewHandle, - first: bool, - detail: Option, - hovered: bool, - tab_style: &theme::Tab, - cx: &mut ViewContext, - ) -> AnyElement { - let title = item.dragged_tab_content(detail, &tab_style, cx); - Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) - } - - fn render_tab_with_title( - title: AnyElement, - item: &Box, - pane: WeakViewHandle, - first: bool, - hovered: bool, - tab_style: &theme::Tab, - cx: &mut ViewContext, - ) -> AnyElement { - let mut container = tab_style.container.clone(); - if first { - container.border.left = false; - } - - let buffer_jewel_element = { - let diameter = 7.0; - let icon_color = if item.has_conflict(cx) { - Some(tab_style.icon_conflict) - } else if item.is_dirty(cx) { - Some(tab_style.icon_dirty) - } else { - None - }; - - Canvas::new(move |bounds, _, _, cx| { - if let Some(color) = icon_color { - let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); - cx.scene().push_quad(Quad { - bounds: square, - background: Some(color), - border: Default::default(), - corner_radii: (diameter / 2.).into(), - }); - } - }) - .constrained() - .with_width(diameter) - .with_height(diameter) - .aligned() - }; - - let title_element = title.aligned().contained().with_style(ContainerStyle { - margin: Margin { - left: tab_style.spacing, - right: tab_style.spacing, - ..Default::default() - }, - ..Default::default() - }); - - let close_element = if hovered { - let item_id = item.id(); - enum TabCloseButton {} - let icon = Svg::new("icons/x.svg"); - MouseEventHandler::new::(item_id, cx, |mouse_state, _| { - if mouse_state.hovered() { - icon.with_color(tab_style.icon_close_active) - } else { - icon.with_color(tab_style.icon_close) - } - }) - .with_padding(Padding::uniform(4.)) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let pane = pane.clone(); - move |_, _, cx| { - let pane = pane.clone(); - cx.window_context().defer(move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, cx) - .detach_and_log_err(cx); - }); - } - }); - } - }) - .into_any_named("close-tab-icon") - .constrained() - } else { - Empty::new().constrained() - } - .with_width(tab_style.close_icon_width) - .aligned(); - - let close_right = settings::get::(cx).close_position.right(); - - if close_right { - Flex::row() - .with_child(buffer_jewel_element) - .with_child(title_element) - .with_child(close_element) - } else { - Flex::row() - .with_child(close_element) - .with_child(title_element) - .with_child(buffer_jewel_element) - } - .contained() - .with_style(container) - .constrained() - .with_height(tab_style.height) - .into_any() - } - - pub fn render_tab_bar_button< - F1: 'static + Fn(&mut Pane, &mut EventContext), - F2: 'static + Fn(&mut Pane, &mut EventContext), - >( - index: usize, - icon: &'static str, - is_active: bool, - tooltip: Option<(&'static str, Option>)>, - cx: &mut ViewContext, - on_click: F1, - on_down: F2, - context_menu: Option>, - ) -> AnyElement { - enum TabBarButton {} - - let mut button = MouseEventHandler::new::(index, cx, |mouse_state, cx| { - let theme = &settings::get::(cx).theme.workspace.tab_bar; - let style = theme.pane_button.in_state(is_active).style_for(mouse_state); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) - .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) - .into_any(); - if let Some((tooltip, action)) = tooltip { - let tooltip_style = settings::get::(cx).theme.tooltip.clone(); - button = button - .with_tooltip::(index, tooltip, action, tooltip_style, cx) - .into_any(); - } - - Stack::new() - .with_child(button) - .with_children( - context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), - ) - .flex(1., false) - .into_any_named("tab bar button") - } - - fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { - let background = theme.workspace.background; - Empty::new() - .contained() - .with_background_color(background) - .into_any() - } - - pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { - self.zoomed = zoomed; - cx.notify(); - } - - pub fn is_zoomed(&self) -> bool { - self.zoomed - } -} - -impl Entity for Pane { - type Event = Event; -} - -impl View for Pane { - fn ui_name() -> &'static str { - "Pane" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - enum MouseNavigationHandler {} - - MouseEventHandler::new::(0, cx, |_, cx| { - let active_item_index = self.active_item_index; - - if let Some(active_item) = self.active_item() { - Flex::column() - .with_child({ - let theme = theme::current(cx).clone(); - - let mut stack = Stack::new(); - - enum TabBarEventHandler {} - stack.add_child( - MouseEventHandler::new::(0, cx, |_, _| { - Empty::new() - .contained() - .with_style(theme.workspace.tab_bar.container) - }) - .on_down( - MouseButton::Left, - move |_, this, cx| { - this.activate_item(active_item_index, true, true, cx); - }, - ), - ); - let tooltip_style = theme.tooltip.clone(); - let tab_bar_theme = theme.workspace.tab_bar.clone(); - - let nav_button_height = tab_bar_theme.height; - let button_style = tab_bar_theme.nav_button; - let border_for_nav_buttons = tab_bar_theme - .tab_style(false, false) - .container - .border - .clone(); - - let mut tab_row = Flex::row() - .with_child(nav_button( - "icons/arrow_left.svg", - button_style.clone(), - nav_button_height, - tooltip_style.clone(), - self.can_navigate_backward(), - { - move |pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace - .go_back(pane, cx) - .detach_and_log_err(cx) - }) - }) - } - } - }, - super::GoBack, - "Go Back", - cx, - )) - .with_child( - nav_button( - "icons/arrow_right.svg", - button_style.clone(), - nav_button_height, - tooltip_style, - self.can_navigate_forward(), - { - move |pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace - .go_forward(pane, cx) - .detach_and_log_err(cx) - }) - }) - } - } - }, - super::GoForward, - "Go Forward", - cx, - ) - .contained() - .with_border(border_for_nav_buttons), - ) - .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); - - if self.has_focus { - let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); - tab_row.add_child( - (render_tab_bar_buttons)(self, cx) - .contained() - .with_style(theme.workspace.tab_bar.pane_button_container) - .flex(1., false) - .into_any(), - ) - } - - stack.add_child(tab_row); - stack - .constrained() - .with_height(theme.workspace.tab_bar.height) - .flex(1., false) - .into_any_named("tab bar") - }) - .with_child({ - enum PaneContentTabDropTarget {} - dragged_item_receiver::( - self, - 0, - self.active_item_index + 1, - !self.can_split, - if self.can_split { Some(100.) } else { None }, - cx, - { - let toolbar = self.toolbar.clone(); - let toolbar_hidden = toolbar.read(cx).hidden(); - move |_, cx| { - Flex::column() - .with_children( - (!toolbar_hidden) - .then(|| ChildView::new(&toolbar, cx).expanded()), - ) - .with_child( - ChildView::new(active_item.as_any(), cx).flex(1., true), - ) - } - }, - ) - .flex(1., true) - }) - .with_child(ChildView::new(&self.tab_context_menu, cx)) - .into_any() - } else { - enum EmptyPane {} - let theme = theme::current(cx).clone(); - - dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { - self.render_blank_pane(&theme, cx) - }) - .on_down(MouseButton::Left, |_, _, cx| { - cx.focus_parent(); - }) - .into_any() - } - }) - .on_down( - MouseButton::Navigate(NavigationDirection::Back), - move |_, pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.go_back(pane, cx).detach_and_log_err(cx) - }) - }) - } - }, - ) - .on_down(MouseButton::Navigate(NavigationDirection::Forward), { - move |_, pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.go_forward(pane, cx).detach_and_log_err(cx) - }) - }) - } - } - }) - .into_any_named("pane") - } - - fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - if !self.has_focus { - self.has_focus = true; - cx.emit(Event::Focus); - cx.notify(); - } - - self.toolbar.update(cx, |toolbar, cx| { - toolbar.focus_changed(true, cx); - }); - - if let Some(active_item) = self.active_item() { - if cx.is_self_focused() { - // Pane was focused directly. We need to either focus a view inside the active item, - // or focus the active item itself - if let Some(weak_last_focused_view) = - self.last_focused_view_by_item.get(&active_item.id()) - { - if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) { - cx.focus(&last_focused_view); - return; - } else { - self.last_focused_view_by_item.remove(&active_item.id()); - } - } - - cx.focus(active_item.as_any()); - } else if focused != self.tab_bar_context_menu.handle { - self.last_focused_view_by_item - .insert(active_item.id(), focused.downgrade()); - } - } - } - - fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = false; - self.toolbar.update(cx, |toolbar, cx| { - toolbar.focus_changed(false, cx); - }); - cx.notify(); - } - - fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { - Self::reset_to_default_keymap_context(keymap); - } -} - -impl ItemNavHistory { - pub fn push(&mut self, data: Option, cx: &mut WindowContext) { - self.history.push(data, self.item.clone(), cx); - } - - pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { - self.history.pop(NavigationMode::GoingBack, cx) - } - - pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { - self.history.pop(NavigationMode::GoingForward, cx) - } -} - -impl NavHistory { - pub fn for_each_entry( - &self, - cx: &AppContext, - mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), - ) { - let borrowed_history = self.0.borrow(); - borrowed_history - .forward_stack - .iter() - .chain(borrowed_history.backward_stack.iter()) - .chain(borrowed_history.closed_stack.iter()) - .for_each(|entry| { - if let Some(project_and_abs_path) = - borrowed_history.paths_by_item.get(&entry.item.id()) - { - f(entry, project_and_abs_path.clone()); - } else if let Some(item) = entry.item.upgrade(cx) { - if let Some(path) = item.project_path(cx) { - f(entry, (path, None)); - } - } - }) - } - - pub fn set_mode(&mut self, mode: NavigationMode) { - self.0.borrow_mut().mode = mode; - } + // pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { + // let mut index = self.active_item_index; + // if index > 0 { + // index -= 1; + // } else if !self.items.is_empty() { + // index = self.items.len() - 1; + // } + // self.activate_item(index, activate_pane, activate_pane, cx); + // } + + // pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { + // let mut index = self.active_item_index; + // if index + 1 < self.items.len() { + // index += 1; + // } else { + // index = 0; + // } + // self.activate_item(index, activate_pane, activate_pane, cx); + // } + + // pub fn close_active_item( + // &mut self, + // action: &CloseActiveItem, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_item_by_id( + // active_item_id, + // action.save_intent.unwrap_or(SaveIntent::Close), + // cx, + // )) + // } + + // pub fn close_item_by_id( + // &mut self, + // item_id_to_close: usize, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + // } + + // pub fn close_inactive_items( + // &mut self, + // _: &CloseInactiveItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_id != active_item_id + // })) + // } + + // pub fn close_clean_items( + // &mut self, + // _: &CloseCleanItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // let item_ids: Vec<_> = self + // .items() + // .filter(|item| !item.is_dirty(cx)) + // .map(|item| item.id()) + // .collect(); + // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // })) + // } + + // pub fn close_items_to_the_left( + // &mut self, + // _: &CloseItemsToTheLeft, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items_to_the_left_by_id(active_item_id, cx)) + // } + + // pub fn close_items_to_the_left_by_id( + // &mut self, + // item_id: usize, + // cx: &mut ViewContext, + // ) -> Task> { + // let item_ids: Vec<_> = self + // .items() + // .take_while(|item| item.id() != item_id) + // .map(|item| item.id()) + // .collect(); + // self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // }) + // } + + // pub fn close_items_to_the_right( + // &mut self, + // _: &CloseItemsToTheRight, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items_to_the_right_by_id(active_item_id, cx)) + // } + + // pub fn close_items_to_the_right_by_id( + // &mut self, + // item_id: usize, + // cx: &mut ViewContext, + // ) -> Task> { + // let item_ids: Vec<_> = self + // .items() + // .rev() + // .take_while(|item| item.id() != item_id) + // .map(|item| item.id()) + // .collect(); + // self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // }) + // } + + // pub fn close_all_items( + // &mut self, + // action: &CloseAllItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + + // Some( + // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { + // true + // }), + // ) + // } + + // pub(super) fn file_names_for_prompt( + // items: &mut dyn Iterator>, + // all_dirty_items: usize, + // cx: &AppContext, + // ) -> String { + // /// Quantity of item paths displayed in prompt prior to cutoff.. + // const FILE_NAMES_CUTOFF_POINT: usize = 10; + // let mut file_names: Vec<_> = items + // .filter_map(|item| { + // item.project_path(cx).and_then(|project_path| { + // project_path + // .path + // .file_name() + // .and_then(|name| name.to_str().map(ToOwned::to_owned)) + // }) + // }) + // .take(FILE_NAMES_CUTOFF_POINT) + // .collect(); + // let should_display_followup_text = + // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; + // if should_display_followup_text { + // let not_shown_files = all_dirty_items - file_names.len(); + // if not_shown_files == 1 { + // file_names.push(".. 1 file not shown".into()); + // } else { + // file_names.push(format!(".. {} files not shown", not_shown_files).into()); + // } + // } + // let file_names = file_names.join("\n"); + // format!( + // "Do you want to save changes to the following {} files?\n{file_names}", + // all_dirty_items + // ) + // } + + // pub fn close_items( + // &mut self, + // cx: &mut ViewContext, + // mut save_intent: SaveIntent, + // should_close: impl 'static + Fn(usize) -> bool, + // ) -> Task> { + // // Find the items to close. + // let mut items_to_close = Vec::new(); + // let mut dirty_items = Vec::new(); + // for item in &self.items { + // if should_close(item.id()) { + // items_to_close.push(item.boxed_clone()); + // if item.is_dirty(cx) { + // dirty_items.push(item.boxed_clone()); + // } + // } + // } + + // // If a buffer is open both in a singleton editor and in a multibuffer, make sure + // // to focus the singleton buffer when prompting to save that buffer, as opposed + // // to focusing the multibuffer, because this gives the user a more clear idea + // // of what content they would be saving. + // items_to_close.sort_by_key(|item| !item.is_singleton(cx)); + + // let workspace = self.workspace.clone(); + // cx.spawn(|pane, mut cx| async move { + // if save_intent == SaveIntent::Close && dirty_items.len() > 1 { + // let mut answer = pane.update(&mut cx, |_, cx| { + // let prompt = + // Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); + // cx.prompt( + // PromptLevel::Warning, + // &prompt, + // &["Save all", "Discard all", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => save_intent = SaveIntent::SaveAll, + // Some(1) => save_intent = SaveIntent::Skip, + // _ => {} + // } + // } + // let mut saved_project_items_ids = HashSet::default(); + // for item in items_to_close.clone() { + // // Find the item's current index and its set of project item models. Avoid + // // storing these in advance, in case they have changed since this task + // // was started. + // let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { + // (pane.index_for_item(&*item), item.project_item_model_ids(cx)) + // })?; + // let item_ix = if let Some(ix) = item_ix { + // ix + // } else { + // continue; + // }; + + // // Check if this view has any project items that are not open anywhere else + // // in the workspace, AND that the user has not already been prompted to save. + // // If there are any such project entries, prompt the user to save this item. + // let project = workspace.read_with(&cx, |workspace, cx| { + // for item in workspace.items(cx) { + // if !items_to_close + // .iter() + // .any(|item_to_close| item_to_close.id() == item.id()) + // { + // let other_project_item_ids = item.project_item_model_ids(cx); + // project_item_ids.retain(|id| !other_project_item_ids.contains(id)); + // } + // } + // workspace.project().clone() + // })?; + // let should_save = project_item_ids + // .iter() + // .any(|id| saved_project_items_ids.insert(*id)); + + // if should_save + // && !Self::save_item( + // project.clone(), + // &pane, + // item_ix, + // &*item, + // save_intent, + // &mut cx, + // ) + // .await? + // { + // break; + // } + + // // Remove the item from the pane. + // pane.update(&mut cx, |pane, cx| { + // if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { + // pane.remove_item(item_ix, false, cx); + // } + // })?; + // } + + // pane.update(&mut cx, |_, cx| cx.notify())?; + // Ok(()) + // }) + // } + + // pub fn remove_item( + // &mut self, + // item_index: usize, + // activate_pane: bool, + // cx: &mut ViewContext, + // ) { + // self.activation_history + // .retain(|&history_entry| history_entry != self.items[item_index].id()); + + // if item_index == self.active_item_index { + // let index_to_activate = self + // .activation_history + // .pop() + // .and_then(|last_activated_item| { + // self.items.iter().enumerate().find_map(|(index, item)| { + // (item.id() == last_activated_item).then_some(index) + // }) + // }) + // // We didn't have a valid activation history entry, so fallback + // // to activating the item to the left + // .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); + + // let should_activate = activate_pane || self.has_focus; + // self.activate_item(index_to_activate, should_activate, should_activate, cx); + // } + + // let item = self.items.remove(item_index); + + // cx.emit(Event::RemoveItem { item_id: item.id() }); + // if self.items.is_empty() { + // item.deactivated(cx); + // self.update_toolbar(cx); + // cx.emit(Event::Remove); + // } + + // if item_index < self.active_item_index { + // self.active_item_index -= 1; + // } + + // self.nav_history.set_mode(NavigationMode::ClosingItem); + // item.deactivated(cx); + // self.nav_history.set_mode(NavigationMode::Normal); + + // if let Some(path) = item.project_path(cx) { + // let abs_path = self + // .nav_history + // .0 + // .borrow() + // .paths_by_item + // .get(&item.id()) + // .and_then(|(_, abs_path)| abs_path.clone()); + + // self.nav_history + // .0 + // .borrow_mut() + // .paths_by_item + // .insert(item.id(), (path, abs_path)); + // } else { + // self.nav_history + // .0 + // .borrow_mut() + // .paths_by_item + // .remove(&item.id()); + // } + + // if self.items.is_empty() && self.zoomed { + // cx.emit(Event::ZoomOut); + // } + + // cx.notify(); + // } + + // pub async fn save_item( + // project: ModelHandle, + // pane: &WeakViewHandle, + // item_ix: usize, + // item: &dyn ItemHandle, + // save_intent: SaveIntent, + // cx: &mut AsyncAppContext, + // ) -> Result { + // const CONFLICT_MESSAGE: &str = + // "This file has changed on disk since you started editing it. Do you want to overwrite it?"; + + // if save_intent == SaveIntent::Skip { + // return Ok(true); + // } + + // let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| { + // ( + // item.has_conflict(cx), + // item.is_dirty(cx), + // item.can_save(cx), + // item.is_singleton(cx), + // ) + // }); + + // // when saving a single buffer, we ignore whether or not it's dirty. + // if save_intent == SaveIntent::Save { + // is_dirty = true; + // } + + // if save_intent == SaveIntent::SaveAs { + // is_dirty = true; + // has_conflict = false; + // can_save = false; + // } + + // if save_intent == SaveIntent::Overwrite { + // has_conflict = false; + // } + + // if has_conflict && can_save { + // let mut answer = pane.update(cx, |pane, cx| { + // pane.activate_item(item_ix, true, true, cx); + // cx.prompt( + // PromptLevel::Warning, + // CONFLICT_MESSAGE, + // &["Overwrite", "Discard", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, + // _ => return Ok(false), + // } + // } else if is_dirty && (can_save || can_save_as) { + // if save_intent == SaveIntent::Close { + // let will_autosave = cx.read(|cx| { + // matches!( + // settings::get::(cx).autosave, + // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange + // ) && Self::can_autosave_item(&*item, cx) + // }); + // if !will_autosave { + // let mut answer = pane.update(cx, |pane, cx| { + // pane.activate_item(item_ix, true, true, cx); + // let prompt = dirty_message_for(item.project_path(cx)); + // cx.prompt( + // PromptLevel::Warning, + // &prompt, + // &["Save", "Don't Save", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => {} + // Some(1) => return Ok(true), // Don't save his file + // _ => return Ok(false), // Cancel + // } + // } + // } + + // if can_save { + // pane.update(cx, |_, cx| item.save(project, cx))?.await?; + // } else if can_save_as { + // let start_abs_path = project + // .read_with(cx, |project, cx| { + // let worktree = project.visible_worktrees(cx).next()?; + // Some(worktree.read(cx).as_local()?.abs_path().to_path_buf()) + // }) + // .unwrap_or_else(|| Path::new("").into()); + + // let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path)); + // if let Some(abs_path) = abs_path.next().await.flatten() { + // pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))? + // .await?; + // } else { + // return Ok(false); + // } + // } + // } + // Ok(true) + // } + + // fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool { + // let is_deleted = item.project_entry_ids(cx).is_empty(); + // item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted + // } + + // pub fn autosave_item( + // item: &dyn ItemHandle, + // project: ModelHandle, + // cx: &mut WindowContext, + // ) -> Task> { + // if Self::can_autosave_item(item, cx) { + // item.save(project, cx) + // } else { + // Task::ready(Ok(())) + // } + // } + + // pub fn focus_active_item(&mut self, cx: &mut ViewContext) { + // if let Some(active_item) = self.active_item() { + // cx.focus(active_item.as_any()); + // } + // } + + // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { + // cx.emit(Event::Split(direction)); + // } + + // fn deploy_split_menu(&mut self, cx: &mut ViewContext) { + // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { + // menu.toggle( + // Default::default(), + // AnchorCorner::TopRight, + // vec![ + // ContextMenuItem::action("Split Right", SplitRight), + // ContextMenuItem::action("Split Left", SplitLeft), + // ContextMenuItem::action("Split Up", SplitUp), + // ContextMenuItem::action("Split Down", SplitDown), + // ], + // cx, + // ); + // }); + + // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; + // } + + // fn deploy_new_menu(&mut self, cx: &mut ViewContext) { + // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { + // menu.toggle( + // Default::default(), + // AnchorCorner::TopRight, + // vec![ + // ContextMenuItem::action("New File", NewFile), + // ContextMenuItem::action("New Terminal", NewCenterTerminal), + // ContextMenuItem::action("New Search", NewSearch), + // ], + // cx, + // ); + // }); + + // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; + // } + + // fn deploy_tab_context_menu( + // &mut self, + // position: Vector2F, + // target_item_id: usize, + // cx: &mut ViewContext, + // ) { + // let active_item_id = self.items[self.active_item_index].id(); + // let is_active_item = target_item_id == active_item_id; + // let target_pane = cx.weak_handle(); + + // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab + + // self.tab_context_menu.update(cx, |menu, cx| { + // menu.show( + // position, + // AnchorCorner::TopLeft, + // if is_active_item { + // vec![ + // ContextMenuItem::action( + // "Close Active Item", + // CloseActiveItem { save_intent: None }, + // ), + // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + // ContextMenuItem::action("Close Clean Items", CloseCleanItems), + // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), + // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), + // ContextMenuItem::action( + // "Close All Items", + // CloseAllItems { save_intent: None }, + // ), + // ] + // } else { + // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. + // vec![ + // ContextMenuItem::handler("Close Inactive Item", { + // let pane = target_pane.clone(); + // move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_item_by_id( + // target_item_id, + // SaveIntent::Close, + // cx, + // ) + // .detach_and_log_err(cx); + // }) + // } + // } + // }), + // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + // ContextMenuItem::action("Close Clean Items", CloseCleanItems), + // ContextMenuItem::handler("Close Items To The Left", { + // let pane = target_pane.clone(); + // move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_items_to_the_left_by_id(target_item_id, cx) + // .detach_and_log_err(cx); + // }) + // } + // } + // }), + // ContextMenuItem::handler("Close Items To The Right", { + // let pane = target_pane.clone(); + // move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_items_to_the_right_by_id(target_item_id, cx) + // .detach_and_log_err(cx); + // }) + // } + // } + // }), + // ContextMenuItem::action( + // "Close All Items", + // CloseAllItems { save_intent: None }, + // ), + // ] + // }, + // cx, + // ); + // }); + // } + + // pub fn toolbar(&self) -> &ViewHandle { + // &self.toolbar + // } + + // pub fn handle_deleted_project_item( + // &mut self, + // entry_id: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> Option<()> { + // let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { + // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + // Some((i, item.id())) + // } else { + // None + // } + // })?; + + // self.remove_item(item_index_to_delete, false, cx); + // self.nav_history.remove_item(item_id); + + // Some(()) + // } + + // fn update_toolbar(&mut self, cx: &mut ViewContext) { + // let active_item = self + // .items + // .get(self.active_item_index) + // .map(|item| item.as_ref()); + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.set_active_item(active_item, cx); + // }); + // } + + // fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { + // let theme = theme::current(cx).clone(); + + // let pane = cx.handle().downgrade(); + // let autoscroll = if mem::take(&mut self.autoscroll) { + // Some(self.active_item_index) + // } else { + // None + // }; + + // let pane_active = self.has_focus; + + // enum Tabs {} + // let mut row = Flex::row().scrollable::(1, autoscroll, cx); + // for (ix, (item, detail)) in self + // .items + // .iter() + // .cloned() + // .zip(self.tab_details(cx)) + // .enumerate() + // { + // let git_status = item + // .project_path(cx) + // .and_then(|path| self.project.read(cx).entry_for_path(&path, cx)) + // .and_then(|entry| entry.git_status()); + + // let detail = if detail == 0 { None } else { Some(detail) }; + // let tab_active = ix == self.active_item_index; + + // row.add_child({ + // enum TabDragReceiver {} + // let mut receiver = + // dragged_item_receiver::(self, ix, ix, true, None, cx, { + // let item = item.clone(); + // let pane = pane.clone(); + // let detail = detail.clone(); + + // let theme = theme::current(cx).clone(); + // let mut tooltip_theme = theme.tooltip.clone(); + // tooltip_theme.max_text_width = None; + // let tab_tooltip_text = + // item.tab_tooltip_text(cx).map(|text| text.into_owned()); + + // let mut tab_style = theme + // .workspace + // .tab_bar + // .tab_style(pane_active, tab_active) + // .clone(); + // let should_show_status = settings::get::(cx).git_status; + // if should_show_status && git_status != None { + // tab_style.label.text.color = match git_status.unwrap() { + // GitFileStatus::Added => tab_style.git.inserted, + // GitFileStatus::Modified => tab_style.git.modified, + // GitFileStatus::Conflict => tab_style.git.conflict, + // }; + // } + + // move |mouse_state, cx| { + // let hovered = mouse_state.hovered(); + + // enum Tab {} + // let mouse_event_handler = + // MouseEventHandler::new::(ix, cx, |_, cx| { + // Self::render_tab( + // &item, + // pane.clone(), + // ix == 0, + // detail, + // hovered, + // &tab_style, + // cx, + // ) + // }) + // .on_down(MouseButton::Left, move |_, this, cx| { + // this.activate_item(ix, true, true, cx); + // }) + // .on_click(MouseButton::Middle, { + // let item_id = item.id(); + // move |_, pane, cx| { + // pane.close_item_by_id(item_id, SaveIntent::Close, cx) + // .detach_and_log_err(cx); + // } + // }) + // .on_down( + // MouseButton::Right, + // move |event, pane, cx| { + // pane.deploy_tab_context_menu(event.position, item.id(), cx); + // }, + // ); + + // if let Some(tab_tooltip_text) = tab_tooltip_text { + // mouse_event_handler + // .with_tooltip::( + // ix, + // tab_tooltip_text, + // None, + // tooltip_theme, + // cx, + // ) + // .into_any() + // } else { + // mouse_event_handler.into_any() + // } + // } + // }); + + // if !pane_active || !tab_active { + // receiver = receiver.with_cursor_style(CursorStyle::PointingHand); + // } + + // receiver.as_draggable( + // DraggedItem { + // handle: item, + // pane: pane.clone(), + // }, + // { + // let theme = theme::current(cx).clone(); + + // let detail = detail.clone(); + // move |_, dragged_item: &DraggedItem, cx: &mut ViewContext| { + // let tab_style = &theme.workspace.tab_bar.dragged_tab; + // Self::render_dragged_tab( + // &dragged_item.handle, + // dragged_item.pane.clone(), + // false, + // detail, + // false, + // &tab_style, + // cx, + // ) + // } + // }, + // ) + // }) + // } + + // // Use the inactive tab style along with the current pane's active status to decide how to render + // // the filler + // let filler_index = self.items.len(); + // let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); + // enum Filler {} + // row.add_child( + // dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { + // Empty::new() + // .contained() + // .with_style(filler_style.container) + // .with_border(filler_style.container.border) + // }) + // .flex(1., true) + // .into_any_named("filler"), + // ); + + // row + // } + + // fn tab_details(&self, cx: &AppContext) -> Vec { + // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); + + // let mut tab_descriptions = HashMap::default(); + // let mut done = false; + // while !done { + // done = true; + + // // Store item indices by their tab description. + // for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { + // if let Some(description) = item.tab_description(*detail, cx) { + // if *detail == 0 + // || Some(&description) != item.tab_description(detail - 1, cx).as_ref() + // { + // tab_descriptions + // .entry(description) + // .or_insert(Vec::new()) + // .push(ix); + // } + // } + // } + + // // If two or more items have the same tab description, increase their level + // // of detail and try again. + // for (_, item_ixs) in tab_descriptions.drain() { + // if item_ixs.len() > 1 { + // done = false; + // for ix in item_ixs { + // tab_details[ix] += 1; + // } + // } + // } + // } + + // tab_details + // } + + // fn render_tab( + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // detail: Option, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let title = item.tab_content(detail, &tab_style, cx); + // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) + // } + + // fn render_dragged_tab( + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // detail: Option, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let title = item.dragged_tab_content(detail, &tab_style, cx); + // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) + // } + + // fn render_tab_with_title( + // title: AnyElement, + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let mut container = tab_style.container.clone(); + // if first { + // container.border.left = false; + // } + + // let buffer_jewel_element = { + // let diameter = 7.0; + // let icon_color = if item.has_conflict(cx) { + // Some(tab_style.icon_conflict) + // } else if item.is_dirty(cx) { + // Some(tab_style.icon_dirty) + // } else { + // None + // }; + + // Canvas::new(move |bounds, _, _, cx| { + // if let Some(color) = icon_color { + // let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); + // cx.scene().push_quad(Quad { + // bounds: square, + // background: Some(color), + // border: Default::default(), + // corner_radii: (diameter / 2.).into(), + // }); + // } + // }) + // .constrained() + // .with_width(diameter) + // .with_height(diameter) + // .aligned() + // }; + + // let title_element = title.aligned().contained().with_style(ContainerStyle { + // margin: Margin { + // left: tab_style.spacing, + // right: tab_style.spacing, + // ..Default::default() + // }, + // ..Default::default() + // }); + + // let close_element = if hovered { + // let item_id = item.id(); + // enum TabCloseButton {} + // let icon = Svg::new("icons/x.svg"); + // MouseEventHandler::new::(item_id, cx, |mouse_state, _| { + // if mouse_state.hovered() { + // icon.with_color(tab_style.icon_close_active) + // } else { + // icon.with_color(tab_style.icon_close) + // } + // }) + // .with_padding(Padding::uniform(4.)) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, { + // let pane = pane.clone(); + // move |_, _, cx| { + // let pane = pane.clone(); + // cx.window_context().defer(move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_item_by_id(item_id, SaveIntent::Close, cx) + // .detach_and_log_err(cx); + // }); + // } + // }); + // } + // }) + // .into_any_named("close-tab-icon") + // .constrained() + // } else { + // Empty::new().constrained() + // } + // .with_width(tab_style.close_icon_width) + // .aligned(); + + // let close_right = settings::get::(cx).close_position.right(); + + // if close_right { + // Flex::row() + // .with_child(buffer_jewel_element) + // .with_child(title_element) + // .with_child(close_element) + // } else { + // Flex::row() + // .with_child(close_element) + // .with_child(title_element) + // .with_child(buffer_jewel_element) + // } + // .contained() + // .with_style(container) + // .constrained() + // .with_height(tab_style.height) + // .into_any() + // } + + // pub fn render_tab_bar_button< + // F1: 'static + Fn(&mut Pane, &mut EventContext), + // F2: 'static + Fn(&mut Pane, &mut EventContext), + // >( + // index: usize, + // icon: &'static str, + // is_active: bool, + // tooltip: Option<(&'static str, Option>)>, + // cx: &mut ViewContext, + // on_click: F1, + // on_down: F2, + // context_menu: Option>, + // ) -> AnyElement { + // enum TabBarButton {} + + // let mut button = MouseEventHandler::new::(index, cx, |mouse_state, cx| { + // let theme = &settings2::get::(cx).theme.workspace.tab_bar; + // let style = theme.pane_button.in_state(is_active).style_for(mouse_state); + // Svg::new(icon) + // .with_color(style.color) + // .constrained() + // .with_width(style.icon_width) + // .aligned() + // .constrained() + // .with_width(style.button_width) + // .with_height(style.button_width) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) + // .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) + // .into_any(); + // if let Some((tooltip, action)) = tooltip { + // let tooltip_style = settings::get::(cx).theme.tooltip.clone(); + // button = button + // .with_tooltip::(index, tooltip, action, tooltip_style, cx) + // .into_any(); + // } + + // Stack::new() + // .with_child(button) + // .with_children( + // context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), + // ) + // .flex(1., false) + // .into_any_named("tab bar button") + // } + + // fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { + // let background = theme.workspace.background; + // Empty::new() + // .contained() + // .with_background_color(background) + // .into_any() + // } + + // pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + // self.zoomed = zoomed; + // cx.notify(); + // } + + // pub fn is_zoomed(&self) -> bool { + // self.zoomed + // } + // } + + // impl Entity for Pane { + // type Event = Event; + // } + + // impl View for Pane { + // fn ui_name() -> &'static str { + // "Pane" + // } + + // fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + // enum MouseNavigationHandler {} + + // MouseEventHandler::new::(0, cx, |_, cx| { + // let active_item_index = self.active_item_index; + + // if let Some(active_item) = self.active_item() { + // Flex::column() + // .with_child({ + // let theme = theme::current(cx).clone(); + + // let mut stack = Stack::new(); + + // enum TabBarEventHandler {} + // stack.add_child( + // MouseEventHandler::new::(0, cx, |_, _| { + // Empty::new() + // .contained() + // .with_style(theme.workspace.tab_bar.container) + // }) + // .on_down( + // MouseButton::Left, + // move |_, this, cx| { + // this.activate_item(active_item_index, true, true, cx); + // }, + // ), + // ); + // let tooltip_style = theme.tooltip.clone(); + // let tab_bar_theme = theme.workspace.tab_bar.clone(); + + // let nav_button_height = tab_bar_theme.height; + // let button_style = tab_bar_theme.nav_button; + // let border_for_nav_buttons = tab_bar_theme + // .tab_style(false, false) + // .container + // .border + // .clone(); + + // let mut tab_row = Flex::row() + // .with_child(nav_button( + // "icons/arrow_left.svg", + // button_style.clone(), + // nav_button_height, + // tooltip_style.clone(), + // self.can_navigate_backward(), + // { + // move |pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace + // .go_back(pane, cx) + // .detach_and_log_err(cx) + // }) + // }) + // } + // } + // }, + // super::GoBack, + // "Go Back", + // cx, + // )) + // .with_child( + // nav_button( + // "icons/arrow_right.svg", + // button_style.clone(), + // nav_button_height, + // tooltip_style, + // self.can_navigate_forward(), + // { + // move |pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace + // .go_forward(pane, cx) + // .detach_and_log_err(cx) + // }) + // }) + // } + // } + // }, + // super::GoForward, + // "Go Forward", + // cx, + // ) + // .contained() + // .with_border(border_for_nav_buttons), + // ) + // .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); + + // if self.has_focus { + // let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); + // tab_row.add_child( + // (render_tab_bar_buttons)(self, cx) + // .contained() + // .with_style(theme.workspace.tab_bar.pane_button_container) + // .flex(1., false) + // .into_any(), + // ) + // } + + // stack.add_child(tab_row); + // stack + // .constrained() + // .with_height(theme.workspace.tab_bar.height) + // .flex(1., false) + // .into_any_named("tab bar") + // }) + // .with_child({ + // enum PaneContentTabDropTarget {} + // dragged_item_receiver::( + // self, + // 0, + // self.active_item_index + 1, + // !self.can_split, + // if self.can_split { Some(100.) } else { None }, + // cx, + // { + // let toolbar = self.toolbar.clone(); + // let toolbar_hidden = toolbar.read(cx).hidden(); + // move |_, cx| { + // Flex::column() + // .with_children( + // (!toolbar_hidden) + // .then(|| ChildView::new(&toolbar, cx).expanded()), + // ) + // .with_child( + // ChildView::new(active_item.as_any(), cx).flex(1., true), + // ) + // } + // }, + // ) + // .flex(1., true) + // }) + // .with_child(ChildView::new(&self.tab_context_menu, cx)) + // .into_any() + // } else { + // enum EmptyPane {} + // let theme = theme::current(cx).clone(); + + // dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { + // self.render_blank_pane(&theme, cx) + // }) + // .on_down(MouseButton::Left, |_, _, cx| { + // cx.focus_parent(); + // }) + // .into_any() + // } + // }) + // .on_down( + // MouseButton::Navigate(NavigationDirection::Back), + // move |_, pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace.go_back(pane, cx).detach_and_log_err(cx) + // }) + // }) + // } + // }, + // ) + // .on_down(MouseButton::Navigate(NavigationDirection::Forward), { + // move |_, pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace.go_forward(pane, cx).detach_and_log_err(cx) + // }) + // }) + // } + // } + // }) + // .into_any_named("pane") + // } + + // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + // if !self.has_focus { + // self.has_focus = true; + // cx.emit(Event::Focus); + // cx.notify(); + // } + + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.focus_changed(true, cx); + // }); + + // if let Some(active_item) = self.active_item() { + // if cx.is_self_focused() { + // // Pane was focused directly. We need to either focus a view inside the active item, + // // or focus the active item itself + // if let Some(weak_last_focused_view) = + // self.last_focused_view_by_item.get(&active_item.id()) + // { + // if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) { + // cx.focus(&last_focused_view); + // return; + // } else { + // self.last_focused_view_by_item.remove(&active_item.id()); + // } + // } + + // cx.focus(active_item.as_any()); + // } else if focused != self.tab_bar_context_menu.handle { + // self.last_focused_view_by_item + // .insert(active_item.id(), focused.downgrade()); + // } + // } + // } + + // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + // self.has_focus = false; + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.focus_changed(false, cx); + // }); + // cx.notify(); + // } + + // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { + // Self::reset_to_default_keymap_context(keymap); + // } + // } + + // impl ItemNavHistory { + // pub fn push(&mut self, data: Option, cx: &mut WindowContext) { + // self.history.push(data, self.item.clone(), cx); + // } + + // pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { + // self.history.pop(NavigationMode::GoingBack, cx) + // } + + // pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { + // self.history.pop(NavigationMode::GoingForward, cx) + // } + // } + + // impl NavHistory { + // pub fn for_each_entry( + // &self, + // cx: &AppContext, + // mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), + // ) { + // let borrowed_history = self.0.borrow(); + // borrowed_history + // .forward_stack + // .iter() + // .chain(borrowed_history.backward_stack.iter()) + // .chain(borrowed_history.closed_stack.iter()) + // .for_each(|entry| { + // if let Some(project_and_abs_path) = + // borrowed_history.paths_by_item.get(&entry.item.id()) + // { + // f(entry, project_and_abs_path.clone()); + // } else if let Some(item) = entry.item.upgrade(cx) { + // if let Some(path) = item.project_path(cx) { + // f(entry, (path, None)); + // } + // } + // }) + // } + + // pub fn set_mode(&mut self, mode: NavigationMode) { + // self.0.borrow_mut().mode = mode; + // } pub fn mode(&self) -> NavigationMode { self.0.borrow().mode } - pub fn disable(&mut self) { - self.0.borrow_mut().mode = NavigationMode::Disabled; - } - - pub fn enable(&mut self) { - self.0.borrow_mut().mode = NavigationMode::Normal; - } - - pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { - let mut state = self.0.borrow_mut(); - let entry = match mode { - NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { - return None - } - NavigationMode::GoingBack => &mut state.backward_stack, - NavigationMode::GoingForward => &mut state.forward_stack, - NavigationMode::ReopeningClosedItem => &mut state.closed_stack, - } - .pop_back(); - if entry.is_some() { - state.did_update(cx); - } - entry - } - - pub fn push( - &mut self, - data: Option, - item: Rc, - cx: &mut WindowContext, - ) { - let state = &mut *self.0.borrow_mut(); - match state.mode { - NavigationMode::Disabled => {} - NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { - if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.backward_stack.pop_front(); - } - state.backward_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - state.forward_stack.clear(); - } - NavigationMode::GoingBack => { - if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.forward_stack.pop_front(); - } - state.forward_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - } - NavigationMode::GoingForward => { - if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.backward_stack.pop_front(); - } - state.backward_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - } - NavigationMode::ClosingItem => { - if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.closed_stack.pop_front(); - } - state.closed_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - } - } - state.did_update(cx); - } - - pub fn remove_item(&mut self, item_id: usize) { - let mut state = self.0.borrow_mut(); - state.paths_by_item.remove(&item_id); - state - .backward_stack - .retain(|entry| entry.item.id() != item_id); - state - .forward_stack - .retain(|entry| entry.item.id() != item_id); - state - .closed_stack - .retain(|entry| entry.item.id() != item_id); - } - - pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { - self.0.borrow().paths_by_item.get(&item_id).cloned() - } -} - -impl NavHistoryState { - pub fn did_update(&self, cx: &mut WindowContext) { - if let Some(pane) = self.pane.upgrade(cx) { - cx.defer(move |cx| { - pane.update(cx, |pane, cx| pane.history_updated(cx)); - }); - } - } -} - -pub struct PaneBackdrop { - child_view: usize, - child: AnyElement, + // pub fn disable(&mut self) { + // self.0.borrow_mut().mode = NavigationMode::Disabled; + // } + + // pub fn enable(&mut self) { + // self.0.borrow_mut().mode = NavigationMode::Normal; + // } + + // pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { + // let mut state = self.0.borrow_mut(); + // let entry = match mode { + // NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { + // return None + // } + // NavigationMode::GoingBack => &mut state.backward_stack, + // NavigationMode::GoingForward => &mut state.forward_stack, + // NavigationMode::ReopeningClosedItem => &mut state.closed_stack, + // } + // .pop_back(); + // if entry.is_some() { + // state.did_update(cx); + // } + // entry + // } + + // pub fn push( + // &mut self, + // data: Option, + // item: Rc, + // cx: &mut WindowContext, + // ) { + // let state = &mut *self.0.borrow_mut(); + // match state.mode { + // NavigationMode::Disabled => {} + // NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { + // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.backward_stack.pop_front(); + // } + // state.backward_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // state.forward_stack.clear(); + // } + // NavigationMode::GoingBack => { + // if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.forward_stack.pop_front(); + // } + // state.forward_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // } + // NavigationMode::GoingForward => { + // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.backward_stack.pop_front(); + // } + // state.backward_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // } + // NavigationMode::ClosingItem => { + // if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.closed_stack.pop_front(); + // } + // state.closed_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // } + // } + // state.did_update(cx); + // } + + // pub fn remove_item(&mut self, item_id: usize) { + // let mut state = self.0.borrow_mut(); + // state.paths_by_item.remove(&item_id); + // state + // .backward_stack + // .retain(|entry| entry.item.id() != item_id); + // state + // .forward_stack + // .retain(|entry| entry.item.id() != item_id); + // state + // .closed_stack + // .retain(|entry| entry.item.id() != item_id); + // } + + // pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { + // self.0.borrow().paths_by_item.get(&item_id).cloned() + // } } -impl PaneBackdrop { - pub fn new(pane_item_view: usize, child: AnyElement) -> Self { - PaneBackdrop { - child, - child_view: pane_item_view, - } - } -} - -impl Element for PaneBackdrop { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState) { - let size = self.child.layout(constraint, view, cx); - (size, ()) - } - - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - _: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) -> Self::PaintState { - let background = theme::current(cx).editor.background; - - let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - - cx.scene().push_quad(gpui::Quad { - bounds: RectF::new(bounds.origin(), bounds.size()), - background: Some(background), - ..Default::default() - }); - - let child_view_id = self.child_view; - cx.scene().push_mouse_region( - MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( - gpui::platform::MouseButton::Left, - move |_, _: &mut V, cx| { - let window = cx.window(); - cx.app_context().focus(window, Some(child_view_id)) - }, - ), - ); - - cx.scene().push_layer(Some(bounds)); - self.child.paint(bounds.origin(), visible_bounds, view, cx); - cx.scene().pop_layer(); - } - - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - _bounds: RectF, - _visible_bounds: RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _bounds: RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> serde_json::Value { - gpui::json::json!({ - "type": "Pane Back Drop", - "view": self.child_view, - "child": self.child.debug(view, cx), - }) - } -} - -fn dirty_message_for(buffer_path: Option) -> String { - let path = buffer_path - .as_ref() - .and_then(|p| p.path.to_str()) - .unwrap_or(&"This buffer"); - let path = truncate_and_remove_front(path, 80); - format!("{path} contains unsaved edits. Do you want to save it?") -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::item::test::{TestItem, TestProjectItem}; - use gpui::TestAppContext; - use project::FakeFs; - use settings::SettingsStore; - - #[gpui::test] - async fn test_remove_active_empty(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - pane.update(cx, |pane, cx| { - assert!(pane - .close_active_item(&CloseActiveItem { save_intent: None }, cx) - .is_none()) - }); - } - - #[gpui::test] - async fn test_add_item_with_new_item(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - // 1. Add with a destination index - // a. Add before the active item - set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - Some(0), - cx, - ); - }); - assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); - - // b. Add after the active item - set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - Some(2), - cx, - ); - }); - assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); - - // c. Add at the end of the item list (including off the length) - set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - Some(5), - cx, - ); - }); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - - // 2. Add without a destination index - // a. Add with active item at the start of the item list - set_labeled_items(&pane, ["A*", "B", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - None, - cx, - ); - }); - set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); - - // b. Add with active item at the end of the item list - set_labeled_items(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - None, - cx, - ); - }); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - } - - #[gpui::test] - async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - // 1. Add with a destination index - // 1a. Add before the active item - let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(d, false, false, Some(0), cx); - }); - assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); - - // 1b. Add after the active item - let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(d, false, false, Some(2), cx); - }); - assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); - - // 1c. Add at the end of the item list (including off the length) - let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(a, false, false, Some(5), cx); - }); - assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); - - // 1d. Add same item to active index - let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(b, false, false, Some(1), cx); - }); - assert_item_labels(&pane, ["A", "B*", "C"], cx); - - // 1e. Add item to index after same item in last position - let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(c, false, false, Some(2), cx); - }); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - - // 2. Add without a destination index - // 2a. Add with active item at the start of the item list - let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(d, false, false, None, cx); - }); - assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); - - // 2b. Add with active item at the end of the item list - let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(a, false, false, None, cx); - }); - assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); - - // 2c. Add active item to active item at end of list - let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(c, false, false, None, cx); - }); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - - // 2d. Add active item to active item at start of list - let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(a, false, false, None, cx); - }); - assert_item_labels(&pane, ["A*", "B", "C"], cx); - } - - #[gpui::test] - async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - // singleton view - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(true) - .with_label("buffer 1") - .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1*"], cx); - - // new singleton view with the same project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(true) - .with_label("buffer 1") - .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1*"], cx); - - // new singleton view with different project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(true) - .with_label("buffer 2") - .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); - - // new multibuffer view with the same project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(false) - .with_label("multibuffer 1") - .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); - - // another multibuffer view with the same project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(false) - .with_label("multibuffer 1b") - .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels( - &pane, - ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], - cx, - ); - } - - #[gpui::test] - async fn test_remove_item_ordering(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - add_labeled_item(&pane, "A", false, cx); - add_labeled_item(&pane, "B", false, cx); - add_labeled_item(&pane, "C", false, cx); - add_labeled_item(&pane, "D", false, cx); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - - pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx)); - add_labeled_item(&pane, "1", false, cx); - assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); - - pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "B*", "C"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "C*"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A*"], cx); - } - - #[gpui::test] - async fn test_close_inactive_items(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - - pane.update(cx, |pane, cx| { - pane.close_inactive_items(&CloseInactiveItems, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["C*"], cx); - } - - #[gpui::test] - async fn test_close_clean_items(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - add_labeled_item(&pane, "A", true, cx); - add_labeled_item(&pane, "B", false, cx); - add_labeled_item(&pane, "C", true, cx); - add_labeled_item(&pane, "D", false, cx); - add_labeled_item(&pane, "E", false, cx); - assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - - pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A^", "C*^"], cx); - } - - #[gpui::test] - async fn test_close_items_to_the_left(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - - pane.update(cx, |pane, cx| { - pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["C*", "D", "E"], cx); - } - - #[gpui::test] - async fn test_close_items_to_the_right(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - - pane.update(cx, |pane, cx| { - pane.close_items_to_the_right(&CloseItemsToTheRight, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - } - - #[gpui::test] - async fn test_close_all_items(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - add_labeled_item(&pane, "A", false, cx); - add_labeled_item(&pane, "B", false, cx); - add_labeled_item(&pane, "C", false, cx); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - - pane.update(cx, |pane, cx| { - pane.close_all_items(&CloseAllItems { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, [], cx); - - add_labeled_item(&pane, "A", true, cx); - add_labeled_item(&pane, "B", true, cx); - add_labeled_item(&pane, "C", true, cx); - assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); - - let save = pane - .update(cx, |pane, cx| { - pane.close_all_items(&CloseAllItems { save_intent: None }, cx) - }) - .unwrap(); - - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - save.await.unwrap(); - assert_item_labels(&pane, [], cx); - } - - fn init_test(cx: &mut TestAppContext) { - cx.update(|cx| { - cx.set_global(SettingsStore::test(cx)); - theme::init((), cx); - crate::init_settings(cx); - Project::init_settings(cx); - }); - } - - fn add_labeled_item( - pane: &ViewHandle, - label: &str, - is_dirty: bool, - cx: &mut TestAppContext, - ) -> Box> { - pane.update(cx, |pane, cx| { - let labeled_item = - Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty))); - pane.add_item(labeled_item.clone(), false, false, None, cx); - labeled_item - }) - } - - fn set_labeled_items( - pane: &ViewHandle, - labels: [&str; COUNT], - cx: &mut TestAppContext, - ) -> [Box>; COUNT] { - pane.update(cx, |pane, cx| { - pane.items.clear(); - let mut active_item_index = 0; - - let mut index = 0; - let items = labels.map(|mut label| { - if label.ends_with("*") { - label = label.trim_end_matches("*"); - active_item_index = index; - } - - let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); - pane.add_item(labeled_item.clone(), false, false, None, cx); - index += 1; - labeled_item - }); - - pane.activate_item(active_item_index, false, false, cx); - - items - }) - } - - // Assert the item label, with the active item label suffixed with a '*' - fn assert_item_labels( - pane: &ViewHandle, - expected_states: [&str; COUNT], - cx: &mut TestAppContext, - ) { - pane.read_with(cx, |pane, cx| { - let actual_states = pane - .items - .iter() - .enumerate() - .map(|(ix, item)| { - let mut state = item - .as_any() - .downcast_ref::() - .unwrap() - .read(cx) - .label - .clone(); - if ix == pane.active_item_index { - state.push('*'); - } - if item.is_dirty(cx) { - state.push('^'); - } - state - }) - .collect::>(); - - assert_eq!( - actual_states, expected_states, - "pane items do not match expectation" - ); - }) - } -} +// impl NavHistoryState { +// pub fn did_update(&self, cx: &mut WindowContext) { +// if let Some(pane) = self.pane.upgrade(cx) { +// cx.defer(move |cx| { +// pane.update(cx, |pane, cx| pane.history_updated(cx)); +// }); +// } +// } +// } + +// pub struct PaneBackdrop { +// child_view: usize, +// child: AnyElement, +// } + +// impl PaneBackdrop { +// pub fn new(pane_item_view: usize, child: AnyElement) -> Self { +// PaneBackdrop { +// child, +// child_view: pane_item_view, +// } +// } +// } + +// impl Element for PaneBackdrop { +// type LayoutState = (); + +// type PaintState = (); + +// fn layout( +// &mut self, +// constraint: gpui::SizeConstraint, +// view: &mut V, +// cx: &mut ViewContext, +// ) -> (Vector2F, Self::LayoutState) { +// let size = self.child.layout(constraint, view, cx); +// (size, ()) +// } + +// fn paint( +// &mut self, +// bounds: RectF, +// visible_bounds: RectF, +// _: &mut Self::LayoutState, +// view: &mut V, +// cx: &mut ViewContext, +// ) -> Self::PaintState { +// let background = theme::current(cx).editor.background; + +// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + +// cx.scene().push_quad(gpui::Quad { +// bounds: RectF::new(bounds.origin(), bounds.size()), +// background: Some(background), +// ..Default::default() +// }); + +// let child_view_id = self.child_view; +// cx.scene().push_mouse_region( +// MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( +// gpui::platform::MouseButton::Left, +// move |_, _: &mut V, cx| { +// let window = cx.window(); +// cx.app_context().focus(window, Some(child_view_id)) +// }, +// ), +// ); + +// cx.scene().push_layer(Some(bounds)); +// self.child.paint(bounds.origin(), visible_bounds, view, cx); +// cx.scene().pop_layer(); +// } + +// fn rect_for_text_range( +// &self, +// range_utf16: std::ops::Range, +// _bounds: RectF, +// _visible_bounds: RectF, +// _layout: &Self::LayoutState, +// _paint: &Self::PaintState, +// view: &V, +// cx: &gpui::ViewContext, +// ) -> Option { +// self.child.rect_for_text_range(range_utf16, view, cx) +// } + +// fn debug( +// &self, +// _bounds: RectF, +// _layout: &Self::LayoutState, +// _paint: &Self::PaintState, +// view: &V, +// cx: &gpui::ViewContext, +// ) -> serde_json::Value { +// gpui::json::json!({ +// "type": "Pane Back Drop", +// "view": self.child_view, +// "child": self.child.debug(view, cx), +// }) +// } +// } + +// fn dirty_message_for(buffer_path: Option) -> String { +// let path = buffer_path +// .as_ref() +// .and_then(|p| p.path.to_str()) +// .unwrap_or(&"This buffer"); +// let path = truncate_and_remove_front(path, 80); +// format!("{path} contains unsaved edits. Do you want to save it?") +// } + +// todo!("uncomment tests") +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::item::test::{TestItem, TestProjectItem}; +// use gpui::TestAppContext; +// use project::FakeFs; +// use settings::SettingsStore; + +// #[gpui::test] +// async fn test_remove_active_empty(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// pane.update(cx, |pane, cx| { +// assert!(pane +// .close_active_item(&CloseActiveItem { save_intent: None }, cx) +// .is_none()) +// }); +// } + +// #[gpui::test] +// async fn test_add_item_with_new_item(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// // 1. Add with a destination index +// // a. Add before the active item +// set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// Some(0), +// cx, +// ); +// }); +// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + +// // b. Add after the active item +// set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// Some(2), +// cx, +// ); +// }); +// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + +// // c. Add at the end of the item list (including off the length) +// set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// Some(5), +// cx, +// ); +// }); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + +// // 2. Add without a destination index +// // a. Add with active item at the start of the item list +// set_labeled_items(&pane, ["A*", "B", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// None, +// cx, +// ); +// }); +// set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); + +// // b. Add with active item at the end of the item list +// set_labeled_items(&pane, ["A", "B", "C*"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// None, +// cx, +// ); +// }); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); +// } + +// #[gpui::test] +// async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// // 1. Add with a destination index +// // 1a. Add before the active item +// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(d, false, false, Some(0), cx); +// }); +// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + +// // 1b. Add after the active item +// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(d, false, false, Some(2), cx); +// }); +// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + +// // 1c. Add at the end of the item list (including off the length) +// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(a, false, false, Some(5), cx); +// }); +// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + +// // 1d. Add same item to active index +// let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(b, false, false, Some(1), cx); +// }); +// assert_item_labels(&pane, ["A", "B*", "C"], cx); + +// // 1e. Add item to index after same item in last position +// let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(c, false, false, Some(2), cx); +// }); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); + +// // 2. Add without a destination index +// // 2a. Add with active item at the start of the item list +// let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(d, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); + +// // 2b. Add with active item at the end of the item list +// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(a, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + +// // 2c. Add active item to active item at end of list +// let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(c, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); + +// // 2d. Add active item to active item at start of list +// let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(a, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["A*", "B", "C"], cx); +// } + +// #[gpui::test] +// async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// // singleton view +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(true) +// .with_label("buffer 1") +// .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1*"], cx); + +// // new singleton view with the same project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(true) +// .with_label("buffer 1") +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1*"], cx); + +// // new singleton view with different project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(true) +// .with_label("buffer 2") +// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); + +// // new multibuffer view with the same project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(false) +// .with_label("multibuffer 1") +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); + +// // another multibuffer view with the same project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(false) +// .with_label("multibuffer 1b") +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels( +// &pane, +// ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], +// cx, +// ); +// } + +// #[gpui::test] +// async fn test_remove_item_ordering(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// add_labeled_item(&pane, "A", false, cx); +// add_labeled_item(&pane, "B", false, cx); +// add_labeled_item(&pane, "C", false, cx); +// add_labeled_item(&pane, "D", false, cx); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + +// pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx)); +// add_labeled_item(&pane, "1", false, cx); +// assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); + +// pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "B*", "C"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "C*"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A*"], cx); +// } + +// #[gpui::test] +// async fn test_close_inactive_items(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_inactive_items(&CloseInactiveItems, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["C*"], cx); +// } + +// #[gpui::test] +// async fn test_close_clean_items(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// add_labeled_item(&pane, "A", true, cx); +// add_labeled_item(&pane, "B", false, cx); +// add_labeled_item(&pane, "C", true, cx); +// add_labeled_item(&pane, "D", false, cx); +// add_labeled_item(&pane, "E", false, cx); +// assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); + +// pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A^", "C*^"], cx); +// } + +// #[gpui::test] +// async fn test_close_items_to_the_left(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["C*", "D", "E"], cx); +// } + +// #[gpui::test] +// async fn test_close_items_to_the_right(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_items_to_the_right(&CloseItemsToTheRight, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); +// } + +// #[gpui::test] +// async fn test_close_all_items(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// add_labeled_item(&pane, "A", false, cx); +// add_labeled_item(&pane, "B", false, cx); +// add_labeled_item(&pane, "C", false, cx); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_all_items(&CloseAllItems { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, [], cx); + +// add_labeled_item(&pane, "A", true, cx); +// add_labeled_item(&pane, "B", true, cx); +// add_labeled_item(&pane, "C", true, cx); +// assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); + +// let save = pane +// .update(cx, |pane, cx| { +// pane.close_all_items(&CloseAllItems { save_intent: None }, cx) +// }) +// .unwrap(); + +// cx.foreground().run_until_parked(); +// window.simulate_prompt_answer(2, cx); +// save.await.unwrap(); +// assert_item_labels(&pane, [], cx); +// } + +// fn init_test(cx: &mut TestAppContext) { +// cx.update(|cx| { +// cx.set_global(SettingsStore::test(cx)); +// theme::init((), cx); +// crate::init_settings(cx); +// Project::init_settings(cx); +// }); +// } + +// fn add_labeled_item( +// pane: &ViewHandle, +// label: &str, +// is_dirty: bool, +// cx: &mut TestAppContext, +// ) -> Box> { +// pane.update(cx, |pane, cx| { +// let labeled_item = +// Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty))); +// pane.add_item(labeled_item.clone(), false, false, None, cx); +// labeled_item +// }) +// } + +// fn set_labeled_items( +// pane: &ViewHandle, +// labels: [&str; COUNT], +// cx: &mut TestAppContext, +// ) -> [Box>; COUNT] { +// pane.update(cx, |pane, cx| { +// pane.items.clear(); +// let mut active_item_index = 0; + +// let mut index = 0; +// let items = labels.map(|mut label| { +// if label.ends_with("*") { +// label = label.trim_end_matches("*"); +// active_item_index = index; +// } + +// let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); +// pane.add_item(labeled_item.clone(), false, false, None, cx); +// index += 1; +// labeled_item +// }); + +// pane.activate_item(active_item_index, false, false, cx); + +// items +// }) +// } + +// // Assert the item label, with the active item label suffixed with a '*' +// fn assert_item_labels( +// pane: &ViewHandle, +// expected_states: [&str; COUNT], +// cx: &mut TestAppContext, +// ) { +// pane.read_with(cx, |pane, cx| { +// let actual_states = pane +// .items +// .iter() +// .enumerate() +// .map(|(ix, item)| { +// let mut state = item +// .as_any() +// .downcast_ref::() +// .unwrap() +// .read(cx) +// .label +// .clone(); +// if ix == pane.active_item_index { +// state.push('*'); +// } +// if item.is_dirty(cx) { +// state.push('^'); +// } +// state +// }) +// .collect::>(); + +// assert_eq!( +// actual_states, expected_states, +// "pane items do not match expectation" +// ); +// }) +// } +// } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index aef03dcda0623a3520c71b3df1b0a60709f6a99b..fce913128a7d88b4ebd9ee705fea7a1b45cccaea 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,22 +1,22 @@ use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, Result}; -use call::{ActiveCall, ParticipantLocation}; +use call2::{ActiveCall, ParticipantLocation}; use collections::HashMap; -use gpui::{ - elements::*, - geometry::{rect::RectF, vector::Vector2F}, - platform::{CursorStyle, MouseButton}, - AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle, -}; -use project::Project; +use gpui2::{Bounds, Handle, Pixels, Point, View, ViewContext}; +use project2::Project; use serde::Deserialize; use std::{cell::RefCell, rc::Rc, sync::Arc}; -use theme::Theme; +use theme2::Theme; const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; +enum Axis { + Vertical, + Horizontal, +} + #[derive(Clone, Debug, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, @@ -27,7 +27,7 @@ impl PaneGroup { Self { root } } - pub fn new(pane: ViewHandle) -> Self { + pub fn new(pane: View) -> Self { Self { root: Member::Pane(pane), } @@ -35,8 +35,8 @@ impl PaneGroup { pub fn split( &mut self, - old_pane: &ViewHandle, - new_pane: &ViewHandle, + old_pane: &View, + new_pane: &View, direction: SplitDirection, ) -> Result<()> { match &mut self.root { @@ -52,14 +52,14 @@ impl PaneGroup { } } - pub fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + pub fn bounding_box_for_pane(&self, pane: &View) -> Option> { match &self.root { Member::Pane(_) => None, Member::Axis(axis) => axis.bounding_box_for_pane(pane), } } - pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + pub fn pane_at_pixel_position(&self, coordinate: Point) -> Option<&View> { match &self.root { Member::Pane(pane) => Some(pane), Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), @@ -70,7 +70,7 @@ impl PaneGroup { /// - Ok(true) if it found and removed a pane /// - Ok(false) if it found but did not remove the pane /// - Err(_) if it did not find the pane - pub fn remove(&mut self, pane: &ViewHandle) -> Result { + pub fn remove(&mut self, pane: &View) -> Result { match &mut self.root { Member::Pane(_) => Ok(false), Member::Axis(axis) => { @@ -82,7 +82,7 @@ impl PaneGroup { } } - pub fn swap(&mut self, from: &ViewHandle, to: &ViewHandle) { + pub fn swap(&mut self, from: &View, to: &View) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => axis.swap(from, to), @@ -91,11 +91,11 @@ impl PaneGroup { pub(crate) fn render( &self, - project: &ModelHandle, + project: &Handle, theme: &Theme, - follower_states: &HashMap, FollowerState>, - active_call: Option<&ModelHandle>, - active_pane: &ViewHandle, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, @@ -113,7 +113,7 @@ impl PaneGroup { ) } - pub(crate) fn panes(&self) -> Vec<&ViewHandle> { + pub(crate) fn panes(&self) -> Vec<&View> { let mut panes = Vec::new(); self.root.collect_panes(&mut panes); panes @@ -123,15 +123,11 @@ impl PaneGroup { #[derive(Clone, Debug, PartialEq)] pub(crate) enum Member { Axis(PaneAxis), - Pane(ViewHandle), + Pane(View), } impl Member { - fn new_axis( - old_pane: ViewHandle, - new_pane: ViewHandle, - direction: SplitDirection, - ) -> Self { + fn new_axis(old_pane: View, new_pane: View, direction: SplitDirection) -> Self { use Axis::*; use SplitDirection::*; @@ -148,7 +144,7 @@ impl Member { Member::Axis(PaneAxis::new(axis, members)) } - fn contains(&self, needle: &ViewHandle) -> bool { + fn contains(&self, needle: &View) -> bool { match self { Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)), Member::Pane(pane) => pane == needle, @@ -157,12 +153,12 @@ impl Member { pub fn render( &self, - project: &ModelHandle, + project: &Handle, basis: usize, theme: &Theme, - follower_states: &HashMap, FollowerState>, - active_call: Option<&ModelHandle>, - active_pane: &ViewHandle, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, @@ -295,7 +291,7 @@ impl Member { } } - fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle>) { + fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View>) { match self { Member::Axis(axis) => { for member in &axis.members { @@ -343,8 +339,8 @@ impl PaneAxis { fn split( &mut self, - old_pane: &ViewHandle, - new_pane: &ViewHandle, + old_pane: &View, + new_pane: &View, direction: SplitDirection, ) -> Result<()> { for (mut idx, member) in self.members.iter_mut().enumerate() { @@ -375,7 +371,7 @@ impl PaneAxis { Err(anyhow!("Pane not found")) } - fn remove(&mut self, pane_to_remove: &ViewHandle) -> Result> { + fn remove(&mut self, pane_to_remove: &View) -> Result> { let mut found_pane = false; let mut remove_member = None; for (idx, member) in self.members.iter_mut().enumerate() { @@ -417,7 +413,7 @@ impl PaneAxis { } } - fn swap(&mut self, from: &ViewHandle, to: &ViewHandle) { + fn swap(&mut self, from: &View, to: &View) { for member in self.members.iter_mut() { match member { Member::Axis(axis) => axis.swap(from, to), @@ -432,7 +428,7 @@ impl PaneAxis { } } - fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + fn bounding_box_for_pane(&self, pane: &View) -> Option { debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); for (idx, member) in self.members.iter().enumerate() { @@ -452,7 +448,7 @@ impl PaneAxis { None } - fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&View> { debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); let bounding_boxes = self.bounding_boxes.borrow(); @@ -472,12 +468,12 @@ impl PaneAxis { fn render( &self, - project: &ModelHandle, + project: &Handle, basis: usize, theme: &Theme, - follower_states: &HashMap, FollowerState>, - active_call: Option<&ModelHandle>, - active_pane: &ViewHandle, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index ddde5c35541bce265105d78b4d8125545bc38ff8..591fab5cd98a77eb60340c9885b056e35860884c 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -1,12 +1,12 @@ use std::{any::Any, sync::Arc}; -use gpui::{ - AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, -}; -use project::search::SearchQuery; +use gpui2::{AppContext, Subscription, Task, View, ViewContext, WindowContext}; +use project2::search::SearchQuery; -use crate::{item::WeakItemHandle, Item, ItemHandle}; +use crate::{ + item::{Item, WeakItemHandle}, + ItemHandle, +}; #[derive(Debug)] pub enum SearchEvent { @@ -128,7 +128,7 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; } -impl SearchableItemHandle for ViewHandle { +impl SearchableItemHandle for View { fn downgrade(&self) -> Box { Box::new(self.downgrade()) } @@ -231,17 +231,19 @@ fn downcast_matches(matches: &Vec>) -> Vec> for AnyViewHandle { - fn from(this: Box) -> Self { - this.as_any().clone() - } -} +// todo!() +// impl From> for AnyViewHandle { +// fn from(this: Box) -> Self { +// this.as_any().clone() +// } +// } -impl From<&Box> for AnyViewHandle { - fn from(this: &Box) -> Self { - this.as_any().clone() - } -} +// todo!() +// impl From<&Box> for AnyViewHandle { +// fn from(this: &Box) -> Self { +// this.as_any().clone() +// } +// } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { @@ -254,18 +256,20 @@ impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; - fn into_any(self) -> AnyWeakViewHandle; + // todo!() + // fn into_any(self) -> AnyWeakViewHandle; } -impl WeakSearchableItemHandle for WeakViewHandle { - fn upgrade(&self, cx: &AppContext) -> Option> { - Some(Box::new(self.upgrade(cx)?)) - } +// todo!() +// impl WeakSearchableItemHandle for WeakViewHandle { +// fn upgrade(&self, cx: &AppContext) -> Option> { +// Some(Box::new(self.upgrade(cx)?)) +// } - fn into_any(self) -> AnyWeakViewHandle { - self.into_any() - } -} +// fn into_any(self) -> AnyWeakViewHandle { +// self.into_any() +// } +// } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 06f6d65e2e761a688d32e484e5484859803183f6..3436a6805e24b24f9bf9210eea274fac04f248a5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,16 +1,16 @@ // pub mod dock; pub mod item; // pub mod notifications; -// pub mod pane; -// pub mod pane_group; +pub mod pane; +pub mod pane_group; // mod persistence; -// pub mod searchable; +pub mod searchable; // pub mod shared_screen; // mod status_bar; -// mod toolbar; +mod toolbar; // mod workspace_settings; -// use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; // use call2::ActiveCall; // use client2::{ // proto::{self, PeerId}, @@ -52,8 +52,8 @@ pub mod item; // use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; // use lazy_static::lazy_static; // use notifications::{NotificationHandle, NotifyResultExt}; -// pub use pane::*; -// pub use pane_group::*; +pub use pane::*; +pub use pane_group::*; // use persistence::{model::SerializedItem, DB}; // pub use persistence::{ // model::{ItemId, WorkspaceLocation}, @@ -66,7 +66,7 @@ pub mod item; // use status_bar::StatusBar; // pub use status_bar::StatusItemView; // use theme::{Theme, ThemeSettings}; -// pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; +pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; // use util::ResultExt; // pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings}; @@ -234,7 +234,7 @@ pub mod item; // ] // ); -// pub type WorkspaceId = i64; +pub type WorkspaceId = i64; // pub fn init_settings(cx: &mut AppContext) { // settings::register::(cx); @@ -365,3381 +365,3381 @@ pub mod item; // }); // } -// type ProjectItemBuilders = HashMap< -// TypeId, -// fn(ModelHandle, AnyModelHandle, &mut ViewContext) -> Box, -// >; -// pub fn register_project_item(cx: &mut AppContext) { -// cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { -// builders.insert(TypeId::of::(), |project, model, cx| { -// let item = model.downcast::().unwrap(); -// Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) -// }); -// }); -// } - -// type FollowableItemBuilder = fn( -// ViewHandle, -// ViewHandle, -// ViewId, -// &mut Option, -// &mut AppContext, -// ) -> Option>>>; -// type FollowableItemBuilders = HashMap< -// TypeId, -// ( -// FollowableItemBuilder, -// fn(&AnyViewHandle) -> Box, -// ), -// >; -// pub fn register_followable_item(cx: &mut AppContext) { -// cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { -// builders.insert( -// TypeId::of::(), -// ( -// |pane, workspace, id, state, cx| { -// I::from_state_proto(pane, workspace, id, state, cx).map(|task| { -// cx.foreground() -// .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) -// }) -// }, -// |this| Box::new(this.clone().downcast::().unwrap()), -// ), -// ); -// }); -// } - -// type ItemDeserializers = HashMap< -// Arc, -// fn( -// ModelHandle, -// WeakViewHandle, -// WorkspaceId, -// ItemId, -// &mut ViewContext, -// ) -> Task>>, -// >; -// pub fn register_deserializable_item(cx: &mut AppContext) { -// cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| { -// if let Some(serialized_item_kind) = I::serialized_item_kind() { -// deserializers.insert( -// Arc::from(serialized_item_kind), -// |project, workspace, workspace_id, item_id, cx| { -// let task = I::deserialize(project, workspace, workspace_id, item_id, cx); -// cx.foreground() -// .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) -// }, -// ); -// } -// }); -// } - -pub struct AppState { - pub languages: Arc, - pub client: Arc, - pub user_store: Handle, - pub workspace_store: Handle, - pub fs: Arc, - pub build_window_options: - fn(Option, Option, &MainThread) -> WindowOptions, - pub initialize_workspace: - fn(WeakHandle, bool, Arc, AsyncAppContext) -> Task>, - pub node_runtime: Arc, -} - -pub struct WorkspaceStore { - workspaces: HashSet>, - followers: Vec, - client: Arc, - _subscriptions: Vec, -} - -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] -struct Follower { - project_id: Option, - peer_id: PeerId, -} - -// impl AppState { -// #[cfg(any(test, feature = "test-support"))] -// pub fn test(cx: &mut AppContext) -> Arc { -// use node_runtime::FakeNodeRuntime; -// use settings::SettingsStore; - -// if !cx.has_global::() { -// cx.set_global(SettingsStore::test(cx)); -// } - -// let fs = fs::FakeFs::new(cx.background().clone()); -// let languages = Arc::new(LanguageRegistry::test()); -// let http_client = util::http::FakeHttpClient::with_404_response(); -// let client = Client::new(http_client.clone(), cx); -// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); -// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); - -// theme::init((), cx); -// client::init(&client, cx); -// crate::init_settings(cx); - -// Arc::new(Self { -// client, -// fs, -// languages, -// user_store, -// // channel_store, -// workspace_store, -// node_runtime: FakeNodeRuntime::new(), -// initialize_workspace: |_, _, _, _| Task::ready(Ok(())), -// build_window_options: |_, _, _| Default::default(), -// }) -// } -// } - -// struct DelayedDebouncedEditAction { -// task: Option>, -// cancel_channel: Option>, -// } - -// impl DelayedDebouncedEditAction { -// fn new() -> DelayedDebouncedEditAction { -// DelayedDebouncedEditAction { -// task: None, -// cancel_channel: None, -// } -// } - -// fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) -// where -// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, -// { -// if let Some(channel) = self.cancel_channel.take() { -// _ = channel.send(()); -// } - -// let (sender, mut receiver) = oneshot::channel::<()>(); -// self.cancel_channel = Some(sender); - -// let previous_task = self.task.take(); -// self.task = Some(cx.spawn(|workspace, mut cx| async move { -// let mut timer = cx.background().timer(delay).fuse(); -// if let Some(previous_task) = previous_task { -// previous_task.await; -// } - -// futures::select_biased! { -// _ = receiver => return, -// _ = timer => {} -// } - -// if let Some(result) = workspace -// .update(&mut cx, |workspace, cx| (func)(workspace, cx)) -// .log_err() -// { -// result.await.log_err(); -// } -// })); -// } -// } - -// pub enum Event { -// PaneAdded(ViewHandle), -// ContactRequestedJoin(u64), -// } - -pub struct Workspace { - weak_self: WeakHandle, - // modal: Option, - // zoomed: Option, - // zoomed_position: Option, - // center: PaneGroup, - // left_dock: ViewHandle, - // bottom_dock: ViewHandle, - // right_dock: ViewHandle, - // panes: Vec>, - // panes_by_item: HashMap>, - // active_pane: ViewHandle, - // last_active_center_pane: Option>, - // last_active_view_id: Option, - // status_bar: ViewHandle, - // titlebar_item: Option, - // notifications: Vec<(TypeId, usize, Box)>, - project: Handle, - // follower_states: HashMap, FollowerState>, - // last_leaders_by_pane: HashMap, PeerId>, - // window_edited: bool, - // active_call: Option<(ModelHandle, Vec)>, - // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, - // database_id: WorkspaceId, - // app_state: Arc, - // subscriptions: Vec, - // _apply_leader_updates: Task>, - // _observe_current_user: Task>, - // _schedule_serialize: Option>, - // pane_history_timestamp: Arc, -} - -// struct ActiveModal { -// view: Box, -// previously_focused_view_id: Option, -// } - -// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -// pub struct ViewId { -// pub creator: PeerId, -// pub id: u64, -// } - -// #[derive(Default)] -// struct FollowerState { -// leader_id: PeerId, -// active_view_id: Option, -// items_by_leader_view_id: HashMap>, -// } - -// enum WorkspaceBounds {} - -// impl Workspace { -// pub fn new( -// workspace_id: WorkspaceId, -// project: ModelHandle, -// app_state: Arc, -// cx: &mut ViewContext, -// ) -> Self { -// cx.observe(&project, |_, _, cx| cx.notify()).detach(); -// cx.subscribe(&project, move |this, _, event, cx| { -// match event { -// project::Event::RemoteIdChanged(_) => { -// this.update_window_title(cx); -// } - -// project::Event::CollaboratorLeft(peer_id) => { -// this.collaborator_left(*peer_id, cx); -// } - -// project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { -// this.update_window_title(cx); -// this.serialize_workspace(cx); -// } - -// project::Event::DisconnectedFromHost => { -// this.update_window_edited(cx); -// cx.blur(); -// } - -// project::Event::Closed => { -// cx.remove_window(); -// } - -// project::Event::DeletedEntry(entry_id) => { -// for pane in this.panes.iter() { -// pane.update(cx, |pane, cx| { -// pane.handle_deleted_project_item(*entry_id, cx) -// }); -// } -// } - -// project::Event::Notification(message) => this.show_notification(0, cx, |cx| { -// cx.add_view(|_| MessageNotification::new(message.clone())) -// }), - -// _ => {} -// } -// cx.notify() -// }) -// .detach(); - -// let weak_handle = cx.weak_handle(); -// let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); - -// let center_pane = cx.add_view(|cx| { -// Pane::new( -// weak_handle.clone(), -// project.clone(), -// pane_history_timestamp.clone(), -// cx, -// ) -// }); -// cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); -// cx.focus(¢er_pane); -// cx.emit(Event::PaneAdded(center_pane.clone())); - -// app_state.workspace_store.update(cx, |store, _| { -// store.workspaces.insert(weak_handle.clone()); -// }); - -// let mut current_user = app_state.user_store.read(cx).watch_current_user(); -// let mut connection_status = app_state.client.status(); -// let _observe_current_user = cx.spawn(|this, mut cx| async move { -// current_user.recv().await; -// connection_status.recv().await; -// let mut stream = -// Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); - -// while stream.recv().await.is_some() { -// this.update(&mut cx, |_, cx| cx.notify())?; -// } -// anyhow::Ok(()) -// }); - -// // All leader updates are enqueued and then processed in a single task, so -// // that each asynchronous operation can be run in order. -// let (leader_updates_tx, mut leader_updates_rx) = -// mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); -// let _apply_leader_updates = cx.spawn(|this, mut cx| async move { -// while let Some((leader_id, update)) = leader_updates_rx.next().await { -// Self::process_leader_update(&this, leader_id, update, &mut cx) -// .await -// .log_err(); -// } - -// Ok(()) -// }); - -// cx.emit_global(WorkspaceCreated(weak_handle.clone())); - -// let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); -// let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); -// let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); -// let left_dock_buttons = -// cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); -// let bottom_dock_buttons = -// cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); -// let right_dock_buttons = -// cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); -// let status_bar = cx.add_view(|cx| { -// let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); -// status_bar.add_left_item(left_dock_buttons, cx); -// status_bar.add_right_item(right_dock_buttons, cx); -// status_bar.add_right_item(bottom_dock_buttons, cx); -// status_bar -// }); - -// cx.update_default_global::, _, _>(|drag_and_drop, _| { -// drag_and_drop.register_container(weak_handle.clone()); -// }); - -// let mut active_call = None; -// if cx.has_global::>() { -// let call = cx.global::>().clone(); -// let mut subscriptions = Vec::new(); -// subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); -// active_call = Some((call, subscriptions)); -// } - -// let subscriptions = vec![ -// cx.observe_fullscreen(|_, _, cx| cx.notify()), -// cx.observe_window_activation(Self::on_window_activation_changed), -// cx.observe_window_bounds(move |_, mut bounds, display, cx| { -// // Transform fixed bounds to be stored in terms of the containing display -// if let WindowBounds::Fixed(mut window_bounds) = bounds { -// if let Some(screen) = cx.platform().screen_by_id(display) { -// let screen_bounds = screen.bounds(); -// window_bounds -// .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); -// window_bounds -// .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); -// bounds = WindowBounds::Fixed(window_bounds); -// } -// } - -// cx.background() -// .spawn(DB.set_window_bounds(workspace_id, bounds, display)) -// .detach_and_log_err(cx); -// }), -// cx.observe(&left_dock, |this, _, cx| { -// this.serialize_workspace(cx); -// cx.notify(); -// }), -// cx.observe(&bottom_dock, |this, _, cx| { -// this.serialize_workspace(cx); -// cx.notify(); -// }), -// cx.observe(&right_dock, |this, _, cx| { -// this.serialize_workspace(cx); -// cx.notify(); -// }), -// ]; - -// cx.defer(|this, cx| this.update_window_title(cx)); -// Workspace { -// weak_self: weak_handle.clone(), -// modal: None, -// zoomed: None, -// zoomed_position: None, -// center: PaneGroup::new(center_pane.clone()), -// panes: vec![center_pane.clone()], -// panes_by_item: Default::default(), -// active_pane: center_pane.clone(), -// last_active_center_pane: Some(center_pane.downgrade()), -// last_active_view_id: None, -// status_bar, -// titlebar_item: None, -// notifications: Default::default(), -// left_dock, -// bottom_dock, -// right_dock, -// project: project.clone(), -// follower_states: Default::default(), -// last_leaders_by_pane: Default::default(), -// window_edited: false, -// active_call, -// database_id: workspace_id, -// app_state, -// _observe_current_user, -// _apply_leader_updates, -// _schedule_serialize: None, -// leader_updates_tx, -// subscriptions, -// pane_history_timestamp, -// } -// } - -// fn new_local( -// abs_paths: Vec, -// app_state: Arc, -// requesting_window: Option>, -// cx: &mut AppContext, -// ) -> Task<( -// WeakViewHandle, -// Vec, anyhow::Error>>>, -// )> { -// let project_handle = Project::local( -// app_state.client.clone(), -// app_state.node_runtime.clone(), -// app_state.user_store.clone(), -// app_state.languages.clone(), -// app_state.fs.clone(), -// cx, -// ); - -// cx.spawn(|mut cx| async move { -// let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); - -// let paths_to_open = Arc::new(abs_paths); - -// // Get project paths for all of the abs_paths -// let mut worktree_roots: HashSet> = Default::default(); -// let mut project_paths: Vec<(PathBuf, Option)> = -// Vec::with_capacity(paths_to_open.len()); -// for path in paths_to_open.iter().cloned() { -// if let Some((worktree, project_entry)) = cx -// .update(|cx| { -// Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) -// }) -// .await -// .log_err() -// { -// worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); -// project_paths.push((path, Some(project_entry))); -// } else { -// project_paths.push((path, None)); -// } -// } - -// let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { -// serialized_workspace.id -// } else { -// DB.next_id().await.unwrap_or(0) -// }; - -// let window = if let Some(window) = requesting_window { -// window.replace_root(&mut cx, |cx| { -// Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) -// }); -// window -// } else { -// { -// let window_bounds_override = window_bounds_env_override(&cx); -// let (bounds, display) = if let Some(bounds) = window_bounds_override { -// (Some(bounds), None) -// } else { -// serialized_workspace -// .as_ref() -// .and_then(|serialized_workspace| { -// let display = serialized_workspace.display?; -// let mut bounds = serialized_workspace.bounds?; - -// // Stored bounds are relative to the containing display. -// // So convert back to global coordinates if that screen still exists -// if let WindowBounds::Fixed(mut window_bounds) = bounds { -// if let Some(screen) = cx.platform().screen_by_id(display) { -// let screen_bounds = screen.bounds(); -// window_bounds.set_origin_x( -// window_bounds.origin_x() + screen_bounds.origin_x(), -// ); -// window_bounds.set_origin_y( -// window_bounds.origin_y() + screen_bounds.origin_y(), -// ); -// bounds = WindowBounds::Fixed(window_bounds); -// } else { -// // Screen no longer exists. Return none here. -// return None; -// } -// } - -// Some((bounds, display)) -// }) -// .unzip() -// }; - -// // Use the serialized workspace to construct the new window -// cx.add_window( -// (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), -// |cx| { -// Workspace::new( -// workspace_id, -// project_handle.clone(), -// app_state.clone(), -// cx, -// ) -// }, -// ) -// } -// }; - -// // We haven't yielded the main thread since obtaining the window handle, -// // so the window exists. -// let workspace = window.root(&cx).unwrap(); - -// (app_state.initialize_workspace)( -// workspace.downgrade(), -// serialized_workspace.is_some(), -// app_state.clone(), -// cx.clone(), -// ) -// .await -// .log_err(); - -// window.update(&mut cx, |cx| cx.activate_window()); - -// let workspace = workspace.downgrade(); -// notify_if_database_failed(&workspace, &mut cx); -// let opened_items = open_items( -// serialized_workspace, -// &workspace, -// project_paths, -// app_state, -// cx, -// ) -// .await -// .unwrap_or_default(); - -// (workspace, opened_items) -// }) -// } - -// pub fn weak_handle(&self) -> WeakViewHandle { -// self.weak_self.clone() -// } - -// pub fn left_dock(&self) -> &ViewHandle { -// &self.left_dock -// } - -// pub fn bottom_dock(&self) -> &ViewHandle { -// &self.bottom_dock -// } - -// pub fn right_dock(&self) -> &ViewHandle { -// &self.right_dock -// } - -// pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) -// where -// T::Event: std::fmt::Debug, -// { -// self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) -// } - -// pub fn add_panel_with_extra_event_handler( -// &mut self, -// panel: ViewHandle, -// cx: &mut ViewContext, -// handler: F, -// ) where -// T::Event: std::fmt::Debug, -// F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, -// { -// let dock = match panel.position(cx) { -// DockPosition::Left => &self.left_dock, -// DockPosition::Bottom => &self.bottom_dock, -// DockPosition::Right => &self.right_dock, -// }; - -// self.subscriptions.push(cx.subscribe(&panel, { -// let mut dock = dock.clone(); -// let mut prev_position = panel.position(cx); -// move |this, panel, event, cx| { -// if T::should_change_position_on_event(event) { -// let new_position = panel.read(cx).position(cx); -// let mut was_visible = false; -// dock.update(cx, |dock, cx| { -// prev_position = new_position; - -// was_visible = dock.is_open() -// && dock -// .visible_panel() -// .map_or(false, |active_panel| active_panel.id() == panel.id()); -// dock.remove_panel(&panel, cx); -// }); - -// if panel.is_zoomed(cx) { -// this.zoomed_position = Some(new_position); -// } - -// dock = match panel.read(cx).position(cx) { -// DockPosition::Left => &this.left_dock, -// DockPosition::Bottom => &this.bottom_dock, -// DockPosition::Right => &this.right_dock, -// } -// .clone(); -// dock.update(cx, |dock, cx| { -// dock.add_panel(panel.clone(), cx); -// if was_visible { -// dock.set_open(true, cx); -// dock.activate_panel(dock.panels_len() - 1, cx); -// } -// }); -// } else if T::should_zoom_in_on_event(event) { -// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); -// if !panel.has_focus(cx) { -// cx.focus(&panel); -// } -// this.zoomed = Some(panel.downgrade().into_any()); -// this.zoomed_position = Some(panel.read(cx).position(cx)); -// } else if T::should_zoom_out_on_event(event) { -// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); -// if this.zoomed_position == Some(prev_position) { -// this.zoomed = None; -// this.zoomed_position = None; -// } -// cx.notify(); -// } else if T::is_focus_event(event) { -// let position = panel.read(cx).position(cx); -// this.dismiss_zoomed_items_to_reveal(Some(position), cx); -// if panel.is_zoomed(cx) { -// this.zoomed = Some(panel.downgrade().into_any()); -// this.zoomed_position = Some(position); -// } else { -// this.zoomed = None; -// this.zoomed_position = None; -// } -// this.update_active_view_for_followers(cx); -// cx.notify(); -// } else { -// handler(this, &panel, event, cx) -// } -// } -// })); - -// dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); -// } - -// pub fn status_bar(&self) -> &ViewHandle { -// &self.status_bar -// } - -// pub fn app_state(&self) -> &Arc { -// &self.app_state -// } - -// pub fn user_store(&self) -> &ModelHandle { -// &self.app_state.user_store -// } - -// pub fn project(&self) -> &ModelHandle { -// &self.project -// } - -// pub fn recent_navigation_history( -// &self, -// limit: Option, -// cx: &AppContext, -// ) -> Vec<(ProjectPath, Option)> { -// let mut abs_paths_opened: HashMap> = HashMap::default(); -// let mut history: HashMap, usize)> = HashMap::default(); -// for pane in &self.panes { -// let pane = pane.read(cx); -// pane.nav_history() -// .for_each_entry(cx, |entry, (project_path, fs_path)| { -// if let Some(fs_path) = &fs_path { -// abs_paths_opened -// .entry(fs_path.clone()) -// .or_default() -// .insert(project_path.clone()); -// } -// let timestamp = entry.timestamp; -// match history.entry(project_path) { -// hash_map::Entry::Occupied(mut entry) => { -// let (_, old_timestamp) = entry.get(); -// if ×tamp > old_timestamp { -// entry.insert((fs_path, timestamp)); -// } -// } -// hash_map::Entry::Vacant(entry) => { -// entry.insert((fs_path, timestamp)); -// } -// } -// }); -// } - -// history -// .into_iter() -// .sorted_by_key(|(_, (_, timestamp))| *timestamp) -// .map(|(project_path, (fs_path, _))| (project_path, fs_path)) -// .rev() -// .filter(|(history_path, abs_path)| { -// let latest_project_path_opened = abs_path -// .as_ref() -// .and_then(|abs_path| abs_paths_opened.get(abs_path)) -// .and_then(|project_paths| { -// project_paths -// .iter() -// .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) -// }); - -// match latest_project_path_opened { -// Some(latest_project_path_opened) => latest_project_path_opened == history_path, -// None => true, -// } -// }) -// .take(limit.unwrap_or(usize::MAX)) -// .collect() -// } - -// fn navigate_history( -// &mut self, -// pane: WeakViewHandle, -// mode: NavigationMode, -// cx: &mut ViewContext, -// ) -> Task> { -// let to_load = if let Some(pane) = pane.upgrade(cx) { -// cx.focus(&pane); - -// pane.update(cx, |pane, cx| { -// loop { -// // Retrieve the weak item handle from the history. -// let entry = pane.nav_history_mut().pop(mode, cx)?; - -// // If the item is still present in this pane, then activate it. -// if let Some(index) = entry -// .item -// .upgrade(cx) -// .and_then(|v| pane.index_for_item(v.as_ref())) -// { -// let prev_active_item_index = pane.active_item_index(); -// pane.nav_history_mut().set_mode(mode); -// pane.activate_item(index, true, true, cx); -// pane.nav_history_mut().set_mode(NavigationMode::Normal); - -// let mut navigated = prev_active_item_index != pane.active_item_index(); -// if let Some(data) = entry.data { -// navigated |= pane.active_item()?.navigate(data, cx); -// } - -// if navigated { -// break None; -// } -// } -// // If the item is no longer present in this pane, then retrieve its -// // project path in order to reopen it. -// else { -// break pane -// .nav_history() -// .path_for_item(entry.item.id()) -// .map(|(project_path, _)| (project_path, entry)); -// } -// } -// }) -// } else { -// None -// }; - -// if let Some((project_path, entry)) = to_load { -// // If the item was no longer present, then load it again from its previous path. -// let task = self.load_path(project_path, cx); -// cx.spawn(|workspace, mut cx| async move { -// let task = task.await; -// let mut navigated = false; -// if let Some((project_entry_id, build_item)) = task.log_err() { -// let prev_active_item_id = pane.update(&mut cx, |pane, _| { -// pane.nav_history_mut().set_mode(mode); -// pane.active_item().map(|p| p.id()) -// })?; - -// pane.update(&mut cx, |pane, cx| { -// let item = pane.open_item(project_entry_id, true, cx, build_item); -// navigated |= Some(item.id()) != prev_active_item_id; -// pane.nav_history_mut().set_mode(NavigationMode::Normal); -// if let Some(data) = entry.data { -// navigated |= item.navigate(data, cx); -// } -// })?; -// } - -// if !navigated { -// workspace -// .update(&mut cx, |workspace, cx| { -// Self::navigate_history(workspace, pane, mode, cx) -// })? -// .await?; -// } - -// Ok(()) -// }) -// } else { -// Task::ready(Ok(())) -// } -// } - -// pub fn go_back( -// &mut self, -// pane: WeakViewHandle, -// cx: &mut ViewContext, -// ) -> Task> { -// self.navigate_history(pane, NavigationMode::GoingBack, cx) -// } - -// pub fn go_forward( -// &mut self, -// pane: WeakViewHandle, -// cx: &mut ViewContext, -// ) -> Task> { -// self.navigate_history(pane, NavigationMode::GoingForward, cx) -// } - -// pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { -// self.navigate_history( -// self.active_pane().downgrade(), -// NavigationMode::ReopeningClosedItem, -// cx, -// ) -// } - -// pub fn client(&self) -> &Client { -// &self.app_state.client -// } - -// pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext) { -// self.titlebar_item = Some(item); -// cx.notify(); -// } - -// pub fn titlebar_item(&self) -> Option { -// self.titlebar_item.clone() -// } - -// /// Call the given callback with a workspace whose project is local. -// /// -// /// If the given workspace has a local project, then it will be passed -// /// to the callback. Otherwise, a new empty window will be created. -// pub fn with_local_workspace( -// &mut self, -// cx: &mut ViewContext, -// callback: F, -// ) -> Task> -// where -// T: 'static, -// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, -// { -// if self.project.read(cx).is_local() { -// Task::Ready(Some(Ok(callback(self, cx)))) -// } else { -// let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); -// cx.spawn(|_vh, mut cx| async move { -// let (workspace, _) = task.await; -// workspace.update(&mut cx, callback) -// }) -// } -// } - -// pub fn worktrees<'a>( -// &self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.project.read(cx).worktrees(cx) -// } - -// pub fn visible_worktrees<'a>( -// &self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.project.read(cx).visible_worktrees(cx) -// } - -// pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { -// let futures = self -// .worktrees(cx) -// .filter_map(|worktree| worktree.read(cx).as_local()) -// .map(|worktree| worktree.scan_complete()) -// .collect::>(); -// async move { -// for future in futures { -// future.await; -// } -// } -// } - -// pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { -// cx.spawn(|mut cx| async move { -// let window = cx -// .windows() -// .into_iter() -// .find(|window| window.is_active(&cx).unwrap_or(false)); -// if let Some(window) = window { -// //This can only get called when the window's project connection has been lost -// //so we don't need to prompt the user for anything and instead just close the window -// window.remove(&mut cx); -// } -// }) -// .detach(); -// } - -// pub fn close( -// &mut self, -// _: &CloseWindow, -// cx: &mut ViewContext, -// ) -> Option>> { -// let window = cx.window(); -// let prepare = self.prepare_to_close(false, cx); -// Some(cx.spawn(|_, mut cx| async move { -// if prepare.await? { -// window.remove(&mut cx); -// } -// Ok(()) -// })) -// } - -// pub fn prepare_to_close( -// &mut self, -// quitting: bool, -// cx: &mut ViewContext, -// ) -> Task> { -// let active_call = self.active_call().cloned(); -// let window = cx.window(); - -// cx.spawn(|this, mut cx| async move { -// let workspace_count = cx -// .windows() -// .into_iter() -// .filter(|window| window.root_is::()) -// .count(); - -// if let Some(active_call) = active_call { -// if !quitting -// && workspace_count == 1 -// && active_call.read_with(&cx, |call, _| call.room().is_some()) -// { -// let answer = window.prompt( -// PromptLevel::Warning, -// "Do you want to leave the current call?", -// &["Close window and hang up", "Cancel"], -// &mut cx, -// ); - -// if let Some(mut answer) = answer { -// if answer.next().await == Some(1) { -// return anyhow::Ok(false); -// } else { -// active_call -// .update(&mut cx, |call, cx| call.hang_up(cx)) -// .await -// .log_err(); -// } -// } -// } -// } - -// Ok(this -// .update(&mut cx, |this, cx| { -// this.save_all_internal(SaveIntent::Close, cx) -// })? -// .await?) -// }) -// } - -// fn save_all( -// &mut self, -// action: &SaveAll, -// cx: &mut ViewContext, -// ) -> Option>> { -// let save_all = -// self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx); -// Some(cx.foreground().spawn(async move { -// save_all.await?; -// Ok(()) -// })) -// } - -// fn save_all_internal( -// &mut self, -// mut save_intent: SaveIntent, -// cx: &mut ViewContext, -// ) -> Task> { -// if self.project.read(cx).is_read_only() { -// return Task::ready(Ok(true)); -// } -// let dirty_items = self -// .panes -// .iter() -// .flat_map(|pane| { -// pane.read(cx).items().filter_map(|item| { -// if item.is_dirty(cx) { -// Some((pane.downgrade(), item.boxed_clone())) -// } else { -// None -// } -// }) -// }) -// .collect::>(); - -// let project = self.project.clone(); -// cx.spawn(|workspace, mut cx| async move { -// // Override save mode and display "Save all files" prompt -// if save_intent == SaveIntent::Close && dirty_items.len() > 1 { -// let mut answer = workspace.update(&mut cx, |_, cx| { -// let prompt = Pane::file_names_for_prompt( -// &mut dirty_items.iter().map(|(_, handle)| handle), -// dirty_items.len(), -// cx, -// ); -// cx.prompt( -// PromptLevel::Warning, -// &prompt, -// &["Save all", "Discard all", "Cancel"], -// ) -// })?; -// match answer.next().await { -// Some(0) => save_intent = SaveIntent::SaveAll, -// Some(1) => save_intent = SaveIntent::Skip, -// _ => {} -// } -// } -// for (pane, item) in dirty_items { -// let (singleton, project_entry_ids) = -// cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); -// if singleton || !project_entry_ids.is_empty() { -// if let Some(ix) = -// pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))? -// { -// if !Pane::save_item( -// project.clone(), -// &pane, -// ix, -// &*item, -// save_intent, -// &mut cx, -// ) -// .await? -// { -// return Ok(false); -// } -// } -// } -// } -// Ok(true) -// }) -// } - -// pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { -// let mut paths = cx.prompt_for_paths(PathPromptOptions { -// files: true, -// directories: true, -// multiple: true, -// }); - -// Some(cx.spawn(|this, mut cx| async move { -// if let Some(paths) = paths.recv().await.flatten() { -// if let Some(task) = this -// .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) -// .log_err() -// { -// task.await? -// } -// } -// Ok(()) -// })) -// } - -// pub fn open_workspace_for_paths( -// &mut self, -// paths: Vec, -// cx: &mut ViewContext, -// ) -> Task> { -// let window = cx.window().downcast::(); -// let is_remote = self.project.read(cx).is_remote(); -// let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); -// let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); -// let close_task = if is_remote || has_worktree || has_dirty_items { -// None -// } else { -// Some(self.prepare_to_close(false, cx)) -// }; -// let app_state = self.app_state.clone(); - -// cx.spawn(|_, mut cx| async move { -// let window_to_replace = if let Some(close_task) = close_task { -// if !close_task.await? { -// return Ok(()); -// } -// window -// } else { -// None -// }; -// cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx)) -// .await?; -// Ok(()) -// }) -// } - -// #[allow(clippy::type_complexity)] -// pub fn open_paths( -// &mut self, -// mut abs_paths: Vec, -// visible: bool, -// cx: &mut ViewContext, -// ) -> Task, anyhow::Error>>>> { -// log::info!("open paths {:?}", abs_paths); - -// let fs = self.app_state.fs.clone(); - -// // Sort the paths to ensure we add worktrees for parents before their children. -// abs_paths.sort_unstable(); -// cx.spawn(|this, mut cx| async move { -// let mut tasks = Vec::with_capacity(abs_paths.len()); -// for abs_path in &abs_paths { -// let project_path = match this -// .update(&mut cx, |this, cx| { -// Workspace::project_path_for_path( -// this.project.clone(), -// abs_path, -// visible, -// cx, -// ) -// }) -// .log_err() -// { -// Some(project_path) => project_path.await.log_err(), -// None => None, -// }; - -// let this = this.clone(); -// let task = cx.spawn(|mut cx| { -// let fs = fs.clone(); -// let abs_path = abs_path.clone(); -// async move { -// let (worktree, project_path) = project_path?; -// if fs.is_file(&abs_path).await { -// Some( -// this.update(&mut cx, |this, cx| { -// this.open_path(project_path, None, true, cx) -// }) -// .log_err()? -// .await, -// ) -// } else { -// this.update(&mut cx, |workspace, cx| { -// let worktree = worktree.read(cx); -// let worktree_abs_path = worktree.abs_path(); -// let entry_id = if abs_path == worktree_abs_path.as_ref() { -// worktree.root_entry() -// } else { -// abs_path -// .strip_prefix(worktree_abs_path.as_ref()) -// .ok() -// .and_then(|relative_path| { -// worktree.entry_for_path(relative_path) -// }) -// } -// .map(|entry| entry.id); -// if let Some(entry_id) = entry_id { -// workspace.project().update(cx, |_, cx| { -// cx.emit(project::Event::ActiveEntryChanged(Some(entry_id))); -// }) -// } -// }) -// .log_err()?; -// None -// } -// } -// }); -// tasks.push(task); -// } - -// futures::future::join_all(tasks).await -// }) -// } - -// fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { -// let mut paths = cx.prompt_for_paths(PathPromptOptions { -// files: false, -// directories: true, -// multiple: true, -// }); -// cx.spawn(|this, mut cx| async move { -// if let Some(paths) = paths.recv().await.flatten() { -// let results = this -// .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? -// .await; -// for result in results.into_iter().flatten() { -// result.log_err(); -// } -// } -// anyhow::Ok(()) -// }) -// .detach_and_log_err(cx); -// } - -// fn project_path_for_path( -// project: ModelHandle, -// abs_path: &Path, -// visible: bool, -// cx: &mut AppContext, -// ) -> Task, ProjectPath)>> { -// let entry = project.update(cx, |project, cx| { -// project.find_or_create_local_worktree(abs_path, visible, cx) -// }); -// cx.spawn(|cx| async move { -// let (worktree, path) = entry.await?; -// let worktree_id = worktree.read_with(&cx, |t, _| t.id()); -// Ok(( -// worktree, -// ProjectPath { -// worktree_id, -// path: path.into(), -// }, -// )) -// }) -// } - -// /// Returns the modal that was toggled closed if it was open. -// pub fn toggle_modal( -// &mut self, -// cx: &mut ViewContext, -// add_view: F, -// ) -> Option> -// where -// V: 'static + Modal, -// F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, -// { -// cx.notify(); -// // Whatever modal was visible is getting clobbered. If its the same type as V, then return -// // it. Otherwise, create a new modal and set it as active. -// if let Some(already_open_modal) = self -// .dismiss_modal(cx) -// .and_then(|modal| modal.downcast::()) -// { -// cx.focus_self(); -// Some(already_open_modal) -// } else { -// let modal = add_view(self, cx); -// cx.subscribe(&modal, |this, _, event, cx| { -// if V::dismiss_on_event(event) { -// this.dismiss_modal(cx); -// } -// }) -// .detach(); -// let previously_focused_view_id = cx.focused_view_id(); -// cx.focus(&modal); -// self.modal = Some(ActiveModal { -// view: Box::new(modal), -// previously_focused_view_id, -// }); -// None -// } -// } - -// pub fn modal(&self) -> Option> { -// self.modal -// .as_ref() -// .and_then(|modal| modal.view.as_any().clone().downcast::()) -// } - -// pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { -// if let Some(modal) = self.modal.take() { -// if let Some(previously_focused_view_id) = modal.previously_focused_view_id { -// if modal.view.has_focus(cx) { -// cx.window_context().focus(Some(previously_focused_view_id)); -// } -// } -// cx.notify(); -// Some(modal.view.as_any().clone()) -// } else { -// None -// } -// } - -// pub fn items<'a>( -// &'a self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.panes.iter().flat_map(|pane| pane.read(cx).items()) -// } - -// pub fn item_of_type(&self, cx: &AppContext) -> Option> { -// self.items_of_type(cx).max_by_key(|item| item.id()) -// } - -// pub fn items_of_type<'a, T: Item>( -// &'a self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.panes -// .iter() -// .flat_map(|pane| pane.read(cx).items_of_type()) -// } - -// pub fn active_item(&self, cx: &AppContext) -> Option> { -// self.active_pane().read(cx).active_item() -// } - -// fn active_project_path(&self, cx: &ViewContext) -> Option { -// self.active_item(cx).and_then(|item| item.project_path(cx)) -// } - -// pub fn save_active_item( -// &mut self, -// save_intent: SaveIntent, -// cx: &mut ViewContext, -// ) -> Task> { -// let project = self.project.clone(); -// let pane = self.active_pane(); -// let item_ix = pane.read(cx).active_item_index(); -// let item = pane.read(cx).active_item(); -// let pane = pane.downgrade(); - -// cx.spawn(|_, mut cx| async move { -// if let Some(item) = item { -// Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx) -// .await -// .map(|_| ()) -// } else { -// Ok(()) -// } -// }) -// } - -// pub fn close_inactive_items_and_panes( -// &mut self, -// _: &CloseInactiveTabsAndPanes, -// cx: &mut ViewContext, -// ) -> Option>> { -// self.close_all_internal(true, SaveIntent::Close, cx) -// } - -// pub fn close_all_items_and_panes( -// &mut self, -// action: &CloseAllItemsAndPanes, -// cx: &mut ViewContext, -// ) -> Option>> { -// self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) -// } - -// fn close_all_internal( -// &mut self, -// retain_active_pane: bool, -// save_intent: SaveIntent, -// cx: &mut ViewContext, -// ) -> Option>> { -// let current_pane = self.active_pane(); - -// let mut tasks = Vec::new(); - -// if retain_active_pane { -// if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| { -// pane.close_inactive_items(&CloseInactiveItems, cx) -// }) { -// tasks.push(current_pane_close); -// }; -// } - -// for pane in self.panes() { -// if retain_active_pane && pane.id() == current_pane.id() { -// continue; -// } - -// if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| { -// pane.close_all_items( -// &CloseAllItems { -// save_intent: Some(save_intent), -// }, -// cx, -// ) -// }) { -// tasks.push(close_pane_items) -// } -// } - -// if tasks.is_empty() { -// None -// } else { -// Some(cx.spawn(|_, _| async move { -// for task in tasks { -// task.await? -// } -// Ok(()) -// })) -// } -// } - -// pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { -// let dock = match dock_side { -// DockPosition::Left => &self.left_dock, -// DockPosition::Bottom => &self.bottom_dock, -// DockPosition::Right => &self.right_dock, -// }; -// let mut focus_center = false; -// let mut reveal_dock = false; -// dock.update(cx, |dock, cx| { -// let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); -// let was_visible = dock.is_open() && !other_is_zoomed; -// dock.set_open(!was_visible, cx); - -// if let Some(active_panel) = dock.active_panel() { -// if was_visible { -// if active_panel.has_focus(cx) { -// focus_center = true; -// } -// } else { -// cx.focus(active_panel.as_any()); -// reveal_dock = true; -// } -// } -// }); - -// if reveal_dock { -// self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); -// } - -// if focus_center { -// cx.focus_self(); -// } - -// cx.notify(); -// self.serialize_workspace(cx); -// } - -// pub fn close_all_docks(&mut self, cx: &mut ViewContext) { -// let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; - -// for dock in docks { -// dock.update(cx, |dock, cx| { -// dock.set_open(false, cx); -// }); -// } - -// cx.focus_self(); -// cx.notify(); -// self.serialize_workspace(cx); -// } - -// /// Transfer focus to the panel of the given type. -// pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { -// self.focus_or_unfocus_panel::(cx, |_, _| true)? -// .as_any() -// .clone() -// .downcast() -// } - -// /// Focus the panel of the given type if it isn't already focused. If it is -// /// already focused, then transfer focus back to the workspace center. -// pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { -// self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); -// } - -// /// Focus or unfocus the given panel type, depending on the given callback. -// fn focus_or_unfocus_panel( -// &mut self, -// cx: &mut ViewContext, -// should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, -// ) -> Option> { -// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { -// if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { -// let mut focus_center = false; -// let mut reveal_dock = false; -// let panel = dock.update(cx, |dock, cx| { -// dock.activate_panel(panel_index, cx); - -// let panel = dock.active_panel().cloned(); -// if let Some(panel) = panel.as_ref() { -// if should_focus(&**panel, cx) { -// dock.set_open(true, cx); -// cx.focus(panel.as_any()); -// reveal_dock = true; -// } else { -// // if panel.is_zoomed(cx) { -// // dock.set_open(false, cx); -// // } -// focus_center = true; -// } -// } -// panel -// }); - -// if focus_center { -// cx.focus_self(); -// } - -// self.serialize_workspace(cx); -// cx.notify(); -// return panel; -// } -// } -// None -// } - -// pub fn panel(&self, cx: &WindowContext) -> Option> { -// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { -// let dock = dock.read(cx); -// if let Some(panel) = dock.panel::() { -// return Some(panel); -// } -// } -// None -// } - -// fn zoom_out(&mut self, cx: &mut ViewContext) { -// for pane in &self.panes { -// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); -// } - -// self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); -// self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); -// self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); -// self.zoomed = None; -// self.zoomed_position = None; - -// cx.notify(); -// } - -// #[cfg(any(test, feature = "test-support"))] -// pub fn zoomed_view(&self, cx: &AppContext) -> Option { -// self.zoomed.and_then(|view| view.upgrade(cx)) -// } - -// fn dismiss_zoomed_items_to_reveal( -// &mut self, -// dock_to_reveal: Option, -// cx: &mut ViewContext, -// ) { -// // If a center pane is zoomed, unzoom it. -// for pane in &self.panes { -// if pane != &self.active_pane || dock_to_reveal.is_some() { -// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); -// } -// } - -// // If another dock is zoomed, hide it. -// let mut focus_center = false; -// for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { -// dock.update(cx, |dock, cx| { -// if Some(dock.position()) != dock_to_reveal { -// if let Some(panel) = dock.active_panel() { -// if panel.is_zoomed(cx) { -// focus_center |= panel.has_focus(cx); -// dock.set_open(false, cx); -// } -// } -// } -// }); -// } - -// if focus_center { -// cx.focus_self(); -// } - -// if self.zoomed_position != dock_to_reveal { -// self.zoomed = None; -// self.zoomed_position = None; -// } - -// cx.notify(); -// } - -// fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { -// let pane = cx.add_view(|cx| { -// Pane::new( -// self.weak_handle(), -// self.project.clone(), -// self.pane_history_timestamp.clone(), -// cx, -// ) -// }); -// cx.subscribe(&pane, Self::handle_pane_event).detach(); -// self.panes.push(pane.clone()); -// cx.focus(&pane); -// cx.emit(Event::PaneAdded(pane.clone())); -// pane -// } - -// pub fn add_item_to_center( -// &mut self, -// item: Box, -// cx: &mut ViewContext, -// ) -> bool { -// if let Some(center_pane) = self.last_active_center_pane.clone() { -// if let Some(center_pane) = center_pane.upgrade(cx) { -// center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); -// true -// } else { -// false -// } -// } else { -// false -// } -// } - -// pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { -// self.active_pane -// .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); -// } - -// pub fn split_item( -// &mut self, -// split_direction: SplitDirection, -// item: Box, -// cx: &mut ViewContext, -// ) { -// let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); -// new_pane.update(cx, move |new_pane, cx| { -// new_pane.add_item(item, true, true, None, cx) -// }) -// } - -// pub fn open_abs_path( -// &mut self, -// abs_path: PathBuf, -// visible: bool, -// cx: &mut ViewContext, -// ) -> Task>> { -// cx.spawn(|workspace, mut cx| async move { -// let open_paths_task_result = workspace -// .update(&mut cx, |workspace, cx| { -// workspace.open_paths(vec![abs_path.clone()], visible, cx) -// }) -// .with_context(|| format!("open abs path {abs_path:?} task spawn"))? -// .await; -// anyhow::ensure!( -// open_paths_task_result.len() == 1, -// "open abs path {abs_path:?} task returned incorrect number of results" -// ); -// match open_paths_task_result -// .into_iter() -// .next() -// .expect("ensured single task result") -// { -// Some(open_result) => { -// open_result.with_context(|| format!("open abs path {abs_path:?} task join")) -// } -// None => anyhow::bail!("open abs path {abs_path:?} task returned None"), -// } -// }) -// } - -// pub fn split_abs_path( -// &mut self, -// abs_path: PathBuf, -// visible: bool, -// cx: &mut ViewContext, -// ) -> Task>> { -// let project_path_task = -// Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); -// cx.spawn(|this, mut cx| async move { -// let (_, path) = project_path_task.await?; -// this.update(&mut cx, |this, cx| this.split_path(path, cx))? -// .await -// }) -// } - -// pub fn open_path( -// &mut self, -// path: impl Into, -// pane: Option>, -// focus_item: bool, -// cx: &mut ViewContext, -// ) -> Task, anyhow::Error>> { -// let pane = pane.unwrap_or_else(|| { -// self.last_active_center_pane.clone().unwrap_or_else(|| { -// self.panes -// .first() -// .expect("There must be an active pane") -// .downgrade() -// }) -// }); - -// let task = self.load_path(path.into(), cx); -// cx.spawn(|_, mut cx| async move { -// let (project_entry_id, build_item) = task.await?; -// pane.update(&mut cx, |pane, cx| { -// pane.open_item(project_entry_id, focus_item, cx, build_item) -// }) -// }) -// } - -// pub fn split_path( -// &mut self, -// path: impl Into, -// cx: &mut ViewContext, -// ) -> Task, anyhow::Error>> { -// let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { -// self.panes -// .first() -// .expect("There must be an active pane") -// .downgrade() -// }); - -// if let Member::Pane(center_pane) = &self.center.root { -// if center_pane.read(cx).items_len() == 0 { -// return self.open_path(path, Some(pane), true, cx); -// } -// } - -// let task = self.load_path(path.into(), cx); -// cx.spawn(|this, mut cx| async move { -// let (project_entry_id, build_item) = task.await?; -// this.update(&mut cx, move |this, cx| -> Option<_> { -// let pane = pane.upgrade(cx)?; -// let new_pane = this.split_pane(pane, SplitDirection::Right, cx); -// new_pane.update(cx, |new_pane, cx| { -// Some(new_pane.open_item(project_entry_id, true, cx, build_item)) -// }) -// }) -// .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? -// }) -// } - -// pub(crate) fn load_path( -// &mut self, -// path: ProjectPath, -// cx: &mut ViewContext, -// ) -> Task< -// Result<( -// ProjectEntryId, -// impl 'static + FnOnce(&mut ViewContext) -> Box, -// )>, -// > { -// let project = self.project().clone(); -// let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); -// cx.spawn(|_, mut cx| async move { -// let (project_entry_id, project_item) = project_item.await?; -// let build_item = cx.update(|cx| { -// cx.default_global::() -// .get(&project_item.model_type()) -// .ok_or_else(|| anyhow!("no item builder for project item")) -// .cloned() -// })?; -// let build_item = -// move |cx: &mut ViewContext| build_item(project, project_item, cx); -// Ok((project_entry_id, build_item)) -// }) -// } - -// pub fn open_project_item( -// &mut self, -// project_item: ModelHandle, -// cx: &mut ViewContext, -// ) -> ViewHandle -// where -// T: ProjectItem, -// { -// use project::Item as _; - -// let entry_id = project_item.read(cx).entry_id(cx); -// if let Some(item) = entry_id -// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) -// .and_then(|item| item.downcast()) -// { -// self.activate_item(&item, cx); -// return item; -// } - -// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); -// self.add_item(Box::new(item.clone()), cx); -// item -// } - -// pub fn split_project_item( -// &mut self, -// project_item: ModelHandle, -// cx: &mut ViewContext, -// ) -> ViewHandle -// where -// T: ProjectItem, -// { -// use project::Item as _; - -// let entry_id = project_item.read(cx).entry_id(cx); -// if let Some(item) = entry_id -// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) -// .and_then(|item| item.downcast()) -// { -// self.activate_item(&item, cx); -// return item; -// } - -// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); -// self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); -// item -// } - -// pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { -// if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { -// self.active_pane.update(cx, |pane, cx| { -// pane.add_item(Box::new(shared_screen), false, true, None, cx) -// }); -// } -// } - -// pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { -// let result = self.panes.iter().find_map(|pane| { -// pane.read(cx) -// .index_for_item(item) -// .map(|ix| (pane.clone(), ix)) -// }); -// if let Some((pane, ix)) = result { -// pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); -// true -// } else { -// false -// } -// } - -// fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { -// let panes = self.center.panes(); -// if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { -// cx.focus(&pane); -// } else { -// self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); -// } -// } - -// pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { -// let panes = self.center.panes(); -// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { -// let next_ix = (ix + 1) % panes.len(); -// let next_pane = panes[next_ix].clone(); -// cx.focus(&next_pane); -// } -// } - -// pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { -// let panes = self.center.panes(); -// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { -// let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); -// let prev_pane = panes[prev_ix].clone(); -// cx.focus(&prev_pane); -// } -// } - -// pub fn activate_pane_in_direction( -// &mut self, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) { -// if let Some(pane) = self.find_pane_in_direction(direction, cx) { -// cx.focus(pane); -// } -// } - -// pub fn swap_pane_in_direction( -// &mut self, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) { -// if let Some(to) = self -// .find_pane_in_direction(direction, cx) -// .map(|pane| pane.clone()) -// { -// self.center.swap(&self.active_pane.clone(), &to); -// cx.notify(); -// } -// } - -// fn find_pane_in_direction( -// &mut self, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) -> Option<&ViewHandle> { -// let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { -// return None; -// }; -// let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); -// let center = match cursor { -// Some(cursor) if bounding_box.contains_point(cursor) => cursor, -// _ => bounding_box.center(), -// }; - -// let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; - -// let target = match direction { -// SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), -// SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), -// SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), -// SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), -// }; -// self.center.pane_at_pixel_position(target) -// } - -// fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { -// if self.active_pane != pane { -// self.active_pane = pane.clone(); -// self.status_bar.update(cx, |status_bar, cx| { -// status_bar.set_active_pane(&self.active_pane, cx); -// }); -// self.active_item_path_changed(cx); -// self.last_active_center_pane = Some(pane.downgrade()); -// } - -// self.dismiss_zoomed_items_to_reveal(None, cx); -// if pane.read(cx).is_zoomed() { -// self.zoomed = Some(pane.downgrade().into_any()); -// } else { -// self.zoomed = None; -// } -// self.zoomed_position = None; -// self.update_active_view_for_followers(cx); - -// cx.notify(); -// } - -// fn handle_pane_event( -// &mut self, -// pane: ViewHandle, -// event: &pane::Event, -// cx: &mut ViewContext, -// ) { -// match event { -// pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), -// pane::Event::Split(direction) => { -// self.split_and_clone(pane, *direction, cx); -// } -// pane::Event::Remove => self.remove_pane(pane, cx), -// pane::Event::ActivateItem { local } => { -// if *local { -// self.unfollow(&pane, cx); -// } -// if &pane == self.active_pane() { -// self.active_item_path_changed(cx); -// } -// } -// pane::Event::ChangeItemTitle => { -// if pane == self.active_pane { -// self.active_item_path_changed(cx); -// } -// self.update_window_edited(cx); -// } -// pane::Event::RemoveItem { item_id } => { -// self.update_window_edited(cx); -// if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { -// if entry.get().id() == pane.id() { -// entry.remove(); -// } -// } -// } -// pane::Event::Focus => { -// self.handle_pane_focused(pane.clone(), cx); -// } -// pane::Event::ZoomIn => { -// if pane == self.active_pane { -// pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); -// if pane.read(cx).has_focus() { -// self.zoomed = Some(pane.downgrade().into_any()); -// self.zoomed_position = None; -// } -// cx.notify(); -// } -// } -// pane::Event::ZoomOut => { -// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); -// if self.zoomed_position.is_none() { -// self.zoomed = None; -// } -// cx.notify(); -// } -// } - -// self.serialize_workspace(cx); -// } - -// pub fn split_pane( -// &mut self, -// pane_to_split: ViewHandle, -// split_direction: SplitDirection, -// cx: &mut ViewContext, -// ) -> ViewHandle { -// let new_pane = self.add_pane(cx); -// self.center -// .split(&pane_to_split, &new_pane, split_direction) -// .unwrap(); -// cx.notify(); -// new_pane -// } - -// pub fn split_and_clone( -// &mut self, -// pane: ViewHandle, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) -> Option> { -// let item = pane.read(cx).active_item()?; -// let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { -// let new_pane = self.add_pane(cx); -// new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); -// self.center.split(&pane, &new_pane, direction).unwrap(); -// Some(new_pane) -// } else { -// None -// }; -// cx.notify(); -// maybe_pane_handle -// } - -// pub fn split_pane_with_item( -// &mut self, -// pane_to_split: WeakViewHandle, -// split_direction: SplitDirection, -// from: WeakViewHandle, -// item_id_to_move: usize, -// cx: &mut ViewContext, -// ) { -// let Some(pane_to_split) = pane_to_split.upgrade(cx) else { -// return; -// }; -// let Some(from) = from.upgrade(cx) else { -// return; -// }; - -// let new_pane = self.add_pane(cx); -// self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); -// self.center -// .split(&pane_to_split, &new_pane, split_direction) -// .unwrap(); -// cx.notify(); -// } - -// pub fn split_pane_with_project_entry( -// &mut self, -// pane_to_split: WeakViewHandle, -// split_direction: SplitDirection, -// project_entry: ProjectEntryId, -// cx: &mut ViewContext, -// ) -> Option>> { -// let pane_to_split = pane_to_split.upgrade(cx)?; -// let new_pane = self.add_pane(cx); -// self.center -// .split(&pane_to_split, &new_pane, split_direction) -// .unwrap(); - -// let path = self.project.read(cx).path_for_entry(project_entry, cx)?; -// let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); -// Some(cx.foreground().spawn(async move { -// task.await?; -// Ok(()) -// })) -// } - -// pub fn move_item( -// &mut self, -// source: ViewHandle, -// destination: ViewHandle, -// item_id_to_move: usize, -// destination_index: usize, -// cx: &mut ViewContext, -// ) { -// let item_to_move = source -// .read(cx) -// .items() -// .enumerate() -// .find(|(_, item_handle)| item_handle.id() == item_id_to_move); - -// if item_to_move.is_none() { -// log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); -// return; -// } -// let (item_ix, item_handle) = item_to_move.unwrap(); -// let item_handle = item_handle.clone(); - -// if source != destination { -// // Close item from previous pane -// source.update(cx, |source, cx| { -// source.remove_item(item_ix, false, cx); -// }); -// } - -// // This automatically removes duplicate items in the pane -// destination.update(cx, |destination, cx| { -// destination.add_item(item_handle, true, true, Some(destination_index), cx); -// cx.focus_self(); -// }); -// } - -// fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { -// if self.center.remove(&pane).unwrap() { -// self.force_remove_pane(&pane, cx); -// self.unfollow(&pane, cx); -// self.last_leaders_by_pane.remove(&pane.downgrade()); -// for removed_item in pane.read(cx).items() { -// self.panes_by_item.remove(&removed_item.id()); -// } - -// cx.notify(); -// } else { -// self.active_item_path_changed(cx); -// } -// } - -// pub fn panes(&self) -> &[ViewHandle] { -// &self.panes -// } - -// pub fn active_pane(&self) -> &ViewHandle { -// &self.active_pane -// } - -// fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { -// self.follower_states.retain(|_, state| { -// if state.leader_id == peer_id { -// for item in state.items_by_leader_view_id.values() { -// item.set_leader_peer_id(None, cx); -// } -// false -// } else { -// true -// } -// }); -// cx.notify(); -// } - -// fn start_following( -// &mut self, -// leader_id: PeerId, -// cx: &mut ViewContext, -// ) -> Option>> { -// let pane = self.active_pane().clone(); - -// self.last_leaders_by_pane -// .insert(pane.downgrade(), leader_id); -// self.unfollow(&pane, cx); -// self.follower_states.insert( -// pane.clone(), -// FollowerState { -// leader_id, -// active_view_id: None, -// items_by_leader_view_id: Default::default(), -// }, -// ); -// cx.notify(); - -// let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); -// let project_id = self.project.read(cx).remote_id(); -// let request = self.app_state.client.request(proto::Follow { -// room_id, -// project_id, -// leader_id: Some(leader_id), -// }); - -// Some(cx.spawn(|this, mut cx| async move { -// let response = request.await?; -// this.update(&mut cx, |this, _| { -// let state = this -// .follower_states -// .get_mut(&pane) -// .ok_or_else(|| anyhow!("following interrupted"))?; -// state.active_view_id = if let Some(active_view_id) = response.active_view_id { -// Some(ViewId::from_proto(active_view_id)?) -// } else { -// None -// }; -// Ok::<_, anyhow::Error>(()) -// })??; -// Self::add_views_from_leader( -// this.clone(), -// leader_id, -// vec![pane], -// response.views, -// &mut cx, -// ) -// .await?; -// this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?; -// Ok(()) -// })) -// } - -// pub fn follow_next_collaborator( -// &mut self, -// _: &FollowNextCollaborator, -// cx: &mut ViewContext, -// ) -> Option>> { -// let collaborators = self.project.read(cx).collaborators(); -// let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) { -// let mut collaborators = collaborators.keys().copied(); -// for peer_id in collaborators.by_ref() { -// if peer_id == leader_id { -// break; -// } -// } -// collaborators.next() -// } else if let Some(last_leader_id) = -// self.last_leaders_by_pane.get(&self.active_pane.downgrade()) -// { -// if collaborators.contains_key(last_leader_id) { -// Some(*last_leader_id) -// } else { -// None -// } -// } else { -// None -// }; - -// let pane = self.active_pane.clone(); -// let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next()) -// else { -// return None; -// }; -// if Some(leader_id) == self.unfollow(&pane, cx) { -// return None; -// } -// self.follow(leader_id, cx) -// } - -// pub fn follow( -// &mut self, -// leader_id: PeerId, -// cx: &mut ViewContext, -// ) -> Option>> { -// let room = ActiveCall::global(cx).read(cx).room()?.read(cx); -// let project = self.project.read(cx); - -// let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else { -// return None; -// }; - -// let other_project_id = match remote_participant.location { -// call::ParticipantLocation::External => None, -// call::ParticipantLocation::UnsharedProject => None, -// call::ParticipantLocation::SharedProject { project_id } => { -// if Some(project_id) == project.remote_id() { -// None -// } else { -// Some(project_id) -// } -// } -// }; - -// // if they are active in another project, follow there. -// if let Some(project_id) = other_project_id { -// let app_state = self.app_state.clone(); -// return Some(crate::join_remote_project( -// project_id, -// remote_participant.user.id, -// app_state, -// cx, -// )); -// } - -// // if you're already following, find the right pane and focus it. -// for (pane, state) in &self.follower_states { -// if leader_id == state.leader_id { -// cx.focus(pane); -// return None; -// } -// } - -// // Otherwise, follow. -// self.start_following(leader_id, cx) -// } - -// pub fn unfollow( -// &mut self, -// pane: &ViewHandle, -// cx: &mut ViewContext, -// ) -> Option { -// let state = self.follower_states.remove(pane)?; -// let leader_id = state.leader_id; -// for (_, item) in state.items_by_leader_view_id { -// item.set_leader_peer_id(None, cx); -// } - -// if self -// .follower_states -// .values() -// .all(|state| state.leader_id != state.leader_id) -// { -// let project_id = self.project.read(cx).remote_id(); -// let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); -// self.app_state -// .client -// .send(proto::Unfollow { -// room_id, -// project_id, -// leader_id: Some(leader_id), -// }) -// .log_err(); -// } - -// cx.notify(); -// Some(leader_id) -// } - -// pub fn is_being_followed(&self, peer_id: PeerId) -> bool { -// self.follower_states -// .values() -// .any(|state| state.leader_id == peer_id) -// } - -// fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { -// // TODO: There should be a better system in place for this -// // (https://github.com/zed-industries/zed/issues/1290) -// let is_fullscreen = cx.window_is_fullscreen(); -// let container_theme = if is_fullscreen { -// let mut container_theme = theme.titlebar.container; -// container_theme.padding.left = container_theme.padding.right; -// container_theme -// } else { -// theme.titlebar.container -// }; - -// enum TitleBar {} -// MouseEventHandler::new::(0, cx, |_, cx| { -// Stack::new() -// .with_children( -// self.titlebar_item -// .as_ref() -// .map(|item| ChildView::new(item, cx)), -// ) -// .contained() -// .with_style(container_theme) -// }) -// .on_click(MouseButton::Left, |event, _, cx| { -// if event.click_count == 2 { -// cx.zoom_window(); -// } -// }) -// .constrained() -// .with_height(theme.titlebar.height) -// .into_any_named("titlebar") -// } - -// fn active_item_path_changed(&mut self, cx: &mut ViewContext) { -// let active_entry = self.active_project_path(cx); -// self.project -// .update(cx, |project, cx| project.set_active_path(active_entry, cx)); -// self.update_window_title(cx); -// } - -// fn update_window_title(&mut self, cx: &mut ViewContext) { -// let project = self.project().read(cx); -// let mut title = String::new(); - -// if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { -// let filename = path -// .path -// .file_name() -// .map(|s| s.to_string_lossy()) -// .or_else(|| { -// Some(Cow::Borrowed( -// project -// .worktree_for_id(path.worktree_id, cx)? -// .read(cx) -// .root_name(), -// )) -// }); - -// if let Some(filename) = filename { -// title.push_str(filename.as_ref()); -// title.push_str(" — "); -// } -// } - -// for (i, name) in project.worktree_root_names(cx).enumerate() { -// if i > 0 { -// title.push_str(", "); -// } -// title.push_str(name); -// } - -// if title.is_empty() { -// title = "empty project".to_string(); -// } - -// if project.is_remote() { -// title.push_str(" ↙"); -// } else if project.is_shared() { -// title.push_str(" ↗"); -// } - -// cx.set_window_title(&title); -// } - -// fn update_window_edited(&mut self, cx: &mut ViewContext) { -// let is_edited = !self.project.read(cx).is_read_only() -// && self -// .items(cx) -// .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); -// if is_edited != self.window_edited { -// self.window_edited = is_edited; -// cx.set_window_edited(self.window_edited) -// } -// } - -// fn render_disconnected_overlay( -// &self, -// cx: &mut ViewContext, -// ) -> Option> { -// if self.project.read(cx).is_read_only() { -// enum DisconnectedOverlay {} -// Some( -// MouseEventHandler::new::(0, cx, |_, cx| { -// let theme = &theme::current(cx); -// Label::new( -// "Your connection to the remote project has been lost.", -// theme.workspace.disconnected_overlay.text.clone(), -// ) -// .aligned() -// .contained() -// .with_style(theme.workspace.disconnected_overlay.container) -// }) -// .with_cursor_style(CursorStyle::Arrow) -// .capture_all() -// .into_any_named("disconnected overlay"), -// ) -// } else { -// None -// } -// } - -// fn render_notifications( -// &self, -// theme: &theme::Workspace, -// cx: &AppContext, -// ) -> Option> { -// if self.notifications.is_empty() { -// None -// } else { -// Some( -// Flex::column() -// .with_children(self.notifications.iter().map(|(_, _, notification)| { -// ChildView::new(notification.as_any(), cx) -// .contained() -// .with_style(theme.notification) -// })) -// .constrained() -// .with_width(theme.notifications.width) -// .contained() -// .with_style(theme.notifications.container) -// .aligned() -// .bottom() -// .right() -// .into_any(), -// ) -// } -// } - -// // RPC handlers - -// fn handle_follow( -// &mut self, -// follower_project_id: Option, -// cx: &mut ViewContext, -// ) -> proto::FollowResponse { -// let client = &self.app_state.client; -// let project_id = self.project.read(cx).remote_id(); - -// let active_view_id = self.active_item(cx).and_then(|i| { -// Some( -// i.to_followable_item_handle(cx)? -// .remote_id(client, cx)? -// .to_proto(), -// ) -// }); - -// cx.notify(); - -// self.last_active_view_id = active_view_id.clone(); -// proto::FollowResponse { -// active_view_id, -// views: self -// .panes() -// .iter() -// .flat_map(|pane| { -// let leader_id = self.leader_for_pane(pane); -// pane.read(cx).items().filter_map({ -// let cx = &cx; -// move |item| { -// let item = item.to_followable_item_handle(cx)?; -// if (project_id.is_none() || project_id != follower_project_id) -// && item.is_project_item(cx) -// { -// return None; -// } -// let id = item.remote_id(client, cx)?.to_proto(); -// let variant = item.to_state_proto(cx)?; -// Some(proto::View { -// id: Some(id), -// leader_id, -// variant: Some(variant), -// }) -// } -// }) -// }) -// .collect(), -// } -// } - -// fn handle_update_followers( -// &mut self, -// leader_id: PeerId, -// message: proto::UpdateFollowers, -// _cx: &mut ViewContext, -// ) { -// self.leader_updates_tx -// .unbounded_send((leader_id, message)) -// .ok(); -// } - -// async fn process_leader_update( -// this: &WeakViewHandle, -// leader_id: PeerId, -// update: proto::UpdateFollowers, -// cx: &mut AsyncAppContext, -// ) -> Result<()> { -// match update.variant.ok_or_else(|| anyhow!("invalid update"))? { -// proto::update_followers::Variant::UpdateActiveView(update_active_view) => { -// this.update(cx, |this, _| { -// for (_, state) in &mut this.follower_states { -// if state.leader_id == leader_id { -// state.active_view_id = -// if let Some(active_view_id) = update_active_view.id.clone() { -// Some(ViewId::from_proto(active_view_id)?) -// } else { -// None -// }; -// } -// } -// anyhow::Ok(()) -// })??; -// } -// proto::update_followers::Variant::UpdateView(update_view) => { -// let variant = update_view -// .variant -// .ok_or_else(|| anyhow!("missing update view variant"))?; -// let id = update_view -// .id -// .ok_or_else(|| anyhow!("missing update view id"))?; -// let mut tasks = Vec::new(); -// this.update(cx, |this, cx| { -// let project = this.project.clone(); -// for (_, state) in &mut this.follower_states { -// if state.leader_id == leader_id { -// let view_id = ViewId::from_proto(id.clone())?; -// if let Some(item) = state.items_by_leader_view_id.get(&view_id) { -// tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); -// } -// } -// } -// anyhow::Ok(()) -// })??; -// try_join_all(tasks).await.log_err(); -// } -// proto::update_followers::Variant::CreateView(view) => { -// let panes = this.read_with(cx, |this, _| { -// this.follower_states -// .iter() -// .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) -// .cloned() -// .collect() -// })?; -// Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?; -// } -// } -// this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?; -// Ok(()) -// } - -// async fn add_views_from_leader( -// this: WeakViewHandle, -// leader_id: PeerId, -// panes: Vec>, -// views: Vec, -// cx: &mut AsyncAppContext, -// ) -> Result<()> { -// let this = this -// .upgrade(cx) -// .ok_or_else(|| anyhow!("workspace dropped"))?; - -// let item_builders = cx.update(|cx| { -// cx.default_global::() -// .values() -// .map(|b| b.0) -// .collect::>() -// }); - -// let mut item_tasks_by_pane = HashMap::default(); -// for pane in panes { -// let mut item_tasks = Vec::new(); -// let mut leader_view_ids = Vec::new(); -// for view in &views { -// let Some(id) = &view.id else { continue }; -// let id = ViewId::from_proto(id.clone())?; -// let mut variant = view.variant.clone(); -// if variant.is_none() { -// Err(anyhow!("missing view variant"))?; -// } -// for build_item in &item_builders { -// let task = cx -// .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); -// if let Some(task) = task { -// item_tasks.push(task); -// leader_view_ids.push(id); -// break; -// } else { -// assert!(variant.is_some()); -// } -// } -// } - -// item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids)); -// } - -// for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { -// let items = futures::future::try_join_all(item_tasks).await?; -// this.update(cx, |this, cx| { -// let state = this.follower_states.get_mut(&pane)?; -// for (id, item) in leader_view_ids.into_iter().zip(items) { -// item.set_leader_peer_id(Some(leader_id), cx); -// state.items_by_leader_view_id.insert(id, item); -// } - -// Some(()) -// }); -// } -// Ok(()) -// } - -// fn update_active_view_for_followers(&mut self, cx: &AppContext) { -// let mut is_project_item = true; -// let mut update = proto::UpdateActiveView::default(); -// if self.active_pane.read(cx).has_focus() { -// let item = self -// .active_item(cx) -// .and_then(|item| item.to_followable_item_handle(cx)); -// if let Some(item) = item { -// is_project_item = item.is_project_item(cx); -// update = proto::UpdateActiveView { -// id: item -// .remote_id(&self.app_state.client, cx) -// .map(|id| id.to_proto()), -// leader_id: self.leader_for_pane(&self.active_pane), -// }; -// } -// } - -// if update.id != self.last_active_view_id { -// self.last_active_view_id = update.id.clone(); -// self.update_followers( -// is_project_item, -// proto::update_followers::Variant::UpdateActiveView(update), -// cx, -// ); -// } -// } - -// fn update_followers( -// &self, -// project_only: bool, -// update: proto::update_followers::Variant, -// cx: &AppContext, -// ) -> Option<()> { -// let project_id = if project_only { -// self.project.read(cx).remote_id() -// } else { -// None -// }; -// self.app_state().workspace_store.read_with(cx, |store, cx| { -// store.update_followers(project_id, update, cx) -// }) -// } - -// pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { -// self.follower_states.get(pane).map(|state| state.leader_id) -// } - -// fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { -// cx.notify(); - -// let call = self.active_call()?; -// let room = call.read(cx).room()?.read(cx); -// let participant = room.remote_participant_for_peer_id(leader_id)?; -// let mut items_to_activate = Vec::new(); - -// let leader_in_this_app; -// let leader_in_this_project; -// match participant.location { -// call::ParticipantLocation::SharedProject { project_id } => { -// leader_in_this_app = true; -// leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); -// } -// call::ParticipantLocation::UnsharedProject => { -// leader_in_this_app = true; -// leader_in_this_project = false; -// } -// call::ParticipantLocation::External => { -// leader_in_this_app = false; -// leader_in_this_project = false; -// } -// }; - -// for (pane, state) in &self.follower_states { -// if state.leader_id != leader_id { -// continue; -// } -// if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { -// if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { -// if leader_in_this_project || !item.is_project_item(cx) { -// items_to_activate.push((pane.clone(), item.boxed_clone())); -// } -// } else { -// log::warn!( -// "unknown view id {:?} for leader {:?}", -// active_view_id, -// leader_id -// ); -// } -// continue; -// } -// if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { -// items_to_activate.push((pane.clone(), Box::new(shared_screen))); -// } -// } - -// for (pane, item) in items_to_activate { -// let pane_was_focused = pane.read(cx).has_focus(); -// if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { -// pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); -// } else { -// pane.update(cx, |pane, cx| { -// pane.add_item(item.boxed_clone(), false, false, None, cx) -// }); -// } - -// if pane_was_focused { -// pane.update(cx, |pane, cx| pane.focus_active_item(cx)); -// } -// } - -// None -// } - -// fn shared_screen_for_peer( -// &self, -// peer_id: PeerId, -// pane: &ViewHandle, -// cx: &mut ViewContext, -// ) -> Option> { -// let call = self.active_call()?; -// let room = call.read(cx).room()?.read(cx); -// let participant = room.remote_participant_for_peer_id(peer_id)?; -// let track = participant.video_tracks.values().next()?.clone(); -// let user = participant.user.clone(); - -// for item in pane.read(cx).items_of_type::() { -// if item.read(cx).peer_id == peer_id { -// return Some(item); -// } -// } - -// Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) -// } - -// pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { -// if active { -// self.update_active_view_for_followers(cx); -// cx.background() -// .spawn(persistence::DB.update_timestamp(self.database_id())) -// .detach(); -// } else { -// for pane in &self.panes { -// pane.update(cx, |pane, cx| { -// if let Some(item) = pane.active_item() { -// item.workspace_deactivated(cx); -// } -// if matches!( -// settings::get::(cx).autosave, -// AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange -// ) { -// for item in pane.items() { -// Pane::autosave_item(item.as_ref(), self.project.clone(), cx) -// .detach_and_log_err(cx); -// } -// } -// }); -// } -// } -// } - -// fn active_call(&self) -> Option<&ModelHandle> { -// self.active_call.as_ref().map(|(call, _)| call) -// } - -// fn on_active_call_event( -// &mut self, -// _: ModelHandle, -// event: &call::room::Event, -// cx: &mut ViewContext, -// ) { -// match event { -// call::room::Event::ParticipantLocationChanged { participant_id } -// | call::room::Event::RemoteVideoTracksChanged { participant_id } => { -// self.leader_updated(*participant_id, cx); -// } -// _ => {} -// } -// } - -// pub fn database_id(&self) -> WorkspaceId { -// self.database_id -// } - -// fn location(&self, cx: &AppContext) -> Option { -// let project = self.project().read(cx); - -// if project.is_local() { -// Some( -// project -// .visible_worktrees(cx) -// .map(|worktree| worktree.read(cx).abs_path()) -// .collect::>() -// .into(), -// ) -// } else { -// None -// } -// } - -// fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { -// match member { -// Member::Axis(PaneAxis { members, .. }) => { -// for child in members.iter() { -// self.remove_panes(child.clone(), cx) -// } -// } -// Member::Pane(pane) => { -// self.force_remove_pane(&pane, cx); -// } -// } -// } - -// fn force_remove_pane(&mut self, pane: &ViewHandle, cx: &mut ViewContext) { -// self.panes.retain(|p| p != pane); -// cx.focus(self.panes.last().unwrap()); -// if self.last_active_center_pane == Some(pane.downgrade()) { -// self.last_active_center_pane = None; -// } -// cx.notify(); -// } - -// fn schedule_serialize(&mut self, cx: &mut ViewContext) { -// self._schedule_serialize = Some(cx.spawn(|this, cx| async move { -// cx.background().timer(Duration::from_millis(100)).await; -// this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) -// .ok(); -// })); -// } - -// fn serialize_workspace(&self, cx: &ViewContext) { -// fn serialize_pane_handle( -// pane_handle: &ViewHandle, -// cx: &AppContext, -// ) -> SerializedPane { -// let (items, active) = { -// let pane = pane_handle.read(cx); -// let active_item_id = pane.active_item().map(|item| item.id()); -// ( -// pane.items() -// .filter_map(|item_handle| { -// Some(SerializedItem { -// kind: Arc::from(item_handle.serialized_item_kind()?), -// item_id: item_handle.id(), -// active: Some(item_handle.id()) == active_item_id, -// }) -// }) -// .collect::>(), -// pane.has_focus(), -// ) -// }; - -// SerializedPane::new(items, active) -// } - -// fn build_serialized_pane_group( -// pane_group: &Member, -// cx: &AppContext, -// ) -> SerializedPaneGroup { -// match pane_group { -// Member::Axis(PaneAxis { -// axis, -// members, -// flexes, -// bounding_boxes: _, -// }) => SerializedPaneGroup::Group { -// axis: *axis, -// children: members -// .iter() -// .map(|member| build_serialized_pane_group(member, cx)) -// .collect::>(), -// flexes: Some(flexes.borrow().clone()), -// }, -// Member::Pane(pane_handle) => { -// SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) -// } -// } -// } - -// fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { -// let left_dock = this.left_dock.read(cx); -// let left_visible = left_dock.is_open(); -// let left_active_panel = left_dock.visible_panel().and_then(|panel| { -// Some( -// cx.view_ui_name(panel.as_any().window(), panel.id())? -// .to_string(), -// ) -// }); -// let left_dock_zoom = left_dock -// .visible_panel() -// .map(|panel| panel.is_zoomed(cx)) -// .unwrap_or(false); - -// let right_dock = this.right_dock.read(cx); -// let right_visible = right_dock.is_open(); -// let right_active_panel = right_dock.visible_panel().and_then(|panel| { -// Some( -// cx.view_ui_name(panel.as_any().window(), panel.id())? -// .to_string(), -// ) -// }); -// let right_dock_zoom = right_dock -// .visible_panel() -// .map(|panel| panel.is_zoomed(cx)) -// .unwrap_or(false); - -// let bottom_dock = this.bottom_dock.read(cx); -// let bottom_visible = bottom_dock.is_open(); -// let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { -// Some( -// cx.view_ui_name(panel.as_any().window(), panel.id())? -// .to_string(), -// ) -// }); -// let bottom_dock_zoom = bottom_dock -// .visible_panel() -// .map(|panel| panel.is_zoomed(cx)) -// .unwrap_or(false); - -// DockStructure { -// left: DockData { -// visible: left_visible, -// active_panel: left_active_panel, -// zoom: left_dock_zoom, -// }, -// right: DockData { -// visible: right_visible, -// active_panel: right_active_panel, -// zoom: right_dock_zoom, -// }, -// bottom: DockData { -// visible: bottom_visible, -// active_panel: bottom_active_panel, -// zoom: bottom_dock_zoom, -// }, -// } -// } - -// if let Some(location) = self.location(cx) { -// // Load bearing special case: -// // - with_local_workspace() relies on this to not have other stuff open -// // when you open your log -// if !location.paths().is_empty() { -// let center_group = build_serialized_pane_group(&self.center.root, cx); -// let docks = build_serialized_docks(self, cx); - -// let serialized_workspace = SerializedWorkspace { -// id: self.database_id, -// location, -// center_group, -// bounds: Default::default(), -// display: Default::default(), -// docks, -// }; - -// cx.background() -// .spawn(persistence::DB.save_workspace(serialized_workspace)) -// .detach(); -// } -// } -// } - -// pub(crate) fn load_workspace( -// workspace: WeakViewHandle, -// serialized_workspace: SerializedWorkspace, -// paths_to_open: Vec>, -// cx: &mut AppContext, -// ) -> Task>>>> { -// cx.spawn(|mut cx| async move { -// let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { -// ( -// workspace.project().clone(), -// workspace.last_active_center_pane.clone(), -// ) -// })?; - -// let mut center_group = None; -// let mut center_items = None; -// // Traverse the splits tree and add to things -// if let Some((group, active_pane, items)) = serialized_workspace -// .center_group -// .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) -// .await -// { -// center_items = Some(items); -// center_group = Some((group, active_pane)) -// } - -// let mut items_by_project_path = cx.read(|cx| { -// center_items -// .unwrap_or_default() -// .into_iter() -// .filter_map(|item| { -// let item = item?; -// let project_path = item.project_path(cx)?; -// Some((project_path, item)) -// }) -// .collect::>() -// }); - -// let opened_items = paths_to_open -// .into_iter() -// .map(|path_to_open| { -// path_to_open -// .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) -// }) -// .collect::>(); - -// // Remove old panes from workspace panes list -// workspace.update(&mut cx, |workspace, cx| { -// if let Some((center_group, active_pane)) = center_group { -// workspace.remove_panes(workspace.center.root.clone(), cx); - -// // Swap workspace center group -// workspace.center = PaneGroup::with_root(center_group); - -// // Change the focus to the workspace first so that we retrigger focus in on the pane. -// cx.focus_self(); - -// if let Some(active_pane) = active_pane { -// cx.focus(&active_pane); -// } else { -// cx.focus(workspace.panes.last().unwrap()); -// } -// } else { -// let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); -// if let Some(old_center_handle) = old_center_handle { -// cx.focus(&old_center_handle) -// } else { -// cx.focus_self() -// } -// } - -// let docks = serialized_workspace.docks; -// workspace.left_dock.update(cx, |dock, cx| { -// dock.set_open(docks.left.visible, cx); -// if let Some(active_panel) = docks.left.active_panel { -// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { -// dock.activate_panel(ix, cx); -// } -// } -// dock.active_panel() -// .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); -// if docks.left.visible && docks.left.zoom { -// cx.focus_self() -// } -// }); -// // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something -// workspace.right_dock.update(cx, |dock, cx| { -// dock.set_open(docks.right.visible, cx); -// if let Some(active_panel) = docks.right.active_panel { -// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { -// dock.activate_panel(ix, cx); -// } -// } -// dock.active_panel() -// .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); - -// if docks.right.visible && docks.right.zoom { -// cx.focus_self() -// } -// }); -// workspace.bottom_dock.update(cx, |dock, cx| { -// dock.set_open(docks.bottom.visible, cx); -// if let Some(active_panel) = docks.bottom.active_panel { -// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { -// dock.activate_panel(ix, cx); -// } -// } +type ProjectItemBuilders = + HashMap, AnyHandle, &mut ViewContext) -> Box>; +pub fn register_project_item(cx: &mut AppContext) { + cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { + builders.insert(TypeId::of::(), |project, model, cx| { + let item = model.downcast::().unwrap(); + Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) + }); + }); +} -// dock.active_panel() -// .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); +// type FollowableItemBuilder = fn( +// ViewHandle, +// ViewHandle, +// ViewId, +// &mut Option, +// &mut AppContext, +// ) -> Option>>>; +// type FollowableItemBuilders = HashMap< +// TypeId, +// ( +// FollowableItemBuilder, +// fn(&AnyViewHandle) -> Box, +// ), +// >; +// pub fn register_followable_item(cx: &mut AppContext) { +// cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { +// builders.insert( +// TypeId::of::(), +// ( +// |pane, workspace, id, state, cx| { +// I::from_state_proto(pane, workspace, id, state, cx).map(|task| { +// cx.foreground() +// .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) +// }) +// }, +// |this| Box::new(this.clone().downcast::().unwrap()), +// ), +// ); +// }); +// } -// if docks.bottom.visible && docks.bottom.zoom { -// cx.focus_self() -// } -// }); +// type ItemDeserializers = HashMap< +// Arc, +// fn( +// ModelHandle, +// WeakViewHandle, +// WorkspaceId, +// ItemId, +// &mut ViewContext, +// ) -> Task>>, +// >; +// pub fn register_deserializable_item(cx: &mut AppContext) { +// cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| { +// if let Some(serialized_item_kind) = I::serialized_item_kind() { +// deserializers.insert( +// Arc::from(serialized_item_kind), +// |project, workspace, workspace_id, item_id, cx| { +// let task = I::deserialize(project, workspace, workspace_id, item_id, cx); +// cx.foreground() +// .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) +// }, +// ); +// } +// }); +// } -// cx.notify(); -// })?; +pub struct AppState { + pub languages: Arc, + pub client: Arc, + pub user_store: Handle, + pub workspace_store: Handle, + pub fs: Arc, + pub build_window_options: + fn(Option, Option, &MainThread) -> WindowOptions, + pub initialize_workspace: + fn(WeakHandle, bool, Arc, AsyncAppContext) -> Task>, + pub node_runtime: Arc, +} -// // Serialize ourself to make sure our timestamps and any pane / item changes are replicated -// workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; +pub struct WorkspaceStore { + workspaces: HashSet>, + followers: Vec, + client: Arc, + _subscriptions: Vec, +} -// Ok(opened_items) -// }) -// } +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +struct Follower { + project_id: Option, + peer_id: PeerId, +} +// impl AppState { // #[cfg(any(test, feature = "test-support"))] -// pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { +// pub fn test(cx: &mut AppContext) -> Arc { // use node_runtime::FakeNodeRuntime; +// use settings::SettingsStore; -// let client = project.read(cx).client(); -// let user_store = project.read(cx).user_store(); +// if !cx.has_global::() { +// cx.set_global(SettingsStore::test(cx)); +// } +// let fs = fs::FakeFs::new(cx.background().clone()); +// let languages = Arc::new(LanguageRegistry::test()); +// let http_client = util::http::FakeHttpClient::with_404_response(); +// let client = Client::new(http_client.clone(), cx); +// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); -// let app_state = Arc::new(AppState { -// languages: project.read(cx).languages().clone(), -// workspace_store, + +// theme::init((), cx); +// client::init(&client, cx); +// crate::init_settings(cx); + +// Arc::new(Self { // client, +// fs, +// languages, // user_store, -// fs: project.read(cx).fs().clone(), -// build_window_options: |_, _, _| Default::default(), -// initialize_workspace: |_, _, _, _| Task::ready(Ok(())), +// // channel_store, +// workspace_store, // node_runtime: FakeNodeRuntime::new(), -// }); -// Self::new(0, project, app_state, cx) -// } - -// fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { -// let dock = match position { -// DockPosition::Left => &self.left_dock, -// DockPosition::Right => &self.right_dock, -// DockPosition::Bottom => &self.bottom_dock, -// }; -// let active_panel = dock.read(cx).visible_panel()?; -// let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { -// dock.read(cx).render_placeholder(cx) -// } else { -// ChildView::new(dock, cx).into_any() -// }; - -// Some( -// element -// .constrained() -// .dynamically(move |constraint, _, cx| match position { -// DockPosition::Left | DockPosition::Right => SizeConstraint::new( -// Vector2F::new(20., constraint.min.y()), -// Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), -// ), -// DockPosition::Bottom => SizeConstraint::new( -// Vector2F::new(constraint.min.x(), 20.), -// Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), -// ), -// }) -// .into_any(), -// ) +// initialize_workspace: |_, _, _, _| Task::ready(Ok(())), +// build_window_options: |_, _, _| Default::default(), +// }) // } // } -// fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { -// ZED_WINDOW_POSITION -// .zip(*ZED_WINDOW_SIZE) -// .map(|(position, size)| { -// WindowBounds::Fixed(RectF::new( -// cx.platform().screens()[0].bounds().origin() + position, -// size, -// )) -// }) +// struct DelayedDebouncedEditAction { +// task: Option>, +// cancel_channel: Option>, // } -// async fn open_items( -// serialized_workspace: Option, -// workspace: &WeakViewHandle, -// mut project_paths_to_open: Vec<(PathBuf, Option)>, -// app_state: Arc, -// mut cx: AsyncAppContext, -// ) -> Result>>>> { -// let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - -// if let Some(serialized_workspace) = serialized_workspace { -// let workspace = workspace.clone(); -// let restored_items = cx -// .update(|cx| { -// Workspace::load_workspace( -// workspace, -// serialized_workspace, -// project_paths_to_open -// .iter() -// .map(|(_, project_path)| project_path) -// .cloned() -// .collect(), -// cx, -// ) -// }) -// .await?; - -// let restored_project_paths = cx.read(|cx| { -// restored_items -// .iter() -// .filter_map(|item| item.as_ref()?.project_path(cx)) -// .collect::>() -// }); - -// for restored_item in restored_items { -// opened_items.push(restored_item.map(Ok)); -// } - -// project_paths_to_open -// .iter_mut() -// .for_each(|(_, project_path)| { -// if let Some(project_path_to_open) = project_path { -// if restored_project_paths.contains(project_path_to_open) { -// *project_path = None; -// } -// } -// }); -// } else { -// for _ in 0..project_paths_to_open.len() { -// opened_items.push(None); +// impl DelayedDebouncedEditAction { +// fn new() -> DelayedDebouncedEditAction { +// DelayedDebouncedEditAction { +// task: None, +// cancel_channel: None, // } // } -// assert!(opened_items.len() == project_paths_to_open.len()); - -// let tasks = -// project_paths_to_open -// .into_iter() -// .enumerate() -// .map(|(i, (abs_path, project_path))| { -// let workspace = workspace.clone(); -// cx.spawn(|mut cx| { -// let fs = app_state.fs.clone(); -// async move { -// let file_project_path = project_path?; -// if fs.is_file(&abs_path).await { -// Some(( -// i, -// workspace -// .update(&mut cx, |workspace, cx| { -// workspace.open_path(file_project_path, None, true, cx) -// }) -// .log_err()? -// .await, -// )) -// } else { -// None -// } -// } -// }) -// }); -// for maybe_opened_path in futures::future::join_all(tasks.into_iter()) -// .await -// .into_iter() +// fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) +// where +// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, // { -// if let Some((i, path_open_result)) = maybe_opened_path { -// opened_items[i] = Some(path_open_result); +// if let Some(channel) = self.cancel_channel.take() { +// _ = channel.send(()); // } -// } -// Ok(opened_items) -// } +// let (sender, mut receiver) = oneshot::channel::<()>(); +// self.cancel_channel = Some(sender); + +// let previous_task = self.task.take(); +// self.task = Some(cx.spawn(|workspace, mut cx| async move { +// let mut timer = cx.background().timer(delay).fuse(); +// if let Some(previous_task) = previous_task { +// previous_task.await; +// } -// fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { -// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; -// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; -// const MESSAGE_ID: usize = 2; +// futures::select_biased! { +// _ = receiver => return, +// _ = timer => {} +// } -// if workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// return; +// if let Some(result) = workspace +// .update(&mut cx, |workspace, cx| (func)(workspace, cx)) +// .log_err() +// { +// result.await.log_err(); +// } +// })); // } +// } -// if db::kvp::KEY_VALUE_STORE -// .read_kvp(NEW_DOCK_HINT_KEY) -// .ok() -// .flatten() -// .is_some() -// { -// if !workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// cx.update(|cx| { -// cx.update_global::(|tracker, _| { -// let entry = tracker -// .entry(TypeId::of::()) -// .or_default(); -// if !entry.contains(&MESSAGE_ID) { -// entry.push(MESSAGE_ID); -// } -// }); -// }); -// } +// pub enum Event { +// PaneAdded(ViewHandle), +// ContactRequestedJoin(u64), +// } -// return; -// } +pub struct Workspace { + weak_self: WeakHandle, + // modal: Option, + // zoomed: Option, + // zoomed_position: Option, + // center: PaneGroup, + // left_dock: ViewHandle, + // bottom_dock: ViewHandle, + // right_dock: ViewHandle, + panes: Vec>, + // panes_by_item: HashMap>, + // active_pane: ViewHandle, + last_active_center_pane: Option>, + // last_active_view_id: Option, + // status_bar: ViewHandle, + // titlebar_item: Option, + // notifications: Vec<(TypeId, usize, Box)>, + project: Handle, + // follower_states: HashMap, FollowerState>, + // last_leaders_by_pane: HashMap, PeerId>, + // window_edited: bool, + // active_call: Option<(ModelHandle, Vec)>, + // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, + // database_id: WorkspaceId, + app_state: Arc, + // subscriptions: Vec, + // _apply_leader_updates: Task>, + // _observe_current_user: Task>, + // _schedule_serialize: Option>, + // pane_history_timestamp: Arc, +} -// cx.spawn(|_| async move { -// db::kvp::KEY_VALUE_STORE -// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) -// .await -// .ok(); -// }) -// .detach(); +// struct ActiveModal { +// view: Box, +// previously_focused_view_id: Option, +// } -// workspace -// .update(cx, |workspace, cx| { -// workspace.show_notification_once(2, cx, |cx| { -// cx.add_view(|_| { -// MessageNotification::new_element(|text, _| { -// Text::new( -// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", -// text, -// ) -// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { -// let code_span_background_color = settings::get::(cx) -// .theme -// .editor -// .document_highlight_read_background; - -// cx.scene().push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }) -// }) -// .into_any() -// }) -// .with_click_message("Read more about the new panel system") -// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) -// }) -// }) -// }) -// .ok(); +// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +// pub struct ViewId { +// pub creator: PeerId, +// pub id: u64, // } +#[derive(Default)] +struct FollowerState { + leader_id: PeerId, + active_view_id: Option, + items_by_leader_view_id: HashMap>, +} + +// enum WorkspaceBounds {} + +impl Workspace { + // pub fn new( + // workspace_id: WorkspaceId, + // project: ModelHandle, + // app_state: Arc, + // cx: &mut ViewContext, + // ) -> Self { + // cx.observe(&project, |_, _, cx| cx.notify()).detach(); + // cx.subscribe(&project, move |this, _, event, cx| { + // match event { + // project::Event::RemoteIdChanged(_) => { + // this.update_window_title(cx); + // } + + // project::Event::CollaboratorLeft(peer_id) => { + // this.collaborator_left(*peer_id, cx); + // } + + // project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { + // this.update_window_title(cx); + // this.serialize_workspace(cx); + // } + + // project::Event::DisconnectedFromHost => { + // this.update_window_edited(cx); + // cx.blur(); + // } + + // project::Event::Closed => { + // cx.remove_window(); + // } + + // project::Event::DeletedEntry(entry_id) => { + // for pane in this.panes.iter() { + // pane.update(cx, |pane, cx| { + // pane.handle_deleted_project_item(*entry_id, cx) + // }); + // } + // } + + // project::Event::Notification(message) => this.show_notification(0, cx, |cx| { + // cx.add_view(|_| MessageNotification::new(message.clone())) + // }), + + // _ => {} + // } + // cx.notify() + // }) + // .detach(); + + // let weak_handle = cx.weak_handle(); + // let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); + + // let center_pane = cx.add_view(|cx| { + // Pane::new( + // weak_handle.clone(), + // project.clone(), + // pane_history_timestamp.clone(), + // cx, + // ) + // }); + // cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); + // cx.focus(¢er_pane); + // cx.emit(Event::PaneAdded(center_pane.clone())); + + // app_state.workspace_store.update(cx, |store, _| { + // store.workspaces.insert(weak_handle.clone()); + // }); + + // let mut current_user = app_state.user_store.read(cx).watch_current_user(); + // let mut connection_status = app_state.client.status(); + // let _observe_current_user = cx.spawn(|this, mut cx| async move { + // current_user.recv().await; + // connection_status.recv().await; + // let mut stream = + // Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); + + // while stream.recv().await.is_some() { + // this.update(&mut cx, |_, cx| cx.notify())?; + // } + // anyhow::Ok(()) + // }); + + // // All leader updates are enqueued and then processed in a single task, so + // // that each asynchronous operation can be run in order. + // let (leader_updates_tx, mut leader_updates_rx) = + // mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); + // let _apply_leader_updates = cx.spawn(|this, mut cx| async move { + // while let Some((leader_id, update)) = leader_updates_rx.next().await { + // Self::process_leader_update(&this, leader_id, update, &mut cx) + // .await + // .log_err(); + // } + + // Ok(()) + // }); + + // cx.emit_global(WorkspaceCreated(weak_handle.clone())); + + // let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + // let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); + // let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); + // let left_dock_buttons = + // cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + // let bottom_dock_buttons = + // cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); + // let right_dock_buttons = + // cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); + // let status_bar = cx.add_view(|cx| { + // let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); + // status_bar.add_left_item(left_dock_buttons, cx); + // status_bar.add_right_item(right_dock_buttons, cx); + // status_bar.add_right_item(bottom_dock_buttons, cx); + // status_bar + // }); + + // cx.update_default_global::, _, _>(|drag_and_drop, _| { + // drag_and_drop.register_container(weak_handle.clone()); + // }); + + // let mut active_call = None; + // if cx.has_global::>() { + // let call = cx.global::>().clone(); + // let mut subscriptions = Vec::new(); + // subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); + // active_call = Some((call, subscriptions)); + // } + + // let subscriptions = vec![ + // cx.observe_fullscreen(|_, _, cx| cx.notify()), + // cx.observe_window_activation(Self::on_window_activation_changed), + // cx.observe_window_bounds(move |_, mut bounds, display, cx| { + // // Transform fixed bounds to be stored in terms of the containing display + // if let WindowBounds::Fixed(mut window_bounds) = bounds { + // if let Some(screen) = cx.platform().screen_by_id(display) { + // let screen_bounds = screen.bounds(); + // window_bounds + // .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); + // window_bounds + // .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); + // bounds = WindowBounds::Fixed(window_bounds); + // } + // } + + // cx.background() + // .spawn(DB.set_window_bounds(workspace_id, bounds, display)) + // .detach_and_log_err(cx); + // }), + // cx.observe(&left_dock, |this, _, cx| { + // this.serialize_workspace(cx); + // cx.notify(); + // }), + // cx.observe(&bottom_dock, |this, _, cx| { + // this.serialize_workspace(cx); + // cx.notify(); + // }), + // cx.observe(&right_dock, |this, _, cx| { + // this.serialize_workspace(cx); + // cx.notify(); + // }), + // ]; + + // cx.defer(|this, cx| this.update_window_title(cx)); + // Workspace { + // weak_self: weak_handle.clone(), + // modal: None, + // zoomed: None, + // zoomed_position: None, + // center: PaneGroup::new(center_pane.clone()), + // panes: vec![center_pane.clone()], + // panes_by_item: Default::default(), + // active_pane: center_pane.clone(), + // last_active_center_pane: Some(center_pane.downgrade()), + // last_active_view_id: None, + // status_bar, + // titlebar_item: None, + // notifications: Default::default(), + // left_dock, + // bottom_dock, + // right_dock, + // project: project.clone(), + // follower_states: Default::default(), + // last_leaders_by_pane: Default::default(), + // window_edited: false, + // active_call, + // database_id: workspace_id, + // app_state, + // _observe_current_user, + // _apply_leader_updates, + // _schedule_serialize: None, + // leader_updates_tx, + // subscriptions, + // pane_history_timestamp, + // } + // } + + // fn new_local( + // abs_paths: Vec, + // app_state: Arc, + // requesting_window: Option>, + // cx: &mut AppContext, + // ) -> Task<( + // WeakViewHandle, + // Vec, anyhow::Error>>>, + // )> { + // let project_handle = Project::local( + // app_state.client.clone(), + // app_state.node_runtime.clone(), + // app_state.user_store.clone(), + // app_state.languages.clone(), + // app_state.fs.clone(), + // cx, + // ); + + // cx.spawn(|mut cx| async move { + // let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + + // let paths_to_open = Arc::new(abs_paths); + + // // Get project paths for all of the abs_paths + // let mut worktree_roots: HashSet> = Default::default(); + // let mut project_paths: Vec<(PathBuf, Option)> = + // Vec::with_capacity(paths_to_open.len()); + // for path in paths_to_open.iter().cloned() { + // if let Some((worktree, project_entry)) = cx + // .update(|cx| { + // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) + // }) + // .await + // .log_err() + // { + // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); + // project_paths.push((path, Some(project_entry))); + // } else { + // project_paths.push((path, None)); + // } + // } + + // let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { + // serialized_workspace.id + // } else { + // DB.next_id().await.unwrap_or(0) + // }; + + // let window = if let Some(window) = requesting_window { + // window.replace_root(&mut cx, |cx| { + // Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + // }); + // window + // } else { + // { + // let window_bounds_override = window_bounds_env_override(&cx); + // let (bounds, display) = if let Some(bounds) = window_bounds_override { + // (Some(bounds), None) + // } else { + // serialized_workspace + // .as_ref() + // .and_then(|serialized_workspace| { + // let display = serialized_workspace.display?; + // let mut bounds = serialized_workspace.bounds?; + + // // Stored bounds are relative to the containing display. + // // So convert back to global coordinates if that screen still exists + // if let WindowBounds::Fixed(mut window_bounds) = bounds { + // if let Some(screen) = cx.platform().screen_by_id(display) { + // let screen_bounds = screen.bounds(); + // window_bounds.set_origin_x( + // window_bounds.origin_x() + screen_bounds.origin_x(), + // ); + // window_bounds.set_origin_y( + // window_bounds.origin_y() + screen_bounds.origin_y(), + // ); + // bounds = WindowBounds::Fixed(window_bounds); + // } else { + // // Screen no longer exists. Return none here. + // return None; + // } + // } + + // Some((bounds, display)) + // }) + // .unzip() + // }; + + // // Use the serialized workspace to construct the new window + // cx.add_window( + // (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), + // |cx| { + // Workspace::new( + // workspace_id, + // project_handle.clone(), + // app_state.clone(), + // cx, + // ) + // }, + // ) + // } + // }; + + // // We haven't yielded the main thread since obtaining the window handle, + // // so the window exists. + // let workspace = window.root(&cx).unwrap(); + + // (app_state.initialize_workspace)( + // workspace.downgrade(), + // serialized_workspace.is_some(), + // app_state.clone(), + // cx.clone(), + // ) + // .await + // .log_err(); + + // window.update(&mut cx, |cx| cx.activate_window()); + + // let workspace = workspace.downgrade(); + // notify_if_database_failed(&workspace, &mut cx); + // let opened_items = open_items( + // serialized_workspace, + // &workspace, + // project_paths, + // app_state, + // cx, + // ) + // .await + // .unwrap_or_default(); + + // (workspace, opened_items) + // }) + // } + + // pub fn weak_handle(&self) -> WeakViewHandle { + // self.weak_self.clone() + // } + + // pub fn left_dock(&self) -> &ViewHandle { + // &self.left_dock + // } + + // pub fn bottom_dock(&self) -> &ViewHandle { + // &self.bottom_dock + // } + + // pub fn right_dock(&self) -> &ViewHandle { + // &self.right_dock + // } + + // pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) + // where + // T::Event: std::fmt::Debug, + // { + // self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) + // } + + // pub fn add_panel_with_extra_event_handler( + // &mut self, + // panel: ViewHandle, + // cx: &mut ViewContext, + // handler: F, + // ) where + // T::Event: std::fmt::Debug, + // F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, + // { + // let dock = match panel.position(cx) { + // DockPosition::Left => &self.left_dock, + // DockPosition::Bottom => &self.bottom_dock, + // DockPosition::Right => &self.right_dock, + // }; + + // self.subscriptions.push(cx.subscribe(&panel, { + // let mut dock = dock.clone(); + // let mut prev_position = panel.position(cx); + // move |this, panel, event, cx| { + // if T::should_change_position_on_event(event) { + // let new_position = panel.read(cx).position(cx); + // let mut was_visible = false; + // dock.update(cx, |dock, cx| { + // prev_position = new_position; + + // was_visible = dock.is_open() + // && dock + // .visible_panel() + // .map_or(false, |active_panel| active_panel.id() == panel.id()); + // dock.remove_panel(&panel, cx); + // }); + + // if panel.is_zoomed(cx) { + // this.zoomed_position = Some(new_position); + // } + + // dock = match panel.read(cx).position(cx) { + // DockPosition::Left => &this.left_dock, + // DockPosition::Bottom => &this.bottom_dock, + // DockPosition::Right => &this.right_dock, + // } + // .clone(); + // dock.update(cx, |dock, cx| { + // dock.add_panel(panel.clone(), cx); + // if was_visible { + // dock.set_open(true, cx); + // dock.activate_panel(dock.panels_len() - 1, cx); + // } + // }); + // } else if T::should_zoom_in_on_event(event) { + // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); + // if !panel.has_focus(cx) { + // cx.focus(&panel); + // } + // this.zoomed = Some(panel.downgrade().into_any()); + // this.zoomed_position = Some(panel.read(cx).position(cx)); + // } else if T::should_zoom_out_on_event(event) { + // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); + // if this.zoomed_position == Some(prev_position) { + // this.zoomed = None; + // this.zoomed_position = None; + // } + // cx.notify(); + // } else if T::is_focus_event(event) { + // let position = panel.read(cx).position(cx); + // this.dismiss_zoomed_items_to_reveal(Some(position), cx); + // if panel.is_zoomed(cx) { + // this.zoomed = Some(panel.downgrade().into_any()); + // this.zoomed_position = Some(position); + // } else { + // this.zoomed = None; + // this.zoomed_position = None; + // } + // this.update_active_view_for_followers(cx); + // cx.notify(); + // } else { + // handler(this, &panel, event, cx) + // } + // } + // })); + + // dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + // } + + // pub fn status_bar(&self) -> &ViewHandle { + // &self.status_bar + // } + + // pub fn app_state(&self) -> &Arc { + // &self.app_state + // } + + // pub fn user_store(&self) -> &ModelHandle { + // &self.app_state.user_store + // } + + pub fn project(&self) -> &Handle { + &self.project + } + + // pub fn recent_navigation_history( + // &self, + // limit: Option, + // cx: &AppContext, + // ) -> Vec<(ProjectPath, Option)> { + // let mut abs_paths_opened: HashMap> = HashMap::default(); + // let mut history: HashMap, usize)> = HashMap::default(); + // for pane in &self.panes { + // let pane = pane.read(cx); + // pane.nav_history() + // .for_each_entry(cx, |entry, (project_path, fs_path)| { + // if let Some(fs_path) = &fs_path { + // abs_paths_opened + // .entry(fs_path.clone()) + // .or_default() + // .insert(project_path.clone()); + // } + // let timestamp = entry.timestamp; + // match history.entry(project_path) { + // hash_map::Entry::Occupied(mut entry) => { + // let (_, old_timestamp) = entry.get(); + // if ×tamp > old_timestamp { + // entry.insert((fs_path, timestamp)); + // } + // } + // hash_map::Entry::Vacant(entry) => { + // entry.insert((fs_path, timestamp)); + // } + // } + // }); + // } + + // history + // .into_iter() + // .sorted_by_key(|(_, (_, timestamp))| *timestamp) + // .map(|(project_path, (fs_path, _))| (project_path, fs_path)) + // .rev() + // .filter(|(history_path, abs_path)| { + // let latest_project_path_opened = abs_path + // .as_ref() + // .and_then(|abs_path| abs_paths_opened.get(abs_path)) + // .and_then(|project_paths| { + // project_paths + // .iter() + // .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) + // }); + + // match latest_project_path_opened { + // Some(latest_project_path_opened) => latest_project_path_opened == history_path, + // None => true, + // } + // }) + // .take(limit.unwrap_or(usize::MAX)) + // .collect() + // } + + // fn navigate_history( + // &mut self, + // pane: WeakViewHandle, + // mode: NavigationMode, + // cx: &mut ViewContext, + // ) -> Task> { + // let to_load = if let Some(pane) = pane.upgrade(cx) { + // cx.focus(&pane); + + // pane.update(cx, |pane, cx| { + // loop { + // // Retrieve the weak item handle from the history. + // let entry = pane.nav_history_mut().pop(mode, cx)?; + + // // If the item is still present in this pane, then activate it. + // if let Some(index) = entry + // .item + // .upgrade(cx) + // .and_then(|v| pane.index_for_item(v.as_ref())) + // { + // let prev_active_item_index = pane.active_item_index(); + // pane.nav_history_mut().set_mode(mode); + // pane.activate_item(index, true, true, cx); + // pane.nav_history_mut().set_mode(NavigationMode::Normal); + + // let mut navigated = prev_active_item_index != pane.active_item_index(); + // if let Some(data) = entry.data { + // navigated |= pane.active_item()?.navigate(data, cx); + // } + + // if navigated { + // break None; + // } + // } + // // If the item is no longer present in this pane, then retrieve its + // // project path in order to reopen it. + // else { + // break pane + // .nav_history() + // .path_for_item(entry.item.id()) + // .map(|(project_path, _)| (project_path, entry)); + // } + // } + // }) + // } else { + // None + // }; + + // if let Some((project_path, entry)) = to_load { + // // If the item was no longer present, then load it again from its previous path. + // let task = self.load_path(project_path, cx); + // cx.spawn(|workspace, mut cx| async move { + // let task = task.await; + // let mut navigated = false; + // if let Some((project_entry_id, build_item)) = task.log_err() { + // let prev_active_item_id = pane.update(&mut cx, |pane, _| { + // pane.nav_history_mut().set_mode(mode); + // pane.active_item().map(|p| p.id()) + // })?; + + // pane.update(&mut cx, |pane, cx| { + // let item = pane.open_item(project_entry_id, true, cx, build_item); + // navigated |= Some(item.id()) != prev_active_item_id; + // pane.nav_history_mut().set_mode(NavigationMode::Normal); + // if let Some(data) = entry.data { + // navigated |= item.navigate(data, cx); + // } + // })?; + // } + + // if !navigated { + // workspace + // .update(&mut cx, |workspace, cx| { + // Self::navigate_history(workspace, pane, mode, cx) + // })? + // .await?; + // } + + // Ok(()) + // }) + // } else { + // Task::ready(Ok(())) + // } + // } + + // pub fn go_back( + // &mut self, + // pane: WeakViewHandle, + // cx: &mut ViewContext, + // ) -> Task> { + // self.navigate_history(pane, NavigationMode::GoingBack, cx) + // } + + // pub fn go_forward( + // &mut self, + // pane: WeakViewHandle, + // cx: &mut ViewContext, + // ) -> Task> { + // self.navigate_history(pane, NavigationMode::GoingForward, cx) + // } + + // pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { + // self.navigate_history( + // self.active_pane().downgrade(), + // NavigationMode::ReopeningClosedItem, + // cx, + // ) + // } + + // pub fn client(&self) -> &Client { + // &self.app_state.client + // } + + // pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext) { + // self.titlebar_item = Some(item); + // cx.notify(); + // } + + // pub fn titlebar_item(&self) -> Option { + // self.titlebar_item.clone() + // } + + // /// Call the given callback with a workspace whose project is local. + // /// + // /// If the given workspace has a local project, then it will be passed + // /// to the callback. Otherwise, a new empty window will be created. + // pub fn with_local_workspace( + // &mut self, + // cx: &mut ViewContext, + // callback: F, + // ) -> Task> + // where + // T: 'static, + // F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, + // { + // if self.project.read(cx).is_local() { + // Task::Ready(Some(Ok(callback(self, cx)))) + // } else { + // let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); + // cx.spawn(|_vh, mut cx| async move { + // let (workspace, _) = task.await; + // workspace.update(&mut cx, callback) + // }) + // } + // } + + // pub fn worktrees<'a>( + // &self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.project.read(cx).worktrees(cx) + // } + + // pub fn visible_worktrees<'a>( + // &self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.project.read(cx).visible_worktrees(cx) + // } + + // pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { + // let futures = self + // .worktrees(cx) + // .filter_map(|worktree| worktree.read(cx).as_local()) + // .map(|worktree| worktree.scan_complete()) + // .collect::>(); + // async move { + // for future in futures { + // future.await; + // } + // } + // } + + // pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { + // cx.spawn(|mut cx| async move { + // let window = cx + // .windows() + // .into_iter() + // .find(|window| window.is_active(&cx).unwrap_or(false)); + // if let Some(window) = window { + // //This can only get called when the window's project connection has been lost + // //so we don't need to prompt the user for anything and instead just close the window + // window.remove(&mut cx); + // } + // }) + // .detach(); + // } + + // pub fn close( + // &mut self, + // _: &CloseWindow, + // cx: &mut ViewContext, + // ) -> Option>> { + // let window = cx.window(); + // let prepare = self.prepare_to_close(false, cx); + // Some(cx.spawn(|_, mut cx| async move { + // if prepare.await? { + // window.remove(&mut cx); + // } + // Ok(()) + // })) + // } + + // pub fn prepare_to_close( + // &mut self, + // quitting: bool, + // cx: &mut ViewContext, + // ) -> Task> { + // let active_call = self.active_call().cloned(); + // let window = cx.window(); + + // cx.spawn(|this, mut cx| async move { + // let workspace_count = cx + // .windows() + // .into_iter() + // .filter(|window| window.root_is::()) + // .count(); + + // if let Some(active_call) = active_call { + // if !quitting + // && workspace_count == 1 + // && active_call.read_with(&cx, |call, _| call.room().is_some()) + // { + // let answer = window.prompt( + // PromptLevel::Warning, + // "Do you want to leave the current call?", + // &["Close window and hang up", "Cancel"], + // &mut cx, + // ); + + // if let Some(mut answer) = answer { + // if answer.next().await == Some(1) { + // return anyhow::Ok(false); + // } else { + // active_call + // .update(&mut cx, |call, cx| call.hang_up(cx)) + // .await + // .log_err(); + // } + // } + // } + // } + + // Ok(this + // .update(&mut cx, |this, cx| { + // this.save_all_internal(SaveIntent::Close, cx) + // })? + // .await?) + // }) + // } + + // fn save_all( + // &mut self, + // action: &SaveAll, + // cx: &mut ViewContext, + // ) -> Option>> { + // let save_all = + // self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx); + // Some(cx.foreground().spawn(async move { + // save_all.await?; + // Ok(()) + // })) + // } + + // fn save_all_internal( + // &mut self, + // mut save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // if self.project.read(cx).is_read_only() { + // return Task::ready(Ok(true)); + // } + // let dirty_items = self + // .panes + // .iter() + // .flat_map(|pane| { + // pane.read(cx).items().filter_map(|item| { + // if item.is_dirty(cx) { + // Some((pane.downgrade(), item.boxed_clone())) + // } else { + // None + // } + // }) + // }) + // .collect::>(); + + // let project = self.project.clone(); + // cx.spawn(|workspace, mut cx| async move { + // // Override save mode and display "Save all files" prompt + // if save_intent == SaveIntent::Close && dirty_items.len() > 1 { + // let mut answer = workspace.update(&mut cx, |_, cx| { + // let prompt = Pane::file_names_for_prompt( + // &mut dirty_items.iter().map(|(_, handle)| handle), + // dirty_items.len(), + // cx, + // ); + // cx.prompt( + // PromptLevel::Warning, + // &prompt, + // &["Save all", "Discard all", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => save_intent = SaveIntent::SaveAll, + // Some(1) => save_intent = SaveIntent::Skip, + // _ => {} + // } + // } + // for (pane, item) in dirty_items { + // let (singleton, project_entry_ids) = + // cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); + // if singleton || !project_entry_ids.is_empty() { + // if let Some(ix) = + // pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))? + // { + // if !Pane::save_item( + // project.clone(), + // &pane, + // ix, + // &*item, + // save_intent, + // &mut cx, + // ) + // .await? + // { + // return Ok(false); + // } + // } + // } + // } + // Ok(true) + // }) + // } + + // pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { + // let mut paths = cx.prompt_for_paths(PathPromptOptions { + // files: true, + // directories: true, + // multiple: true, + // }); + + // Some(cx.spawn(|this, mut cx| async move { + // if let Some(paths) = paths.recv().await.flatten() { + // if let Some(task) = this + // .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) + // .log_err() + // { + // task.await? + // } + // } + // Ok(()) + // })) + // } + + // pub fn open_workspace_for_paths( + // &mut self, + // paths: Vec, + // cx: &mut ViewContext, + // ) -> Task> { + // let window = cx.window().downcast::(); + // let is_remote = self.project.read(cx).is_remote(); + // let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); + // let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); + // let close_task = if is_remote || has_worktree || has_dirty_items { + // None + // } else { + // Some(self.prepare_to_close(false, cx)) + // }; + // let app_state = self.app_state.clone(); + + // cx.spawn(|_, mut cx| async move { + // let window_to_replace = if let Some(close_task) = close_task { + // if !close_task.await? { + // return Ok(()); + // } + // window + // } else { + // None + // }; + // cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx)) + // .await?; + // Ok(()) + // }) + // } + + #[allow(clippy::type_complexity)] + pub fn open_paths( + &mut self, + mut abs_paths: Vec, + visible: bool, + cx: &mut ViewContext, + ) -> Task, anyhow::Error>>>> { + log::info!("open paths {:?}", abs_paths); + + let fs = self.app_state.fs.clone(); + + // Sort the paths to ensure we add worktrees for parents before their children. + abs_paths.sort_unstable(); + cx.spawn(|this, mut cx| async move { + let mut tasks = Vec::with_capacity(abs_paths.len()); + for abs_path in &abs_paths { + let project_path = match this + .update(&mut cx, |this, cx| { + Workspace::project_path_for_path( + this.project.clone(), + abs_path, + visible, + cx, + ) + }) + .log_err() + { + Some(project_path) => project_path.await.log_err(), + None => None, + }; + + let this = this.clone(); + let task = cx.spawn(|mut cx| { + let fs = fs.clone(); + let abs_path = abs_path.clone(); + async move { + let (worktree, project_path) = project_path?; + if fs.is_file(&abs_path).await { + Some( + this.update(&mut cx, |this, cx| { + this.open_path(project_path, None, true, cx) + }) + .log_err()? + .await, + ) + } else { + this.update(&mut cx, |workspace, cx| { + let worktree = worktree.read(cx); + let worktree_abs_path = worktree.abs_path(); + let entry_id = if abs_path == worktree_abs_path.as_ref() { + worktree.root_entry() + } else { + abs_path + .strip_prefix(worktree_abs_path.as_ref()) + .ok() + .and_then(|relative_path| { + worktree.entry_for_path(relative_path) + }) + } + .map(|entry| entry.id); + if let Some(entry_id) = entry_id { + workspace.project.update(cx, |_, cx| { + cx.emit(project2::Event::ActiveEntryChanged(Some( + entry_id, + ))); + }) + } + }) + .log_err()?; + None + } + } + }); + tasks.push(task); + } + + futures::future::join_all(tasks).await + }) + } + + // fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { + // let mut paths = cx.prompt_for_paths(PathPromptOptions { + // files: false, + // directories: true, + // multiple: true, + // }); + // cx.spawn(|this, mut cx| async move { + // if let Some(paths) = paths.recv().await.flatten() { + // let results = this + // .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? + // .await; + // for result in results.into_iter().flatten() { + // result.log_err(); + // } + // } + // anyhow::Ok(()) + // }) + // .detach_and_log_err(cx); + // } + + fn project_path_for_path( + project: Handle, + abs_path: &Path, + visible: bool, + cx: &mut AppContext, + ) -> Task, ProjectPath)>> { + let entry = project.update(cx, |project, cx| { + project.find_or_create_local_worktree(abs_path, visible, cx) + }); + cx.spawn(|cx| async move { + let (worktree, path) = entry.await?; + let worktree_id = worktree.update(&mut cx, |t, _| t.id())?; + Ok(( + worktree, + ProjectPath { + worktree_id, + path: path.into(), + }, + )) + }) + } + + // /// Returns the modal that was toggled closed if it was open. + // pub fn toggle_modal( + // &mut self, + // cx: &mut ViewContext, + // add_view: F, + // ) -> Option> + // where + // V: 'static + Modal, + // F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, + // { + // cx.notify(); + // // Whatever modal was visible is getting clobbered. If its the same type as V, then return + // // it. Otherwise, create a new modal and set it as active. + // if let Some(already_open_modal) = self + // .dismiss_modal(cx) + // .and_then(|modal| modal.downcast::()) + // { + // cx.focus_self(); + // Some(already_open_modal) + // } else { + // let modal = add_view(self, cx); + // cx.subscribe(&modal, |this, _, event, cx| { + // if V::dismiss_on_event(event) { + // this.dismiss_modal(cx); + // } + // }) + // .detach(); + // let previously_focused_view_id = cx.focused_view_id(); + // cx.focus(&modal); + // self.modal = Some(ActiveModal { + // view: Box::new(modal), + // previously_focused_view_id, + // }); + // None + // } + // } + + // pub fn modal(&self) -> Option> { + // self.modal + // .as_ref() + // .and_then(|modal| modal.view.as_any().clone().downcast::()) + // } + + // pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { + // if let Some(modal) = self.modal.take() { + // if let Some(previously_focused_view_id) = modal.previously_focused_view_id { + // if modal.view.has_focus(cx) { + // cx.window_context().focus(Some(previously_focused_view_id)); + // } + // } + // cx.notify(); + // Some(modal.view.as_any().clone()) + // } else { + // None + // } + // } + + // pub fn items<'a>( + // &'a self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.panes.iter().flat_map(|pane| pane.read(cx).items()) + // } + + // pub fn item_of_type(&self, cx: &AppContext) -> Option> { + // self.items_of_type(cx).max_by_key(|item| item.id()) + // } + + // pub fn items_of_type<'a, T: Item>( + // &'a self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.panes + // .iter() + // .flat_map(|pane| pane.read(cx).items_of_type()) + // } + + // pub fn active_item(&self, cx: &AppContext) -> Option> { + // self.active_pane().read(cx).active_item() + // } + + // fn active_project_path(&self, cx: &ViewContext) -> Option { + // self.active_item(cx).and_then(|item| item.project_path(cx)) + // } + + // pub fn save_active_item( + // &mut self, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // let project = self.project.clone(); + // let pane = self.active_pane(); + // let item_ix = pane.read(cx).active_item_index(); + // let item = pane.read(cx).active_item(); + // let pane = pane.downgrade(); + + // cx.spawn(|_, mut cx| async move { + // if let Some(item) = item { + // Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx) + // .await + // .map(|_| ()) + // } else { + // Ok(()) + // } + // }) + // } + + // pub fn close_inactive_items_and_panes( + // &mut self, + // _: &CloseInactiveTabsAndPanes, + // cx: &mut ViewContext, + // ) -> Option>> { + // self.close_all_internal(true, SaveIntent::Close, cx) + // } + + // pub fn close_all_items_and_panes( + // &mut self, + // action: &CloseAllItemsAndPanes, + // cx: &mut ViewContext, + // ) -> Option>> { + // self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) + // } + + // fn close_all_internal( + // &mut self, + // retain_active_pane: bool, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Option>> { + // let current_pane = self.active_pane(); + + // let mut tasks = Vec::new(); + + // if retain_active_pane { + // if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| { + // pane.close_inactive_items(&CloseInactiveItems, cx) + // }) { + // tasks.push(current_pane_close); + // }; + // } + + // for pane in self.panes() { + // if retain_active_pane && pane.id() == current_pane.id() { + // continue; + // } + + // if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| { + // pane.close_all_items( + // &CloseAllItems { + // save_intent: Some(save_intent), + // }, + // cx, + // ) + // }) { + // tasks.push(close_pane_items) + // } + // } + + // if tasks.is_empty() { + // None + // } else { + // Some(cx.spawn(|_, _| async move { + // for task in tasks { + // task.await? + // } + // Ok(()) + // })) + // } + // } + + // pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { + // let dock = match dock_side { + // DockPosition::Left => &self.left_dock, + // DockPosition::Bottom => &self.bottom_dock, + // DockPosition::Right => &self.right_dock, + // }; + // let mut focus_center = false; + // let mut reveal_dock = false; + // dock.update(cx, |dock, cx| { + // let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); + // let was_visible = dock.is_open() && !other_is_zoomed; + // dock.set_open(!was_visible, cx); + + // if let Some(active_panel) = dock.active_panel() { + // if was_visible { + // if active_panel.has_focus(cx) { + // focus_center = true; + // } + // } else { + // cx.focus(active_panel.as_any()); + // reveal_dock = true; + // } + // } + // }); + + // if reveal_dock { + // self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); + // } + + // if focus_center { + // cx.focus_self(); + // } + + // cx.notify(); + // self.serialize_workspace(cx); + // } + + // pub fn close_all_docks(&mut self, cx: &mut ViewContext) { + // let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + + // for dock in docks { + // dock.update(cx, |dock, cx| { + // dock.set_open(false, cx); + // }); + // } + + // cx.focus_self(); + // cx.notify(); + // self.serialize_workspace(cx); + // } + + // /// Transfer focus to the panel of the given type. + // pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { + // self.focus_or_unfocus_panel::(cx, |_, _| true)? + // .as_any() + // .clone() + // .downcast() + // } + + // /// Focus the panel of the given type if it isn't already focused. If it is + // /// already focused, then transfer focus back to the workspace center. + // pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { + // self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); + // } + + // /// Focus or unfocus the given panel type, depending on the given callback. + // fn focus_or_unfocus_panel( + // &mut self, + // cx: &mut ViewContext, + // should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, + // ) -> Option> { + // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + // if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { + // let mut focus_center = false; + // let mut reveal_dock = false; + // let panel = dock.update(cx, |dock, cx| { + // dock.activate_panel(panel_index, cx); + + // let panel = dock.active_panel().cloned(); + // if let Some(panel) = panel.as_ref() { + // if should_focus(&**panel, cx) { + // dock.set_open(true, cx); + // cx.focus(panel.as_any()); + // reveal_dock = true; + // } else { + // // if panel.is_zoomed(cx) { + // // dock.set_open(false, cx); + // // } + // focus_center = true; + // } + // } + // panel + // }); + + // if focus_center { + // cx.focus_self(); + // } + + // self.serialize_workspace(cx); + // cx.notify(); + // return panel; + // } + // } + // None + // } + + // pub fn panel(&self, cx: &WindowContext) -> Option> { + // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + // let dock = dock.read(cx); + // if let Some(panel) = dock.panel::() { + // return Some(panel); + // } + // } + // None + // } + + // fn zoom_out(&mut self, cx: &mut ViewContext) { + // for pane in &self.panes { + // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + // } + + // self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + // self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + // self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + // self.zoomed = None; + // self.zoomed_position = None; + + // cx.notify(); + // } + + // #[cfg(any(test, feature = "test-support"))] + // pub fn zoomed_view(&self, cx: &AppContext) -> Option { + // self.zoomed.and_then(|view| view.upgrade(cx)) + // } + + // fn dismiss_zoomed_items_to_reveal( + // &mut self, + // dock_to_reveal: Option, + // cx: &mut ViewContext, + // ) { + // // If a center pane is zoomed, unzoom it. + // for pane in &self.panes { + // if pane != &self.active_pane || dock_to_reveal.is_some() { + // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + // } + // } + + // // If another dock is zoomed, hide it. + // let mut focus_center = false; + // for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { + // dock.update(cx, |dock, cx| { + // if Some(dock.position()) != dock_to_reveal { + // if let Some(panel) = dock.active_panel() { + // if panel.is_zoomed(cx) { + // focus_center |= panel.has_focus(cx); + // dock.set_open(false, cx); + // } + // } + // } + // }); + // } + + // if focus_center { + // cx.focus_self(); + // } + + // if self.zoomed_position != dock_to_reveal { + // self.zoomed = None; + // self.zoomed_position = None; + // } + + // cx.notify(); + // } + + // fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { + // let pane = cx.add_view(|cx| { + // Pane::new( + // self.weak_handle(), + // self.project.clone(), + // self.pane_history_timestamp.clone(), + // cx, + // ) + // }); + // cx.subscribe(&pane, Self::handle_pane_event).detach(); + // self.panes.push(pane.clone()); + // cx.focus(&pane); + // cx.emit(Event::PaneAdded(pane.clone())); + // pane + // } + + // pub fn add_item_to_center( + // &mut self, + // item: Box, + // cx: &mut ViewContext, + // ) -> bool { + // if let Some(center_pane) = self.last_active_center_pane.clone() { + // if let Some(center_pane) = center_pane.upgrade(cx) { + // center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); + // true + // } else { + // false + // } + // } else { + // false + // } + // } + + // pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { + // self.active_pane + // .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); + // } + + // pub fn split_item( + // &mut self, + // split_direction: SplitDirection, + // item: Box, + // cx: &mut ViewContext, + // ) { + // let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); + // new_pane.update(cx, move |new_pane, cx| { + // new_pane.add_item(item, true, true, None, cx) + // }) + // } + + // pub fn open_abs_path( + // &mut self, + // abs_path: PathBuf, + // visible: bool, + // cx: &mut ViewContext, + // ) -> Task>> { + // cx.spawn(|workspace, mut cx| async move { + // let open_paths_task_result = workspace + // .update(&mut cx, |workspace, cx| { + // workspace.open_paths(vec![abs_path.clone()], visible, cx) + // }) + // .with_context(|| format!("open abs path {abs_path:?} task spawn"))? + // .await; + // anyhow::ensure!( + // open_paths_task_result.len() == 1, + // "open abs path {abs_path:?} task returned incorrect number of results" + // ); + // match open_paths_task_result + // .into_iter() + // .next() + // .expect("ensured single task result") + // { + // Some(open_result) => { + // open_result.with_context(|| format!("open abs path {abs_path:?} task join")) + // } + // None => anyhow::bail!("open abs path {abs_path:?} task returned None"), + // } + // }) + // } + + // pub fn split_abs_path( + // &mut self, + // abs_path: PathBuf, + // visible: bool, + // cx: &mut ViewContext, + // ) -> Task>> { + // let project_path_task = + // Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); + // cx.spawn(|this, mut cx| async move { + // let (_, path) = project_path_task.await?; + // this.update(&mut cx, |this, cx| this.split_path(path, cx))? + // .await + // }) + // } + + pub fn open_path( + &mut self, + path: impl Into, + pane: Option>, + focus_item: bool, + cx: &mut ViewContext, + ) -> Task, anyhow::Error>> { + let pane = pane.unwrap_or_else(|| { + self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }) + }); + + let task = self.load_path(path.into(), cx); + cx.spawn(|_, mut cx| async move { + let (project_entry_id, build_item) = task.await?; + pane.update(&mut cx, |pane, cx| { + pane.open_item(project_entry_id, focus_item, cx, build_item) + }) + }) + } + + // pub fn split_path( + // &mut self, + // path: impl Into, + // cx: &mut ViewContext, + // ) -> Task, anyhow::Error>> { + // let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { + // self.panes + // .first() + // .expect("There must be an active pane") + // .downgrade() + // }); + + // if let Member::Pane(center_pane) = &self.center.root { + // if center_pane.read(cx).items_len() == 0 { + // return self.open_path(path, Some(pane), true, cx); + // } + // } + + // let task = self.load_path(path.into(), cx); + // cx.spawn(|this, mut cx| async move { + // let (project_entry_id, build_item) = task.await?; + // this.update(&mut cx, move |this, cx| -> Option<_> { + // let pane = pane.upgrade(cx)?; + // let new_pane = this.split_pane(pane, SplitDirection::Right, cx); + // new_pane.update(cx, |new_pane, cx| { + // Some(new_pane.open_item(project_entry_id, true, cx, build_item)) + // }) + // }) + // .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? + // }) + // } + + pub(crate) fn load_path( + &mut self, + path: ProjectPath, + cx: &mut ViewContext, + ) -> Task< + Result<( + ProjectEntryId, + impl 'static + FnOnce(&mut ViewContext) -> Box, + )>, + > { + let project = self.project().clone(); + let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); + cx.spawn(|_, mut cx| async move { + let (project_entry_id, project_item) = project_item.await?; + let build_item = cx.update(|cx| { + cx.default_global::() + .get(&project_item.model_type()) + .ok_or_else(|| anyhow!("no item builder for project item")) + .cloned() + })?; + let build_item = + move |cx: &mut ViewContext| build_item(project, project_item, cx); + Ok((project_entry_id, build_item)) + }) + } + + // pub fn open_project_item( + // &mut self, + // project_item: ModelHandle, + // cx: &mut ViewContext, + // ) -> ViewHandle + // where + // T: ProjectItem, + // { + // use project::Item as _; + + // let entry_id = project_item.read(cx).entry_id(cx); + // if let Some(item) = entry_id + // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + // .and_then(|item| item.downcast()) + // { + // self.activate_item(&item, cx); + // return item; + // } + + // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + // self.add_item(Box::new(item.clone()), cx); + // item + // } + + // pub fn split_project_item( + // &mut self, + // project_item: ModelHandle, + // cx: &mut ViewContext, + // ) -> ViewHandle + // where + // T: ProjectItem, + // { + // use project::Item as _; + + // let entry_id = project_item.read(cx).entry_id(cx); + // if let Some(item) = entry_id + // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + // .and_then(|item| item.downcast()) + // { + // self.activate_item(&item, cx); + // return item; + // } + + // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + // self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); + // item + // } + + // pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + // if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { + // self.active_pane.update(cx, |pane, cx| { + // pane.add_item(Box::new(shared_screen), false, true, None, cx) + // }); + // } + // } + + // pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { + // let result = self.panes.iter().find_map(|pane| { + // pane.read(cx) + // .index_for_item(item) + // .map(|ix| (pane.clone(), ix)) + // }); + // if let Some((pane, ix)) = result { + // pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); + // true + // } else { + // false + // } + // } + + // fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { + // let panes = self.center.panes(); + // if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { + // cx.focus(&pane); + // } else { + // self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); + // } + // } + + // pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { + // let panes = self.center.panes(); + // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + // let next_ix = (ix + 1) % panes.len(); + // let next_pane = panes[next_ix].clone(); + // cx.focus(&next_pane); + // } + // } + + // pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { + // let panes = self.center.panes(); + // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + // let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); + // let prev_pane = panes[prev_ix].clone(); + // cx.focus(&prev_pane); + // } + // } + + // pub fn activate_pane_in_direction( + // &mut self, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) { + // if let Some(pane) = self.find_pane_in_direction(direction, cx) { + // cx.focus(pane); + // } + // } + + // pub fn swap_pane_in_direction( + // &mut self, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) { + // if let Some(to) = self + // .find_pane_in_direction(direction, cx) + // .map(|pane| pane.clone()) + // { + // self.center.swap(&self.active_pane.clone(), &to); + // cx.notify(); + // } + // } + + // fn find_pane_in_direction( + // &mut self, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> Option<&ViewHandle> { + // let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { + // return None; + // }; + // let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); + // let center = match cursor { + // Some(cursor) if bounding_box.contains_point(cursor) => cursor, + // _ => bounding_box.center(), + // }; + + // let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; + + // let target = match direction { + // SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), + // SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), + // SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), + // SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), + // }; + // self.center.pane_at_pixel_position(target) + // } + + // fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { + // if self.active_pane != pane { + // self.active_pane = pane.clone(); + // self.status_bar.update(cx, |status_bar, cx| { + // status_bar.set_active_pane(&self.active_pane, cx); + // }); + // self.active_item_path_changed(cx); + // self.last_active_center_pane = Some(pane.downgrade()); + // } + + // self.dismiss_zoomed_items_to_reveal(None, cx); + // if pane.read(cx).is_zoomed() { + // self.zoomed = Some(pane.downgrade().into_any()); + // } else { + // self.zoomed = None; + // } + // self.zoomed_position = None; + // self.update_active_view_for_followers(cx); + + // cx.notify(); + // } + + // fn handle_pane_event( + // &mut self, + // pane: ViewHandle, + // event: &pane::Event, + // cx: &mut ViewContext, + // ) { + // match event { + // pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), + // pane::Event::Split(direction) => { + // self.split_and_clone(pane, *direction, cx); + // } + // pane::Event::Remove => self.remove_pane(pane, cx), + // pane::Event::ActivateItem { local } => { + // if *local { + // self.unfollow(&pane, cx); + // } + // if &pane == self.active_pane() { + // self.active_item_path_changed(cx); + // } + // } + // pane::Event::ChangeItemTitle => { + // if pane == self.active_pane { + // self.active_item_path_changed(cx); + // } + // self.update_window_edited(cx); + // } + // pane::Event::RemoveItem { item_id } => { + // self.update_window_edited(cx); + // if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { + // if entry.get().id() == pane.id() { + // entry.remove(); + // } + // } + // } + // pane::Event::Focus => { + // self.handle_pane_focused(pane.clone(), cx); + // } + // pane::Event::ZoomIn => { + // if pane == self.active_pane { + // pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + // if pane.read(cx).has_focus() { + // self.zoomed = Some(pane.downgrade().into_any()); + // self.zoomed_position = None; + // } + // cx.notify(); + // } + // } + // pane::Event::ZoomOut => { + // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + // if self.zoomed_position.is_none() { + // self.zoomed = None; + // } + // cx.notify(); + // } + // } + + // self.serialize_workspace(cx); + // } + + // pub fn split_pane( + // &mut self, + // pane_to_split: ViewHandle, + // split_direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> ViewHandle { + // let new_pane = self.add_pane(cx); + // self.center + // .split(&pane_to_split, &new_pane, split_direction) + // .unwrap(); + // cx.notify(); + // new_pane + // } + + // pub fn split_and_clone( + // &mut self, + // pane: ViewHandle, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> Option> { + // let item = pane.read(cx).active_item()?; + // let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { + // let new_pane = self.add_pane(cx); + // new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); + // self.center.split(&pane, &new_pane, direction).unwrap(); + // Some(new_pane) + // } else { + // None + // }; + // cx.notify(); + // maybe_pane_handle + // } + + // pub fn split_pane_with_item( + // &mut self, + // pane_to_split: WeakViewHandle, + // split_direction: SplitDirection, + // from: WeakViewHandle, + // item_id_to_move: usize, + // cx: &mut ViewContext, + // ) { + // let Some(pane_to_split) = pane_to_split.upgrade(cx) else { + // return; + // }; + // let Some(from) = from.upgrade(cx) else { + // return; + // }; + + // let new_pane = self.add_pane(cx); + // self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); + // self.center + // .split(&pane_to_split, &new_pane, split_direction) + // .unwrap(); + // cx.notify(); + // } + + // pub fn split_pane_with_project_entry( + // &mut self, + // pane_to_split: WeakViewHandle, + // split_direction: SplitDirection, + // project_entry: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> Option>> { + // let pane_to_split = pane_to_split.upgrade(cx)?; + // let new_pane = self.add_pane(cx); + // self.center + // .split(&pane_to_split, &new_pane, split_direction) + // .unwrap(); + + // let path = self.project.read(cx).path_for_entry(project_entry, cx)?; + // let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); + // Some(cx.foreground().spawn(async move { + // task.await?; + // Ok(()) + // })) + // } + + // pub fn move_item( + // &mut self, + // source: ViewHandle, + // destination: ViewHandle, + // item_id_to_move: usize, + // destination_index: usize, + // cx: &mut ViewContext, + // ) { + // let item_to_move = source + // .read(cx) + // .items() + // .enumerate() + // .find(|(_, item_handle)| item_handle.id() == item_id_to_move); + + // if item_to_move.is_none() { + // log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); + // return; + // } + // let (item_ix, item_handle) = item_to_move.unwrap(); + // let item_handle = item_handle.clone(); + + // if source != destination { + // // Close item from previous pane + // source.update(cx, |source, cx| { + // source.remove_item(item_ix, false, cx); + // }); + // } + + // // This automatically removes duplicate items in the pane + // destination.update(cx, |destination, cx| { + // destination.add_item(item_handle, true, true, Some(destination_index), cx); + // cx.focus_self(); + // }); + // } + + // fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { + // if self.center.remove(&pane).unwrap() { + // self.force_remove_pane(&pane, cx); + // self.unfollow(&pane, cx); + // self.last_leaders_by_pane.remove(&pane.downgrade()); + // for removed_item in pane.read(cx).items() { + // self.panes_by_item.remove(&removed_item.id()); + // } + + // cx.notify(); + // } else { + // self.active_item_path_changed(cx); + // } + // } + + // pub fn panes(&self) -> &[ViewHandle] { + // &self.panes + // } + + // pub fn active_pane(&self) -> &ViewHandle { + // &self.active_pane + // } + + // fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + // self.follower_states.retain(|_, state| { + // if state.leader_id == peer_id { + // for item in state.items_by_leader_view_id.values() { + // item.set_leader_peer_id(None, cx); + // } + // false + // } else { + // true + // } + // }); + // cx.notify(); + // } + + // fn start_following( + // &mut self, + // leader_id: PeerId, + // cx: &mut ViewContext, + // ) -> Option>> { + // let pane = self.active_pane().clone(); + + // self.last_leaders_by_pane + // .insert(pane.downgrade(), leader_id); + // self.unfollow(&pane, cx); + // self.follower_states.insert( + // pane.clone(), + // FollowerState { + // leader_id, + // active_view_id: None, + // items_by_leader_view_id: Default::default(), + // }, + // ); + // cx.notify(); + + // let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + // let project_id = self.project.read(cx).remote_id(); + // let request = self.app_state.client.request(proto::Follow { + // room_id, + // project_id, + // leader_id: Some(leader_id), + // }); + + // Some(cx.spawn(|this, mut cx| async move { + // let response = request.await?; + // this.update(&mut cx, |this, _| { + // let state = this + // .follower_states + // .get_mut(&pane) + // .ok_or_else(|| anyhow!("following interrupted"))?; + // state.active_view_id = if let Some(active_view_id) = response.active_view_id { + // Some(ViewId::from_proto(active_view_id)?) + // } else { + // None + // }; + // Ok::<_, anyhow::Error>(()) + // })??; + // Self::add_views_from_leader( + // this.clone(), + // leader_id, + // vec![pane], + // response.views, + // &mut cx, + // ) + // .await?; + // this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?; + // Ok(()) + // })) + // } + + // pub fn follow_next_collaborator( + // &mut self, + // _: &FollowNextCollaborator, + // cx: &mut ViewContext, + // ) -> Option>> { + // let collaborators = self.project.read(cx).collaborators(); + // let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) { + // let mut collaborators = collaborators.keys().copied(); + // for peer_id in collaborators.by_ref() { + // if peer_id == leader_id { + // break; + // } + // } + // collaborators.next() + // } else if let Some(last_leader_id) = + // self.last_leaders_by_pane.get(&self.active_pane.downgrade()) + // { + // if collaborators.contains_key(last_leader_id) { + // Some(*last_leader_id) + // } else { + // None + // } + // } else { + // None + // }; + + // let pane = self.active_pane.clone(); + // let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next()) + // else { + // return None; + // }; + // if Some(leader_id) == self.unfollow(&pane, cx) { + // return None; + // } + // self.follow(leader_id, cx) + // } + + // pub fn follow( + // &mut self, + // leader_id: PeerId, + // cx: &mut ViewContext, + // ) -> Option>> { + // let room = ActiveCall::global(cx).read(cx).room()?.read(cx); + // let project = self.project.read(cx); + + // let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else { + // return None; + // }; + + // let other_project_id = match remote_participant.location { + // call::ParticipantLocation::External => None, + // call::ParticipantLocation::UnsharedProject => None, + // call::ParticipantLocation::SharedProject { project_id } => { + // if Some(project_id) == project.remote_id() { + // None + // } else { + // Some(project_id) + // } + // } + // }; + + // // if they are active in another project, follow there. + // if let Some(project_id) = other_project_id { + // let app_state = self.app_state.clone(); + // return Some(crate::join_remote_project( + // project_id, + // remote_participant.user.id, + // app_state, + // cx, + // )); + // } + + // // if you're already following, find the right pane and focus it. + // for (pane, state) in &self.follower_states { + // if leader_id == state.leader_id { + // cx.focus(pane); + // return None; + // } + // } + + // // Otherwise, follow. + // self.start_following(leader_id, cx) + // } + + // pub fn unfollow( + // &mut self, + // pane: &ViewHandle, + // cx: &mut ViewContext, + // ) -> Option { + // let state = self.follower_states.remove(pane)?; + // let leader_id = state.leader_id; + // for (_, item) in state.items_by_leader_view_id { + // item.set_leader_peer_id(None, cx); + // } + + // if self + // .follower_states + // .values() + // .all(|state| state.leader_id != state.leader_id) + // { + // let project_id = self.project.read(cx).remote_id(); + // let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + // self.app_state + // .client + // .send(proto::Unfollow { + // room_id, + // project_id, + // leader_id: Some(leader_id), + // }) + // .log_err(); + // } + + // cx.notify(); + // Some(leader_id) + // } + + // pub fn is_being_followed(&self, peer_id: PeerId) -> bool { + // self.follower_states + // .values() + // .any(|state| state.leader_id == peer_id) + // } + + // fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { + // // TODO: There should be a better system in place for this + // // (https://github.com/zed-industries/zed/issues/1290) + // let is_fullscreen = cx.window_is_fullscreen(); + // let container_theme = if is_fullscreen { + // let mut container_theme = theme.titlebar.container; + // container_theme.padding.left = container_theme.padding.right; + // container_theme + // } else { + // theme.titlebar.container + // }; + + // enum TitleBar {} + // MouseEventHandler::new::(0, cx, |_, cx| { + // Stack::new() + // .with_children( + // self.titlebar_item + // .as_ref() + // .map(|item| ChildView::new(item, cx)), + // ) + // .contained() + // .with_style(container_theme) + // }) + // .on_click(MouseButton::Left, |event, _, cx| { + // if event.click_count == 2 { + // cx.zoom_window(); + // } + // }) + // .constrained() + // .with_height(theme.titlebar.height) + // .into_any_named("titlebar") + // } + + // fn active_item_path_changed(&mut self, cx: &mut ViewContext) { + // let active_entry = self.active_project_path(cx); + // self.project + // .update(cx, |project, cx| project.set_active_path(active_entry, cx)); + // self.update_window_title(cx); + // } + + // fn update_window_title(&mut self, cx: &mut ViewContext) { + // let project = self.project().read(cx); + // let mut title = String::new(); + + // if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { + // let filename = path + // .path + // .file_name() + // .map(|s| s.to_string_lossy()) + // .or_else(|| { + // Some(Cow::Borrowed( + // project + // .worktree_for_id(path.worktree_id, cx)? + // .read(cx) + // .root_name(), + // )) + // }); + + // if let Some(filename) = filename { + // title.push_str(filename.as_ref()); + // title.push_str(" — "); + // } + // } + + // for (i, name) in project.worktree_root_names(cx).enumerate() { + // if i > 0 { + // title.push_str(", "); + // } + // title.push_str(name); + // } + + // if title.is_empty() { + // title = "empty project".to_string(); + // } + + // if project.is_remote() { + // title.push_str(" ↙"); + // } else if project.is_shared() { + // title.push_str(" ↗"); + // } + + // cx.set_window_title(&title); + // } + + // fn update_window_edited(&mut self, cx: &mut ViewContext) { + // let is_edited = !self.project.read(cx).is_read_only() + // && self + // .items(cx) + // .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); + // if is_edited != self.window_edited { + // self.window_edited = is_edited; + // cx.set_window_edited(self.window_edited) + // } + // } + + // fn render_disconnected_overlay( + // &self, + // cx: &mut ViewContext, + // ) -> Option> { + // if self.project.read(cx).is_read_only() { + // enum DisconnectedOverlay {} + // Some( + // MouseEventHandler::new::(0, cx, |_, cx| { + // let theme = &theme::current(cx); + // Label::new( + // "Your connection to the remote project has been lost.", + // theme.workspace.disconnected_overlay.text.clone(), + // ) + // .aligned() + // .contained() + // .with_style(theme.workspace.disconnected_overlay.container) + // }) + // .with_cursor_style(CursorStyle::Arrow) + // .capture_all() + // .into_any_named("disconnected overlay"), + // ) + // } else { + // None + // } + // } + + // fn render_notifications( + // &self, + // theme: &theme::Workspace, + // cx: &AppContext, + // ) -> Option> { + // if self.notifications.is_empty() { + // None + // } else { + // Some( + // Flex::column() + // .with_children(self.notifications.iter().map(|(_, _, notification)| { + // ChildView::new(notification.as_any(), cx) + // .contained() + // .with_style(theme.notification) + // })) + // .constrained() + // .with_width(theme.notifications.width) + // .contained() + // .with_style(theme.notifications.container) + // .aligned() + // .bottom() + // .right() + // .into_any(), + // ) + // } + // } + + // // RPC handlers + + // fn handle_follow( + // &mut self, + // follower_project_id: Option, + // cx: &mut ViewContext, + // ) -> proto::FollowResponse { + // let client = &self.app_state.client; + // let project_id = self.project.read(cx).remote_id(); + + // let active_view_id = self.active_item(cx).and_then(|i| { + // Some( + // i.to_followable_item_handle(cx)? + // .remote_id(client, cx)? + // .to_proto(), + // ) + // }); + + // cx.notify(); + + // self.last_active_view_id = active_view_id.clone(); + // proto::FollowResponse { + // active_view_id, + // views: self + // .panes() + // .iter() + // .flat_map(|pane| { + // let leader_id = self.leader_for_pane(pane); + // pane.read(cx).items().filter_map({ + // let cx = &cx; + // move |item| { + // let item = item.to_followable_item_handle(cx)?; + // if (project_id.is_none() || project_id != follower_project_id) + // && item.is_project_item(cx) + // { + // return None; + // } + // let id = item.remote_id(client, cx)?.to_proto(); + // let variant = item.to_state_proto(cx)?; + // Some(proto::View { + // id: Some(id), + // leader_id, + // variant: Some(variant), + // }) + // } + // }) + // }) + // .collect(), + // } + // } + + // fn handle_update_followers( + // &mut self, + // leader_id: PeerId, + // message: proto::UpdateFollowers, + // _cx: &mut ViewContext, + // ) { + // self.leader_updates_tx + // .unbounded_send((leader_id, message)) + // .ok(); + // } + + // async fn process_leader_update( + // this: &WeakViewHandle, + // leader_id: PeerId, + // update: proto::UpdateFollowers, + // cx: &mut AsyncAppContext, + // ) -> Result<()> { + // match update.variant.ok_or_else(|| anyhow!("invalid update"))? { + // proto::update_followers::Variant::UpdateActiveView(update_active_view) => { + // this.update(cx, |this, _| { + // for (_, state) in &mut this.follower_states { + // if state.leader_id == leader_id { + // state.active_view_id = + // if let Some(active_view_id) = update_active_view.id.clone() { + // Some(ViewId::from_proto(active_view_id)?) + // } else { + // None + // }; + // } + // } + // anyhow::Ok(()) + // })??; + // } + // proto::update_followers::Variant::UpdateView(update_view) => { + // let variant = update_view + // .variant + // .ok_or_else(|| anyhow!("missing update view variant"))?; + // let id = update_view + // .id + // .ok_or_else(|| anyhow!("missing update view id"))?; + // let mut tasks = Vec::new(); + // this.update(cx, |this, cx| { + // let project = this.project.clone(); + // for (_, state) in &mut this.follower_states { + // if state.leader_id == leader_id { + // let view_id = ViewId::from_proto(id.clone())?; + // if let Some(item) = state.items_by_leader_view_id.get(&view_id) { + // tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); + // } + // } + // } + // anyhow::Ok(()) + // })??; + // try_join_all(tasks).await.log_err(); + // } + // proto::update_followers::Variant::CreateView(view) => { + // let panes = this.read_with(cx, |this, _| { + // this.follower_states + // .iter() + // .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) + // .cloned() + // .collect() + // })?; + // Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?; + // } + // } + // this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?; + // Ok(()) + // } + + // async fn add_views_from_leader( + // this: WeakViewHandle, + // leader_id: PeerId, + // panes: Vec>, + // views: Vec, + // cx: &mut AsyncAppContext, + // ) -> Result<()> { + // let this = this + // .upgrade(cx) + // .ok_or_else(|| anyhow!("workspace dropped"))?; + + // let item_builders = cx.update(|cx| { + // cx.default_global::() + // .values() + // .map(|b| b.0) + // .collect::>() + // }); + + // let mut item_tasks_by_pane = HashMap::default(); + // for pane in panes { + // let mut item_tasks = Vec::new(); + // let mut leader_view_ids = Vec::new(); + // for view in &views { + // let Some(id) = &view.id else { continue }; + // let id = ViewId::from_proto(id.clone())?; + // let mut variant = view.variant.clone(); + // if variant.is_none() { + // Err(anyhow!("missing view variant"))?; + // } + // for build_item in &item_builders { + // let task = cx + // .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); + // if let Some(task) = task { + // item_tasks.push(task); + // leader_view_ids.push(id); + // break; + // } else { + // assert!(variant.is_some()); + // } + // } + // } + + // item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids)); + // } + + // for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { + // let items = futures::future::try_join_all(item_tasks).await?; + // this.update(cx, |this, cx| { + // let state = this.follower_states.get_mut(&pane)?; + // for (id, item) in leader_view_ids.into_iter().zip(items) { + // item.set_leader_peer_id(Some(leader_id), cx); + // state.items_by_leader_view_id.insert(id, item); + // } + + // Some(()) + // }); + // } + // Ok(()) + // } + + // fn update_active_view_for_followers(&mut self, cx: &AppContext) { + // let mut is_project_item = true; + // let mut update = proto::UpdateActiveView::default(); + // if self.active_pane.read(cx).has_focus() { + // let item = self + // .active_item(cx) + // .and_then(|item| item.to_followable_item_handle(cx)); + // if let Some(item) = item { + // is_project_item = item.is_project_item(cx); + // update = proto::UpdateActiveView { + // id: item + // .remote_id(&self.app_state.client, cx) + // .map(|id| id.to_proto()), + // leader_id: self.leader_for_pane(&self.active_pane), + // }; + // } + // } + + // if update.id != self.last_active_view_id { + // self.last_active_view_id = update.id.clone(); + // self.update_followers( + // is_project_item, + // proto::update_followers::Variant::UpdateActiveView(update), + // cx, + // ); + // } + // } + + // fn update_followers( + // &self, + // project_only: bool, + // update: proto::update_followers::Variant, + // cx: &AppContext, + // ) -> Option<()> { + // let project_id = if project_only { + // self.project.read(cx).remote_id() + // } else { + // None + // }; + // self.app_state().workspace_store.read_with(cx, |store, cx| { + // store.update_followers(project_id, update, cx) + // }) + // } + + // pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { + // self.follower_states.get(pane).map(|state| state.leader_id) + // } + + // fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + // cx.notify(); + + // let call = self.active_call()?; + // let room = call.read(cx).room()?.read(cx); + // let participant = room.remote_participant_for_peer_id(leader_id)?; + // let mut items_to_activate = Vec::new(); + + // let leader_in_this_app; + // let leader_in_this_project; + // match participant.location { + // call::ParticipantLocation::SharedProject { project_id } => { + // leader_in_this_app = true; + // leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); + // } + // call::ParticipantLocation::UnsharedProject => { + // leader_in_this_app = true; + // leader_in_this_project = false; + // } + // call::ParticipantLocation::External => { + // leader_in_this_app = false; + // leader_in_this_project = false; + // } + // }; + + // for (pane, state) in &self.follower_states { + // if state.leader_id != leader_id { + // continue; + // } + // if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + // if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + // if leader_in_this_project || !item.is_project_item(cx) { + // items_to_activate.push((pane.clone(), item.boxed_clone())); + // } + // } else { + // log::warn!( + // "unknown view id {:?} for leader {:?}", + // active_view_id, + // leader_id + // ); + // } + // continue; + // } + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + // } + + // for (pane, item) in items_to_activate { + // let pane_was_focused = pane.read(cx).has_focus(); + // if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + // pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + // } else { + // pane.update(cx, |pane, cx| { + // pane.add_item(item.boxed_clone(), false, false, None, cx) + // }); + // } + + // if pane_was_focused { + // pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + // } + // } + + // None + // } + + // fn shared_screen_for_peer( + // &self, + // peer_id: PeerId, + // pane: &ViewHandle, + // cx: &mut ViewContext, + // ) -> Option> { + // let call = self.active_call()?; + // let room = call.read(cx).room()?.read(cx); + // let participant = room.remote_participant_for_peer_id(peer_id)?; + // let track = participant.video_tracks.values().next()?.clone(); + // let user = participant.user.clone(); + + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Some(item); + // } + // } + + // Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + // } + + // pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { + // if active { + // self.update_active_view_for_followers(cx); + // cx.background() + // .spawn(persistence::DB.update_timestamp(self.database_id())) + // .detach(); + // } else { + // for pane in &self.panes { + // pane.update(cx, |pane, cx| { + // if let Some(item) = pane.active_item() { + // item.workspace_deactivated(cx); + // } + // if matches!( + // settings::get::(cx).autosave, + // AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange + // ) { + // for item in pane.items() { + // Pane::autosave_item(item.as_ref(), self.project.clone(), cx) + // .detach_and_log_err(cx); + // } + // } + // }); + // } + // } + // } + + // fn active_call(&self) -> Option<&ModelHandle> { + // self.active_call.as_ref().map(|(call, _)| call) + // } + + // fn on_active_call_event( + // &mut self, + // _: ModelHandle, + // event: &call::room::Event, + // cx: &mut ViewContext, + // ) { + // match event { + // call::room::Event::ParticipantLocationChanged { participant_id } + // | call::room::Event::RemoteVideoTracksChanged { participant_id } => { + // self.leader_updated(*participant_id, cx); + // } + // _ => {} + // } + // } + + // pub fn database_id(&self) -> WorkspaceId { + // self.database_id + // } + + // fn location(&self, cx: &AppContext) -> Option { + // let project = self.project().read(cx); + + // if project.is_local() { + // Some( + // project + // .visible_worktrees(cx) + // .map(|worktree| worktree.read(cx).abs_path()) + // .collect::>() + // .into(), + // ) + // } else { + // None + // } + // } + + // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { + // match member { + // Member::Axis(PaneAxis { members, .. }) => { + // for child in members.iter() { + // self.remove_panes(child.clone(), cx) + // } + // } + // Member::Pane(pane) => { + // self.force_remove_pane(&pane, cx); + // } + // } + // } + + // fn force_remove_pane(&mut self, pane: &ViewHandle, cx: &mut ViewContext) { + // self.panes.retain(|p| p != pane); + // cx.focus(self.panes.last().unwrap()); + // if self.last_active_center_pane == Some(pane.downgrade()) { + // self.last_active_center_pane = None; + // } + // cx.notify(); + // } + + // fn schedule_serialize(&mut self, cx: &mut ViewContext) { + // self._schedule_serialize = Some(cx.spawn(|this, cx| async move { + // cx.background().timer(Duration::from_millis(100)).await; + // this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) + // .ok(); + // })); + // } + + // fn serialize_workspace(&self, cx: &ViewContext) { + // fn serialize_pane_handle( + // pane_handle: &ViewHandle, + // cx: &AppContext, + // ) -> SerializedPane { + // let (items, active) = { + // let pane = pane_handle.read(cx); + // let active_item_id = pane.active_item().map(|item| item.id()); + // ( + // pane.items() + // .filter_map(|item_handle| { + // Some(SerializedItem { + // kind: Arc::from(item_handle.serialized_item_kind()?), + // item_id: item_handle.id(), + // active: Some(item_handle.id()) == active_item_id, + // }) + // }) + // .collect::>(), + // pane.has_focus(), + // ) + // }; + + // SerializedPane::new(items, active) + // } + + // fn build_serialized_pane_group( + // pane_group: &Member, + // cx: &AppContext, + // ) -> SerializedPaneGroup { + // match pane_group { + // Member::Axis(PaneAxis { + // axis, + // members, + // flexes, + // bounding_boxes: _, + // }) => SerializedPaneGroup::Group { + // axis: *axis, + // children: members + // .iter() + // .map(|member| build_serialized_pane_group(member, cx)) + // .collect::>(), + // flexes: Some(flexes.borrow().clone()), + // }, + // Member::Pane(pane_handle) => { + // SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) + // } + // } + // } + + // fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { + // let left_dock = this.left_dock.read(cx); + // let left_visible = left_dock.is_open(); + // let left_active_panel = left_dock.visible_panel().and_then(|panel| { + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) + // }); + // let left_dock_zoom = left_dock + // .visible_panel() + // .map(|panel| panel.is_zoomed(cx)) + // .unwrap_or(false); + + // let right_dock = this.right_dock.read(cx); + // let right_visible = right_dock.is_open(); + // let right_active_panel = right_dock.visible_panel().and_then(|panel| { + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) + // }); + // let right_dock_zoom = right_dock + // .visible_panel() + // .map(|panel| panel.is_zoomed(cx)) + // .unwrap_or(false); + + // let bottom_dock = this.bottom_dock.read(cx); + // let bottom_visible = bottom_dock.is_open(); + // let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) + // }); + // let bottom_dock_zoom = bottom_dock + // .visible_panel() + // .map(|panel| panel.is_zoomed(cx)) + // .unwrap_or(false); + + // DockStructure { + // left: DockData { + // visible: left_visible, + // active_panel: left_active_panel, + // zoom: left_dock_zoom, + // }, + // right: DockData { + // visible: right_visible, + // active_panel: right_active_panel, + // zoom: right_dock_zoom, + // }, + // bottom: DockData { + // visible: bottom_visible, + // active_panel: bottom_active_panel, + // zoom: bottom_dock_zoom, + // }, + // } + // } + + // if let Some(location) = self.location(cx) { + // // Load bearing special case: + // // - with_local_workspace() relies on this to not have other stuff open + // // when you open your log + // if !location.paths().is_empty() { + // let center_group = build_serialized_pane_group(&self.center.root, cx); + // let docks = build_serialized_docks(self, cx); + + // let serialized_workspace = SerializedWorkspace { + // id: self.database_id, + // location, + // center_group, + // bounds: Default::default(), + // display: Default::default(), + // docks, + // }; + + // cx.background() + // .spawn(persistence::DB.save_workspace(serialized_workspace)) + // .detach(); + // } + // } + // } + + // pub(crate) fn load_workspace( + // workspace: WeakViewHandle, + // serialized_workspace: SerializedWorkspace, + // paths_to_open: Vec>, + // cx: &mut AppContext, + // ) -> Task>>>> { + // cx.spawn(|mut cx| async move { + // let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { + // ( + // workspace.project().clone(), + // workspace.last_active_center_pane.clone(), + // ) + // })?; + + // let mut center_group = None; + // let mut center_items = None; + // // Traverse the splits tree and add to things + // if let Some((group, active_pane, items)) = serialized_workspace + // .center_group + // .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) + // .await + // { + // center_items = Some(items); + // center_group = Some((group, active_pane)) + // } + + // let mut items_by_project_path = cx.read(|cx| { + // center_items + // .unwrap_or_default() + // .into_iter() + // .filter_map(|item| { + // let item = item?; + // let project_path = item.project_path(cx)?; + // Some((project_path, item)) + // }) + // .collect::>() + // }); + + // let opened_items = paths_to_open + // .into_iter() + // .map(|path_to_open| { + // path_to_open + // .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) + // }) + // .collect::>(); + + // // Remove old panes from workspace panes list + // workspace.update(&mut cx, |workspace, cx| { + // if let Some((center_group, active_pane)) = center_group { + // workspace.remove_panes(workspace.center.root.clone(), cx); + + // // Swap workspace center group + // workspace.center = PaneGroup::with_root(center_group); + + // // Change the focus to the workspace first so that we retrigger focus in on the pane. + // cx.focus_self(); + + // if let Some(active_pane) = active_pane { + // cx.focus(&active_pane); + // } else { + // cx.focus(workspace.panes.last().unwrap()); + // } + // } else { + // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); + // if let Some(old_center_handle) = old_center_handle { + // cx.focus(&old_center_handle) + // } else { + // cx.focus_self() + // } + // } + + // let docks = serialized_workspace.docks; + // workspace.left_dock.update(cx, |dock, cx| { + // dock.set_open(docks.left.visible, cx); + // if let Some(active_panel) = docks.left.active_panel { + // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + // dock.activate_panel(ix, cx); + // } + // } + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); + // if docks.left.visible && docks.left.zoom { + // cx.focus_self() + // } + // }); + // // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something + // workspace.right_dock.update(cx, |dock, cx| { + // dock.set_open(docks.right.visible, cx); + // if let Some(active_panel) = docks.right.active_panel { + // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + // dock.activate_panel(ix, cx); + // } + // } + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); + + // if docks.right.visible && docks.right.zoom { + // cx.focus_self() + // } + // }); + // workspace.bottom_dock.update(cx, |dock, cx| { + // dock.set_open(docks.bottom.visible, cx); + // if let Some(active_panel) = docks.bottom.active_panel { + // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + // dock.activate_panel(ix, cx); + // } + // } + + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); + + // if docks.bottom.visible && docks.bottom.zoom { + // cx.focus_self() + // } + // }); + + // cx.notify(); + // })?; + + // // Serialize ourself to make sure our timestamps and any pane / item changes are replicated + // workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; + + // Ok(opened_items) + // }) + // } + + // #[cfg(any(test, feature = "test-support"))] + // pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { + // use node_runtime::FakeNodeRuntime; + + // let client = project.read(cx).client(); + // let user_store = project.read(cx).user_store(); + + // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); + // let app_state = Arc::new(AppState { + // languages: project.read(cx).languages().clone(), + // workspace_store, + // client, + // user_store, + // fs: project.read(cx).fs().clone(), + // build_window_options: |_, _, _| Default::default(), + // initialize_workspace: |_, _, _, _| Task::ready(Ok(())), + // node_runtime: FakeNodeRuntime::new(), + // }); + // Self::new(0, project, app_state, cx) + // } + + // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { + // let dock = match position { + // DockPosition::Left => &self.left_dock, + // DockPosition::Right => &self.right_dock, + // DockPosition::Bottom => &self.bottom_dock, + // }; + // let active_panel = dock.read(cx).visible_panel()?; + // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { + // dock.read(cx).render_placeholder(cx) + // } else { + // ChildView::new(dock, cx).into_any() + // }; + + // Some( + // element + // .constrained() + // .dynamically(move |constraint, _, cx| match position { + // DockPosition::Left | DockPosition::Right => SizeConstraint::new( + // Vector2F::new(20., constraint.min.y()), + // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), + // ), + // DockPosition::Bottom => SizeConstraint::new( + // Vector2F::new(constraint.min.x(), 20.), + // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), + // ), + // }) + // .into_any(), + // ) + // } + // } + + // fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { + // ZED_WINDOW_POSITION + // .zip(*ZED_WINDOW_SIZE) + // .map(|(position, size)| { + // WindowBounds::Fixed(RectF::new( + // cx.platform().screens()[0].bounds().origin() + position, + // size, + // )) + // }) + // } + + // async fn open_items( + // serialized_workspace: Option, + // workspace: &WeakViewHandle, + // mut project_paths_to_open: Vec<(PathBuf, Option)>, + // app_state: Arc, + // mut cx: AsyncAppContext, + // ) -> Result>>>> { + // let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); + + // if let Some(serialized_workspace) = serialized_workspace { + // let workspace = workspace.clone(); + // let restored_items = cx + // .update(|cx| { + // Workspace::load_workspace( + // workspace, + // serialized_workspace, + // project_paths_to_open + // .iter() + // .map(|(_, project_path)| project_path) + // .cloned() + // .collect(), + // cx, + // ) + // }) + // .await?; + + // let restored_project_paths = cx.read(|cx| { + // restored_items + // .iter() + // .filter_map(|item| item.as_ref()?.project_path(cx)) + // .collect::>() + // }); + + // for restored_item in restored_items { + // opened_items.push(restored_item.map(Ok)); + // } + + // project_paths_to_open + // .iter_mut() + // .for_each(|(_, project_path)| { + // if let Some(project_path_to_open) = project_path { + // if restored_project_paths.contains(project_path_to_open) { + // *project_path = None; + // } + // } + // }); + // } else { + // for _ in 0..project_paths_to_open.len() { + // opened_items.push(None); + // } + // } + // assert!(opened_items.len() == project_paths_to_open.len()); + + // let tasks = + // project_paths_to_open + // .into_iter() + // .enumerate() + // .map(|(i, (abs_path, project_path))| { + // let workspace = workspace.clone(); + // cx.spawn(|mut cx| { + // let fs = app_state.fs.clone(); + // async move { + // let file_project_path = project_path?; + // if fs.is_file(&abs_path).await { + // Some(( + // i, + // workspace + // .update(&mut cx, |workspace, cx| { + // workspace.open_path(file_project_path, None, true, cx) + // }) + // .log_err()? + // .await, + // )) + // } else { + // None + // } + // } + // }) + // }); + + // for maybe_opened_path in futures::future::join_all(tasks.into_iter()) + // .await + // .into_iter() + // { + // if let Some((i, path_open_result)) = maybe_opened_path { + // opened_items[i] = Some(path_open_result); + // } + // } + + // Ok(opened_items) + // } + + // fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { + // const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; + // const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; + // const MESSAGE_ID: usize = 2; + + // if workspace + // .read_with(cx, |workspace, cx| { + // workspace.has_shown_notification_once::(MESSAGE_ID, cx) + // }) + // .unwrap_or(false) + // { + // return; + // } + + // if db::kvp::KEY_VALUE_STORE + // .read_kvp(NEW_DOCK_HINT_KEY) + // .ok() + // .flatten() + // .is_some() + // { + // if !workspace + // .read_with(cx, |workspace, cx| { + // workspace.has_shown_notification_once::(MESSAGE_ID, cx) + // }) + // .unwrap_or(false) + // { + // cx.update(|cx| { + // cx.update_global::(|tracker, _| { + // let entry = tracker + // .entry(TypeId::of::()) + // .or_default(); + // if !entry.contains(&MESSAGE_ID) { + // entry.push(MESSAGE_ID); + // } + // }); + // }); + // } + + // return; + // } + + // cx.spawn(|_| async move { + // db::kvp::KEY_VALUE_STORE + // .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) + // .await + // .ok(); + // }) + // .detach(); + + // workspace + // .update(cx, |workspace, cx| { + // workspace.show_notification_once(2, cx, |cx| { + // cx.add_view(|_| { + // MessageNotification::new_element(|text, _| { + // Text::new( + // "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", + // text, + // ) + // .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { + // let code_span_background_color = settings::get::(cx) + // .theme + // .editor + // .document_highlight_read_background; + + // cx.scene().push_quad(gpui::Quad { + // bounds, + // background: Some(code_span_background_color), + // border: Default::default(), + // corner_radii: (2.0).into(), + // }) + // }) + // .into_any() + // }) + // .with_click_message("Read more about the new panel system") + // .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) + // }) + // }) + // }) + // .ok(); +} + // fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { // const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; @@ -4321,16 +4321,20 @@ pub async fn activate_workspace_for_project( // } use client2::{proto::PeerId, Client, UserStore}; -use collections::HashSet; +use collections::{HashMap, HashSet}; use gpui2::{ - AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, WeakHandle, WindowBounds, - WindowHandle, WindowOptions, + AnyHandle, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View, ViewContext, + WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions, }; -use item::ItemHandle; +use item::{ItemHandle, ProjectItem}; use language2::LanguageRegistry; use node_runtime::NodeRuntime; -use project2::Project; -use std::{path::PathBuf, sync::Arc}; +use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; +use std::{ + any::TypeId, + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; #[allow(clippy::type_complexity)] @@ -4341,7 +4345,7 @@ pub fn open_paths( cx: &mut AppContext, ) -> Task< anyhow::Result<( - WeakHandle, + WindowHandle, Vec, anyhow::Error>>>, )>, > { @@ -4357,18 +4361,18 @@ pub fn open_paths( if let Some(existing) = existing { Ok(( existing.clone(), - existing - .update(&mut cx, |workspace, cx| { - workspace.open_paths(abs_paths, true, cx) - })? - .await, + cx.update_window_root(&existing, |workspace, cx| { + workspace.open_paths(abs_paths, true, cx) + })? + .await, )) } else { - Ok(cx - .update(|cx| { - Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) - }) - .await) + todo!() + // Ok(cx + // .update(|cx| { + // Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) + // }) + // .await) } }) }