Detailed changes
@@ -3,7 +3,12 @@
// ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
// };
// use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
-// use anyhow::Result;
+use anyhow::Result;
+use client2::{
+ proto::{self, PeerId, ViewId},
+ Client,
+};
+use theme2::Theme;
// use client2::{
// proto::{self, PeerId},
// Client,
@@ -78,218 +83,227 @@
// }
// }
-// #[derive(Eq, PartialEq, Hash, Debug)]
-// pub enum ItemEvent {
-// CloseItem,
-// UpdateTab,
-// UpdateBreadcrumbs,
-// Edit,
-// }
-
-// // TODO: Combine this with existing HighlightedText struct?
-// pub struct BreadcrumbText {
-// pub text: String,
-// pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
-// }
-
-// pub trait Item: View {
-// fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
-// fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
-// fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-// false
-// }
-// fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
-// None
-// }
-// fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
-// None
-// }
-// fn tab_content<V: 'static>(
-// &self,
-// detail: Option<usize>,
-// style: &theme2::Tab,
-// cx: &AppContext,
-// ) -> AnyElement<V>;
-// fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) {
-// } // (model id, Item)
-// fn is_singleton(&self, _cx: &AppContext) -> bool {
-// false
-// }
-// fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
-// fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
-// where
-// Self: Sized,
-// {
-// None
-// }
-// fn is_dirty(&self, _: &AppContext) -> bool {
-// false
-// }
-// fn has_conflict(&self, _: &AppContext) -> bool {
-// false
-// }
-// fn can_save(&self, _cx: &AppContext) -> bool {
-// false
-// }
-// fn save(
-// &mut self,
-// _project: ModelHandle<Project>,
-// _cx: &mut ViewContext<Self>,
-// ) -> Task<Result<()>> {
-// unimplemented!("save() must be implemented if can_save() returns true")
-// }
-// fn save_as(
-// &mut self,
-// _project: ModelHandle<Project>,
-// _abs_path: PathBuf,
-// _cx: &mut ViewContext<Self>,
-// ) -> Task<Result<()>> {
-// unimplemented!("save_as() must be implemented if can_save() returns true")
-// }
-// fn reload(
-// &mut self,
-// _project: ModelHandle<Project>,
-// _cx: &mut ViewContext<Self>,
-// ) -> Task<Result<()>> {
-// unimplemented!("reload() must be implemented if can_save() returns true")
-// }
-// fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-// SmallVec::new()
-// }
-// fn should_close_item_on_event(_: &Self::Event) -> bool {
-// false
-// }
-// fn should_update_tab_on_event(_: &Self::Event) -> bool {
-// false
-// }
-
-// fn act_as_type<'a>(
-// &'a self,
-// type_id: TypeId,
-// self_handle: &'a ViewHandle<Self>,
-// _: &'a AppContext,
-// ) -> Option<&AnyViewHandle> {
-// if TypeId::of::<Self>() == type_id {
-// Some(self_handle)
-// } else {
-// None
-// }
-// }
-
-// fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-// None
-// }
-
-// fn breadcrumb_location(&self) -> ToolbarItemLocation {
-// ToolbarItemLocation::Hidden
-// }
-
-// fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-// None
-// }
-
-// fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
-
-// fn serialized_item_kind() -> Option<&'static str> {
-// None
-// }
-
-// fn deserialize(
-// _project: ModelHandle<Project>,
-// _workspace: WeakViewHandle<Workspace>,
-// _workspace_id: WorkspaceId,
-// _item_id: ItemId,
-// _cx: &mut ViewContext<Pane>,
-// ) -> Task<Result<ViewHandle<Self>>> {
-// unimplemented!(
-// "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
-// )
-// }
-// fn show_toolbar(&self) -> bool {
-// true
-// }
-// fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
-// None
-// }
-// }
+#[derive(Eq, PartialEq, Hash, Debug)]
+pub enum ItemEvent {
+ CloseItem,
+ UpdateTab,
+ UpdateBreadcrumbs,
+ Edit,
+}
-use core::fmt;
+// TODO: Combine this with existing HighlightedText struct?
+pub struct BreadcrumbText {
+ pub text: String,
+ pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
+}
-pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
- // fn subscribe_to_item_events(
- // &self,
- // cx: &mut WindowContext,
- // handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
- // ) -> gpui2::Subscription;
- // fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
- // fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
- // fn tab_content(
- // &self,
- // detail: Option<usize>,
- // style: &theme2::Tab,
- // cx: &AppContext,
- // ) -> AnyElement<Pane>;
- // fn dragged_tab_content(
+pub trait Item: EventEmitter {
+ // fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
+ // fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
+ // fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+ // false
+ // }
+ // fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+ // None
+ // }
+ // fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
+ // None
+ // }
+ // fn tab_content<V: 'static>(
// &self,
// detail: Option<usize>,
// style: &theme2::Tab,
// cx: &AppContext,
- // ) -> AnyElement<Workspace>;
- // fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
- // fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
- // fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
- // fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item));
- // fn is_singleton(&self, cx: &AppContext) -> bool;
- // fn boxed_clone(&self) -> Box<dyn ItemHandle>;
- // fn clone_on_split(
- // &self,
- // workspace_id: WorkspaceId,
- // cx: &mut WindowContext,
- // ) -> Option<Box<dyn ItemHandle>>;
- // fn added_to_pane(
- // &self,
- // workspace: &mut Workspace,
- // pane: ViewHandle<Pane>,
- // cx: &mut ViewContext<Workspace>,
- // );
- // fn deactivated(&self, cx: &mut WindowContext);
- // fn workspace_deactivated(&self, cx: &mut WindowContext);
- // fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
- // fn id(&self) -> usize;
- // fn window(&self) -> AnyWindowHandle;
- // fn as_any(&self) -> &AnyViewHandle;
- // fn is_dirty(&self, cx: &AppContext) -> bool;
- // fn has_conflict(&self, cx: &AppContext) -> bool;
- // fn can_save(&self, cx: &AppContext) -> bool;
- // fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+ // ) -> AnyElement<V>;
+ // fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) {
+ // } // (model id, Item)
+ fn is_singleton(&self, _cx: &AppContext) -> bool {
+ false
+ }
+ // fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
+ // fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
+ // where
+ // Self: Sized,
+ // {
+ // None
+ // }
+ // fn is_dirty(&self, _: &AppContext) -> bool {
+ // false
+ // }
+ // fn has_conflict(&self, _: &AppContext) -> bool {
+ // false
+ // }
+ // fn can_save(&self, _cx: &AppContext) -> bool {
+ // false
+ // }
+ // fn save(
+ // &mut self,
+ // _project: ModelHandle<Project>,
+ // _cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // unimplemented!("save() must be implemented if can_save() returns true")
+ // }
// fn save_as(
- // &self,
- // project: ModelHandle<Project>,
- // abs_path: PathBuf,
- // cx: &mut WindowContext,
- // ) -> Task<Result<()>>;
- // fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
- // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
- // fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
- // fn on_release(
- // &self,
- // cx: &mut AppContext,
- // callback: Box<dyn FnOnce(&mut AppContext)>,
- // ) -> gpui2::Subscription;
- // fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
- // fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
- // fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
- // fn serialized_item_kind(&self) -> Option<&'static str>;
- // fn show_toolbar(&self, cx: &AppContext) -> bool;
- // fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F>;
+ // &mut self,
+ // _project: ModelHandle<Project>,
+ // _abs_path: PathBuf,
+ // _cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // unimplemented!("save_as() must be implemented if can_save() returns true")
+ // }
+ // fn reload(
+ // &mut self,
+ // _project: ModelHandle<Project>,
+ // _cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // unimplemented!("reload() must be implemented if can_save() returns true")
+ // }
+ // fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+ // SmallVec::new()
+ // }
+ // fn should_close_item_on_event(_: &Self::Event) -> bool {
+ // false
+ // }
+ // fn should_update_tab_on_event(_: &Self::Event) -> bool {
+ // false
+ // }
+
+ // fn act_as_type<'a>(
+ // &'a self,
+ // type_id: TypeId,
+ // self_handle: &'a ViewHandle<Self>,
+ // _: &'a AppContext,
+ // ) -> Option<&AnyViewHandle> {
+ // if TypeId::of::<Self>() == type_id {
+ // Some(self_handle)
+ // } else {
+ // None
+ // }
+ // }
+
+ // fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+ // None
+ // }
+
+ // fn breadcrumb_location(&self) -> ToolbarItemLocation {
+ // ToolbarItemLocation::Hidden
+ // }
+
+ // fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+ // None
+ // }
+
+ // fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
+
+ // fn serialized_item_kind() -> Option<&'static str> {
+ // None
+ // }
+
+ // fn deserialize(
+ // _project: ModelHandle<Project>,
+ // _workspace: WeakViewHandle<Workspace>,
+ // _workspace_id: WorkspaceId,
+ // _item_id: ItemId,
+ // _cx: &mut ViewContext<Pane>,
+ // ) -> Task<Result<ViewHandle<Self>>> {
+ // unimplemented!(
+ // "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
+ // )
+ // }
+ // fn show_toolbar(&self) -> bool {
+ // true
+ // }
+ // fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
+ // None
+ // }
}
-// pub trait WeakItemHandle {
-// fn id(&self) -> usize;
-// fn window(&self) -> AnyWindowHandle;
-// fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
-// }
+use core::fmt;
+use std::{
+ any::{Any, TypeId},
+ borrow::Cow,
+ ops::Range,
+ path::PathBuf,
+ sync::Arc,
+};
+
+use gpui2::{
+ AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels,
+ Point, Task, View, ViewContext, WindowContext,
+};
+use project2::{Project, ProjectEntryId, ProjectPath};
+use smallvec::SmallVec;
+
+use crate::{
+ pane::Pane, searchable::SearchableItemHandle, ToolbarItemLocation, Workspace, WorkspaceId,
+};
+
+pub trait ItemHandle: 'static + fmt::Debug + Send {
+ fn subscribe_to_item_events(
+ &self,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
+ ) -> gpui2::Subscription;
+ fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
+ fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
+ fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane>;
+ fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace>;
+ fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
+ fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
+ fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
+ fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item));
+ fn is_singleton(&self, cx: &AppContext) -> bool;
+ fn boxed_clone(&self) -> Box<dyn ItemHandle>;
+ fn clone_on_split(
+ &self,
+ workspace_id: WorkspaceId,
+ cx: &mut WindowContext,
+ ) -> Option<Box<dyn ItemHandle>>;
+ fn added_to_pane(
+ &self,
+ workspace: &mut Workspace,
+ pane: View<Pane>,
+ cx: &mut ViewContext<Workspace>,
+ );
+ fn deactivated(&self, cx: &mut WindowContext);
+ fn workspace_deactivated(&self, cx: &mut WindowContext);
+ fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
+ fn id(&self) -> usize;
+ fn window(&self) -> AnyWindowHandle;
+ // fn as_any(&self) -> &AnyView; todo!()
+ fn is_dirty(&self, cx: &AppContext) -> bool;
+ fn has_conflict(&self, cx: &AppContext) -> bool;
+ fn can_save(&self, cx: &AppContext) -> bool;
+ fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+ fn save_as(
+ &self,
+ project: Handle<Project>,
+ abs_path: PathBuf,
+ cx: &mut WindowContext,
+ ) -> Task<Result<()>>;
+ fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+ // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!()
+ fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
+ fn on_release(
+ &self,
+ cx: &mut AppContext,
+ callback: Box<dyn FnOnce(&mut AppContext)>,
+ ) -> gpui2::Subscription;
+ fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
+ fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
+ fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
+ fn serialized_item_kind(&self) -> Option<&'static str>;
+ fn show_toolbar(&self, cx: &AppContext) -> bool;
+ fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
+}
+
+pub trait WeakItemHandle {
+ fn id(&self) -> usize;
+ fn window(&self) -> AnyWindowHandle;
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+}
+// todo!()
// impl dyn ItemHandle {
// pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
// self.as_any().clone().downcast()
@@ -653,11 +667,11 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
// }
// }
-// impl Clone for Box<dyn ItemHandle> {
-// fn clone(&self) -> Box<dyn ItemHandle> {
-// self.boxed_clone()
-// }
-// }
+impl Clone for Box<dyn ItemHandle> {
+ fn clone(&self) -> Box<dyn ItemHandle> {
+ self.boxed_clone()
+ }
+}
// impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
// fn id(&self) -> usize {
@@ -673,63 +687,65 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
// }
// }
-// pub trait ProjectItem: Item {
-// type Item: project2::Item + gpui2::Entity;
+pub trait ProjectItem: Item {
+ type Item: project2::Item;
-// fn for_project_item(
-// project: ModelHandle<Project>,
-// item: ModelHandle<Self::Item>,
-// cx: &mut ViewContext<Self>,
-// ) -> Self;
-// }
-
-// pub trait FollowableItem: Item {
-// fn remote_id(&self) -> Option<ViewId>;
-// fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
-// fn from_state_proto(
-// pane: ViewHandle<Pane>,
-// project: ViewHandle<Workspace>,
-// id: ViewId,
-// state: &mut Option<proto::view::Variant>,
-// cx: &mut AppContext,
-// ) -> Option<Task<Result<ViewHandle<Self>>>>;
-// fn add_event_to_update_proto(
-// &self,
-// event: &Self::Event,
-// update: &mut Option<proto::update_view::Variant>,
-// cx: &AppContext,
-// ) -> bool;
-// fn apply_update_proto(
-// &mut self,
-// project: &ModelHandle<Project>,
-// message: proto::update_view::Variant,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<()>>;
-// fn is_project_item(&self, cx: &AppContext) -> bool;
+ fn for_project_item(
+ project: Handle<Project>,
+ item: Handle<Self::Item>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self
+ where
+ Self: Sized;
+}
-// fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
-// fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
-// }
+pub trait FollowableItem: Item {
+ fn remote_id(&self) -> Option<ViewId>;
+ fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn from_state_proto(
+ pane: View<Pane>,
+ project: View<Workspace>,
+ id: ViewId,
+ state: &mut Option<proto::view::Variant>,
+ cx: &mut AppContext,
+ ) -> Option<Task<Result<View<Self>>>>;
+ fn add_event_to_update_proto(
+ &self,
+ event: &Self::Event,
+ update: &mut Option<proto::update_view::Variant>,
+ cx: &AppContext,
+ ) -> bool;
+ fn apply_update_proto(
+ &mut self,
+ project: &Handle<Project>,
+ message: proto::update_view::Variant,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>>;
+ fn is_project_item(&self, cx: &AppContext) -> bool;
+
+ fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
+ fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
+}
-// pub trait FollowableItemHandle: ItemHandle {
-// fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
-// fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
-// fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
-// fn add_event_to_update_proto(
-// &self,
-// event: &dyn Any,
-// update: &mut Option<proto::update_view::Variant>,
-// cx: &AppContext,
-// ) -> bool;
-// fn apply_update_proto(
-// &self,
-// project: &ModelHandle<Project>,
-// message: proto::update_view::Variant,
-// cx: &mut WindowContext,
-// ) -> Task<Result<()>>;
-// fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
-// fn is_project_item(&self, cx: &AppContext) -> bool;
-// }
+pub trait FollowableItemHandle: ItemHandle {
+ fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+ fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
+ fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn add_event_to_update_proto(
+ &self,
+ event: &dyn Any,
+ update: &mut Option<proto::update_view::Variant>,
+ cx: &AppContext,
+ ) -> bool;
+ fn apply_update_proto(
+ &self,
+ project: &Handle<Project>,
+ message: proto::update_view::Variant,
+ cx: &mut WindowContext,
+ ) -> Task<Result<()>>;
+ fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
+ fn is_project_item(&self, cx: &AppContext) -> bool;
+}
// impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
// fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
@@ -981,9 +997,9 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync {
// .for_each(|item| f(item.id(), item.read(cx)))
// }
-// fn is_singleton(&self, _: &AppContext) -> bool {
-// self.is_singleton
-// }
+// fn is_singleton(&self, _: &AppContext) -> bool {
+// self.is_singleton
+// }
// fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
// self.nav_history = Some(history);
@@ -1,150 +1,150 @@
-mod dragged_item_receiver;
-
-use super::{ItemHandle, SplitDirection};
-pub use crate::toolbar::Toolbar;
-use crate::{
- item::{ItemSettings, WeakItemHandle},
- notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
- Workspace, WorkspaceSettings,
-};
-use anyhow::Result;
-use collections::{HashMap, HashSet, VecDeque};
-// use context_menu::{ContextMenu, ContextMenuItem};
-
-use dragged_item_receiver::dragged_item_receiver;
-use fs2::repository::GitFileStatus;
-use futures::StreamExt;
-use gpui2::{
- actions,
- elements::*,
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- impl_actions,
- keymap_matcher::KeymapContext,
- platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
- Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
- ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
- WindowContext,
-};
-use project2::{Project, ProjectEntryId, ProjectPath};
-use serde::Deserialize;
-use std::{
- any::Any,
- cell::RefCell,
- cmp, mem,
- path::{Path, PathBuf},
- rc::Rc,
- sync::{
- atomic::{AtomicUsize, Ordering},
- Arc,
- },
-};
-use theme2::{Theme, ThemeSettings};
-use util::truncate_and_remove_front;
-
-#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub enum SaveIntent {
- /// write all files (even if unchanged)
- /// prompt before overwriting on-disk changes
- Save,
- /// write any files that have local changes
- /// prompt before overwriting on-disk changes
- SaveAll,
- /// always prompt for a new path
- SaveAs,
- /// prompt "you have unsaved changes" before writing
- Close,
- /// write all dirty files, don't prompt on conflict
- Overwrite,
- /// skip all save-related behavior
- Skip,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct ActivateItem(pub usize);
-
-#[derive(Clone, PartialEq)]
-pub struct CloseItemById {
- pub item_id: usize,
- pub pane: WeakViewHandle<Pane>,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct CloseItemsToTheLeftById {
- pub item_id: usize,
- pub pane: WeakViewHandle<Pane>,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct CloseItemsToTheRightById {
- pub item_id: usize,
- pub pane: WeakViewHandle<Pane>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseActiveItem {
- pub save_intent: Option<SaveIntent>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseAllItems {
- pub save_intent: Option<SaveIntent>,
-}
-
-actions!(
- pane,
- [
- ActivatePrevItem,
- ActivateNextItem,
- ActivateLastItem,
- CloseInactiveItems,
- CloseCleanItems,
- CloseItemsToTheLeft,
- CloseItemsToTheRight,
- GoBack,
- GoForward,
- ReopenClosedItem,
- SplitLeft,
- SplitUp,
- SplitRight,
- SplitDown,
- ]
-);
-
-impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
-
-const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(Pane::toggle_zoom);
- cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
- pane.activate_item(action.0, true, true, cx);
- });
- cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
- pane.activate_item(pane.items.len() - 1, true, true, cx);
- });
- cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
- pane.activate_prev_item(true, cx);
- });
- cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
- pane.activate_next_item(true, cx);
- });
- cx.add_async_action(Pane::close_active_item);
- cx.add_async_action(Pane::close_inactive_items);
- cx.add_async_action(Pane::close_clean_items);
- cx.add_async_action(Pane::close_items_to_the_left);
- cx.add_async_action(Pane::close_items_to_the_right);
- cx.add_async_action(Pane::close_all_items);
- cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
- cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
- cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
- cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
-}
+// mod dragged_item_receiver;
+
+// use super::{ItemHandle, SplitDirection};
+// pub use crate::toolbar::Toolbar;
+// use crate::{
+// item::{ItemSettings, WeakItemHandle},
+// notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
+// Workspace, WorkspaceSettings,
+// };
+// use anyhow::Result;
+// use collections::{HashMap, HashSet, VecDeque};
+// // use context_menu::{ContextMenu, ContextMenuItem};
+
+// use dragged_item_receiver::dragged_item_receiver;
+// use fs2::repository::GitFileStatus;
+// use futures::StreamExt;
+// use gpui2::{
+// actions,
+// elements::*,
+// geometry::{
+// rect::RectF,
+// vector::{vec2f, Vector2F},
+// },
+// impl_actions,
+// keymap_matcher::KeymapContext,
+// platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
+// Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
+// ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+// WindowContext,
+// };
+// use project2::{Project, ProjectEntryId, ProjectPath};
+// use serde::Deserialize;
+// use std::{
+// any::Any,
+// cell::RefCell,
+// cmp, mem,
+// path::{Path, PathBuf},
+// rc::Rc,
+// sync::{
+// atomic::{AtomicUsize, Ordering},
+// Arc,
+// },
+// };
+// use theme2::{Theme, ThemeSettings};
+// use util::truncate_and_remove_front;
+
+// #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
+// #[serde(rename_all = "camelCase")]
+// pub enum SaveIntent {
+// /// write all files (even if unchanged)
+// /// prompt before overwriting on-disk changes
+// Save,
+// /// write any files that have local changes
+// /// prompt before overwriting on-disk changes
+// SaveAll,
+// /// always prompt for a new path
+// SaveAs,
+// /// prompt "you have unsaved changes" before writing
+// Close,
+// /// write all dirty files, don't prompt on conflict
+// Overwrite,
+// /// skip all save-related behavior
+// Skip,
+// }
+
+// #[derive(Clone, Deserialize, PartialEq)]
+// pub struct ActivateItem(pub usize);
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemById {
+// pub item_id: usize,
+// pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheLeftById {
+// pub item_id: usize,
+// pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheRightById {
+// pub item_id: usize,
+// pub pane: WeakViewHandle<Pane>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseActiveItem {
+// pub save_intent: Option<SaveIntent>,
+// }
+
+// #[derive(Clone, PartialEq, Debug, Deserialize)]
+// #[serde(rename_all = "camelCase")]
+// pub struct CloseAllItems {
+// pub save_intent: Option<SaveIntent>,
+// }
+
+// actions!(
+// pane,
+// [
+// ActivatePrevItem,
+// ActivateNextItem,
+// ActivateLastItem,
+// CloseInactiveItems,
+// CloseCleanItems,
+// CloseItemsToTheLeft,
+// CloseItemsToTheRight,
+// GoBack,
+// GoForward,
+// ReopenClosedItem,
+// SplitLeft,
+// SplitUp,
+// SplitRight,
+// SplitDown,
+// ]
+// );
+
+// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
+
+// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
+
+// pub fn init(cx: &mut AppContext) {
+// cx.add_action(Pane::toggle_zoom);
+// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
+// pane.activate_item(action.0, true, true, cx);
+// });
+// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
+// pane.activate_item(pane.items.len() - 1, true, true, cx);
+// });
+// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
+// pane.activate_prev_item(true, cx);
+// });
+// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
+// pane.activate_next_item(true, cx);
+// });
+// cx.add_async_action(Pane::close_active_item);
+// cx.add_async_action(Pane::close_inactive_items);
+// cx.add_async_action(Pane::close_clean_items);
+// cx.add_async_action(Pane::close_items_to_the_left);
+// cx.add_async_action(Pane::close_items_to_the_right);
+// cx.add_async_action(Pane::close_all_items);
+// cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
+// cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
+// cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
+// cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
+// }
#[derive(Debug)]
pub enum Event {
@@ -159,23 +159,36 @@ pub enum Event {
ZoomOut,
}
+use crate::item::{ItemHandle, WeakItemHandle};
+use collections::{HashMap, VecDeque};
+use gpui2::{Handle, ViewContext, WeakView};
+use project2::{Project, ProjectEntryId, ProjectPath};
+use std::{
+ any::Any,
+ cell::RefCell,
+ cmp, mem,
+ path::PathBuf,
+ rc::Rc,
+ sync::{atomic::AtomicUsize, Arc},
+};
+
pub struct Pane {
items: Vec<Box<dyn ItemHandle>>,
- activation_history: Vec<usize>,
- zoomed: bool,
- active_item_index: usize,
- last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
- autoscroll: bool,
+ // activation_history: Vec<usize>,
+ // zoomed: bool,
+ // active_item_index: usize,
+ // last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
+ // autoscroll: bool,
nav_history: NavHistory,
- toolbar: ViewHandle<Toolbar>,
- tab_bar_context_menu: TabBarContextMenu,
- tab_context_menu: ViewHandle<ContextMenu>,
- workspace: WeakViewHandle<Workspace>,
- project: ModelHandle<Project>,
- has_focus: bool,
- can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
- can_split: bool,
- render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
+ // toolbar: ViewHandle<Toolbar>,
+ // tab_bar_context_menu: TabBarContextMenu,
+ // tab_context_menu: ViewHandle<ContextMenu>,
+ // workspace: WeakViewHandle<Workspace>,
+ project: Handle<Project>,
+ // has_focus: bool,
+ // can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
+ // can_split: bool,
+ // render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
}
pub struct ItemNavHistory {
@@ -192,7 +205,7 @@ struct NavHistoryState {
forward_stack: VecDeque<NavigationEntry>,
closed_stack: VecDeque<NavigationEntry>,
paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
- pane: WeakViewHandle<Pane>,
+ pane: WeakView<Pane>,
next_timestamp: Arc<AtomicUsize>,
}
@@ -218,261 +231,261 @@ pub struct NavigationEntry {
pub timestamp: usize,
}
-pub struct DraggedItem {
- pub handle: Box<dyn ItemHandle>,
- pub pane: WeakViewHandle<Pane>,
-}
-
-pub enum ReorderBehavior {
- None,
- MoveAfterActive,
- MoveToIndex(usize),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum TabBarContextMenuKind {
- New,
- Split,
-}
-
-struct TabBarContextMenu {
- kind: TabBarContextMenuKind,
- handle: ViewHandle<ContextMenu>,
-}
-
-impl TabBarContextMenu {
- fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
- if self.kind == kind {
- return Some(self.handle.clone());
- }
- None
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
- svg_path: &'static str,
- style: theme2::Interactive<theme2::IconButton>,
- nav_button_height: f32,
- tooltip_style: TooltipStyle,
- enabled: bool,
- on_click: F,
- tooltip_action: A,
- action_name: &str,
- cx: &mut ViewContext<Pane>,
-) -> AnyElement<Pane> {
- MouseEventHandler::new::<A, _>(0, cx, |state, _| {
- let style = if enabled {
- style.style_for(state)
- } else {
- style.disabled_style()
- };
- Svg::new(svg_path)
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .contained()
- .with_style(style.container)
- .constrained()
- .with_width(style.button_width)
- .with_height(nav_button_height)
- .aligned()
- .top()
- })
- .with_cursor_style(if enabled {
- CursorStyle::PointingHand
- } else {
- CursorStyle::default()
- })
- .on_click(MouseButton::Left, move |_, toolbar, cx| {
- on_click(toolbar, cx)
- })
- .with_tooltip::<A>(
- 0,
- action_name.to_string(),
- Some(Box::new(tooltip_action)),
- tooltip_style,
- cx,
- )
- .contained()
- .into_any_named("nav button")
-}
+// pub struct DraggedItem {
+// pub handle: Box<dyn ItemHandle>,
+// pub pane: WeakViewHandle<Pane>,
+// }
+
+// pub enum ReorderBehavior {
+// None,
+// MoveAfterActive,
+// MoveToIndex(usize),
+// }
+
+// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// enum TabBarContextMenuKind {
+// New,
+// Split,
+// }
+
+// struct TabBarContextMenu {
+// kind: TabBarContextMenuKind,
+// handle: ViewHandle<ContextMenu>,
+// }
+
+// impl TabBarContextMenu {
+// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
+// if self.kind == kind {
+// return Some(self.handle.clone());
+// }
+// None
+// }
+// }
+
+// #[allow(clippy::too_many_arguments)]
+// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
+// svg_path: &'static str,
+// style: theme2::Interactive<theme2::IconButton>,
+// nav_button_height: f32,
+// tooltip_style: TooltipStyle,
+// enabled: bool,
+// on_click: F,
+// tooltip_action: A,
+// action_name: &str,
+// cx: &mut ViewContext<Pane>,
+// ) -> AnyElement<Pane> {
+// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
+// let style = if enabled {
+// style.style_for(state)
+// } else {
+// style.disabled_style()
+// };
+// Svg::new(svg_path)
+// .with_color(style.color)
+// .constrained()
+// .with_width(style.icon_width)
+// .aligned()
+// .contained()
+// .with_style(style.container)
+// .constrained()
+// .with_width(style.button_width)
+// .with_height(nav_button_height)
+// .aligned()
+// .top()
+// })
+// .with_cursor_style(if enabled {
+// CursorStyle::PointingHand
+// } else {
+// CursorStyle::default()
+// })
+// .on_click(MouseButton::Left, move |_, toolbar, cx| {
+// on_click(toolbar, cx)
+// })
+// .with_tooltip::<A>(
+// 0,
+// action_name.to_string(),
+// Some(Box::new(tooltip_action)),
+// tooltip_style,
+// cx,
+// )
+// .contained()
+// .into_any_named("nav button")
+// }
impl Pane {
- pub fn new(
- workspace: WeakViewHandle<Workspace>,
- project: ModelHandle<Project>,
- next_timestamp: Arc<AtomicUsize>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
- let pane_view_id = cx.view_id();
- let handle = cx.weak_handle();
- let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
- context_menu.update(cx, |menu, _| {
- menu.set_position_mode(OverlayPositionMode::Local)
- });
-
- Self {
- items: Vec::new(),
- activation_history: Vec::new(),
- zoomed: false,
- active_item_index: 0,
- last_focused_view_by_item: Default::default(),
- autoscroll: false,
- nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
- mode: NavigationMode::Normal,
- backward_stack: Default::default(),
- forward_stack: Default::default(),
- closed_stack: Default::default(),
- paths_by_item: Default::default(),
- pane: handle.clone(),
- next_timestamp,
- }))),
- toolbar: cx.add_view(|_| Toolbar::new()),
- tab_bar_context_menu: TabBarContextMenu {
- kind: TabBarContextMenuKind::New,
- handle: context_menu,
- },
- tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
- workspace,
- project,
- has_focus: false,
- can_drop: Rc::new(|_, _| true),
- can_split: true,
- render_tab_bar_buttons: Rc::new(move |pane, cx| {
- Flex::row()
- // New menu
- .with_child(Self::render_tab_bar_button(
- 0,
- "icons/plus.svg",
- false,
- Some(("New...".into(), None)),
- cx,
- |pane, cx| pane.deploy_new_menu(cx),
- |pane, cx| {
- pane.tab_bar_context_menu
- .handle
- .update(cx, |menu, _| menu.delay_cancel())
- },
- pane.tab_bar_context_menu
- .handle_if_kind(TabBarContextMenuKind::New),
- ))
- .with_child(Self::render_tab_bar_button(
- 1,
- "icons/split.svg",
- false,
- Some(("Split Pane".into(), None)),
- cx,
- |pane, cx| pane.deploy_split_menu(cx),
- |pane, cx| {
- pane.tab_bar_context_menu
- .handle
- .update(cx, |menu, _| menu.delay_cancel())
- },
- pane.tab_bar_context_menu
- .handle_if_kind(TabBarContextMenuKind::Split),
- ))
- .with_child({
- let icon_path;
- let tooltip_label;
- if pane.is_zoomed() {
- icon_path = "icons/minimize.svg";
- tooltip_label = "Zoom In";
- } else {
- icon_path = "icons/maximize.svg";
- tooltip_label = "Zoom In";
- }
-
- Pane::render_tab_bar_button(
- 2,
- icon_path,
- pane.is_zoomed(),
- Some((tooltip_label, Some(Box::new(ToggleZoom)))),
- cx,
- move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
- move |_, _| {},
- None,
- )
- })
- .into_any()
- }),
- }
- }
-
- pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
- &self.workspace
- }
-
- pub fn has_focus(&self) -> bool {
- self.has_focus
- }
-
- pub fn active_item_index(&self) -> usize {
- self.active_item_index
- }
-
- pub fn on_can_drop<F>(&mut self, can_drop: F)
- where
- F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
- {
- self.can_drop = Rc::new(can_drop);
- }
-
- pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
- self.can_split = can_split;
- cx.notify();
- }
-
- pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
- self.toolbar.update(cx, |toolbar, cx| {
- toolbar.set_can_navigate(can_navigate, cx);
- });
- cx.notify();
- }
-
- pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
- where
- F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
- {
- self.render_tab_bar_buttons = Rc::new(render);
- cx.notify();
- }
-
- pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
- ItemNavHistory {
- history: self.nav_history.clone(),
- item: Rc::new(item.downgrade()),
- }
- }
-
- pub fn nav_history(&self) -> &NavHistory {
- &self.nav_history
- }
-
- pub fn nav_history_mut(&mut self) -> &mut NavHistory {
- &mut self.nav_history
- }
-
- pub fn disable_history(&mut self) {
- self.nav_history.disable();
- }
-
- pub fn enable_history(&mut self) {
- self.nav_history.enable();
- }
-
- pub fn can_navigate_backward(&self) -> bool {
- !self.nav_history.0.borrow().backward_stack.is_empty()
- }
-
- pub fn can_navigate_forward(&self) -> bool {
- !self.nav_history.0.borrow().forward_stack.is_empty()
- }
-
- fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
- self.toolbar.update(cx, |_, cx| cx.notify());
- }
+ // pub fn new(
+ // workspace: WeakViewHandle<Workspace>,
+ // project: ModelHandle<Project>,
+ // next_timestamp: Arc<AtomicUsize>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Self {
+ // let pane_view_id = cx.view_id();
+ // let handle = cx.weak_handle();
+ // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
+ // context_menu.update(cx, |menu, _| {
+ // menu.set_position_mode(OverlayPositionMode::Local)
+ // });
+
+ // Self {
+ // items: Vec::new(),
+ // activation_history: Vec::new(),
+ // zoomed: false,
+ // active_item_index: 0,
+ // last_focused_view_by_item: Default::default(),
+ // autoscroll: false,
+ // nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
+ // mode: NavigationMode::Normal,
+ // backward_stack: Default::default(),
+ // forward_stack: Default::default(),
+ // closed_stack: Default::default(),
+ // paths_by_item: Default::default(),
+ // pane: handle.clone(),
+ // next_timestamp,
+ // }))),
+ // toolbar: cx.add_view(|_| Toolbar::new()),
+ // tab_bar_context_menu: TabBarContextMenu {
+ // kind: TabBarContextMenuKind::New,
+ // handle: context_menu,
+ // },
+ // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
+ // workspace,
+ // project,
+ // has_focus: false,
+ // can_drop: Rc::new(|_, _| true),
+ // can_split: true,
+ // render_tab_bar_buttons: Rc::new(move |pane, cx| {
+ // Flex::row()
+ // // New menu
+ // .with_child(Self::render_tab_bar_button(
+ // 0,
+ // "icons/plus.svg",
+ // false,
+ // Some(("New...".into(), None)),
+ // cx,
+ // |pane, cx| pane.deploy_new_menu(cx),
+ // |pane, cx| {
+ // pane.tab_bar_context_menu
+ // .handle
+ // .update(cx, |menu, _| menu.delay_cancel())
+ // },
+ // pane.tab_bar_context_menu
+ // .handle_if_kind(TabBarContextMenuKind::New),
+ // ))
+ // .with_child(Self::render_tab_bar_button(
+ // 1,
+ // "icons/split.svg",
+ // false,
+ // Some(("Split Pane".into(), None)),
+ // cx,
+ // |pane, cx| pane.deploy_split_menu(cx),
+ // |pane, cx| {
+ // pane.tab_bar_context_menu
+ // .handle
+ // .update(cx, |menu, _| menu.delay_cancel())
+ // },
+ // pane.tab_bar_context_menu
+ // .handle_if_kind(TabBarContextMenuKind::Split),
+ // ))
+ // .with_child({
+ // let icon_path;
+ // let tooltip_label;
+ // if pane.is_zoomed() {
+ // icon_path = "icons/minimize.svg";
+ // tooltip_label = "Zoom In";
+ // } else {
+ // icon_path = "icons/maximize.svg";
+ // tooltip_label = "Zoom In";
+ // }
+
+ // Pane::render_tab_bar_button(
+ // 2,
+ // icon_path,
+ // pane.is_zoomed(),
+ // Some((tooltip_label, Some(Box::new(ToggleZoom)))),
+ // cx,
+ // move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
+ // move |_, _| {},
+ // None,
+ // )
+ // })
+ // .into_any()
+ // }),
+ // }
+ // }
+
+ // pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
+ // &self.workspace
+ // }
+
+ // pub fn has_focus(&self) -> bool {
+ // self.has_focus
+ // }
+
+ // pub fn active_item_index(&self) -> usize {
+ // self.active_item_index
+ // }
+
+ // pub fn on_can_drop<F>(&mut self, can_drop: F)
+ // where
+ // F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
+ // {
+ // self.can_drop = Rc::new(can_drop);
+ // }
+
+ // pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
+ // self.can_split = can_split;
+ // cx.notify();
+ // }
+
+ // pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
+ // self.toolbar.update(cx, |toolbar, cx| {
+ // toolbar.set_can_navigate(can_navigate, cx);
+ // });
+ // cx.notify();
+ // }
+
+ // pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
+ // where
+ // F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
+ // {
+ // self.render_tab_bar_buttons = Rc::new(render);
+ // cx.notify();
+ // }
+
+ // pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
+ // ItemNavHistory {
+ // history: self.nav_history.clone(),
+ // item: Rc::new(item.downgrade()),
+ // }
+ // }
+
+ // pub fn nav_history(&self) -> &NavHistory {
+ // &self.nav_history
+ // }
+
+ // pub fn nav_history_mut(&mut self) -> &mut NavHistory {
+ // &mut self.nav_history
+ // }
+
+ // pub fn disable_history(&mut self) {
+ // self.nav_history.disable();
+ // }
+
+ // pub fn enable_history(&mut self) {
+ // self.nav_history.enable();
+ // }
+
+ // pub fn can_navigate_backward(&self) -> bool {
+ // !self.nav_history.0.borrow().backward_stack.is_empty()
+ // }
+
+ // pub fn can_navigate_forward(&self) -> bool {
+ // !self.nav_history.0.borrow().forward_stack.is_empty()
+ // }
+
+ // fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
+ // self.toolbar.update(cx, |_, cx| cx.notify());
+ // }
pub(crate) fn open_item(
&mut self,
@@ -599,63 +612,63 @@ impl Pane {
cx.emit(Event::AddItem { item });
}
- pub fn items_len(&self) -> usize {
- self.items.len()
- }
-
- pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
- self.items.iter()
- }
-
- pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
- self.items
- .iter()
- .filter_map(|item| item.as_any().clone().downcast())
- }
-
- pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
- self.items.get(self.active_item_index).cloned()
- }
-
- pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
- self.items
- .get(self.active_item_index)?
- .pixel_position_of_cursor(cx)
- }
-
- pub fn item_for_entry(
- &self,
- entry_id: ProjectEntryId,
- cx: &AppContext,
- ) -> Option<Box<dyn ItemHandle>> {
- self.items.iter().find_map(|item| {
- if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
- Some(item.boxed_clone())
- } else {
- None
- }
- })
- }
-
- pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
- self.items.iter().position(|i| i.id() == item.id())
- }
-
- pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
- // Potentially warn the user of the new keybinding
- let workspace_handle = self.workspace().clone();
- cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
- .detach();
-
- if self.zoomed {
- cx.emit(Event::ZoomOut);
- } else if !self.items.is_empty() {
- if !self.has_focus {
- cx.focus_self();
- }
- cx.emit(Event::ZoomIn);
- }
- }
+ // pub fn items_len(&self) -> usize {
+ // self.items.len()
+ // }
+
+ // pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
+ // self.items.iter()
+ // }
+
+ // pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
+ // self.items
+ // .iter()
+ // .filter_map(|item| item.as_any().clone().downcast())
+ // }
+
+ // pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
+ // self.items.get(self.active_item_index).cloned()
+ // }
+
+ // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
+ // self.items
+ // .get(self.active_item_index)?
+ // .pixel_position_of_cursor(cx)
+ // }
+
+ // pub fn item_for_entry(
+ // &self,
+ // entry_id: ProjectEntryId,
+ // cx: &AppContext,
+ // ) -> Option<Box<dyn ItemHandle>> {
+ // self.items.iter().find_map(|item| {
+ // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+ // Some(item.boxed_clone())
+ // } else {
+ // None
+ // }
+ // })
+ // }
+
+ // pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
+ // self.items.iter().position(|i| i.id() == item.id())
+ // }
+
+ // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
+ // // Potentially warn the user of the new keybinding
+ // let workspace_handle = self.workspace().clone();
+ // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
+ // .detach();
+
+ // if self.zoomed {
+ // cx.emit(Event::ZoomOut);
+ // } else if !self.items.is_empty() {
+ // if !self.has_focus {
+ // cx.focus_self();
+ // }
+ // cx.emit(Event::ZoomIn);
+ // }
+ // }
pub fn activate_item(
&mut self,
@@ -699,2039 +712,2040 @@ impl Pane {
}
}
- pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
- let mut index = self.active_item_index;
- if index > 0 {
- index -= 1;
- } else if !self.items.is_empty() {
- index = self.items.len() - 1;
- }
- self.activate_item(index, activate_pane, activate_pane, cx);
- }
-
- pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
- let mut index = self.active_item_index;
- if index + 1 < self.items.len() {
- index += 1;
- } else {
- index = 0;
- }
- self.activate_item(index, activate_pane, activate_pane, cx);
- }
-
- pub fn close_active_item(
- &mut self,
- action: &CloseActiveItem,
- cx: &mut ViewContext<Self>,
- ) -> Option<Task<Result<()>>> {
- if self.items.is_empty() {
- return None;
- }
- let active_item_id = self.items[self.active_item_index].id();
- Some(self.close_item_by_id(
- active_item_id,
- action.save_intent.unwrap_or(SaveIntent::Close),
- cx,
- ))
- }
-
- pub fn close_item_by_id(
- &mut self,
- item_id_to_close: usize,
- save_intent: SaveIntent,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
- }
-
- pub fn close_inactive_items(
- &mut self,
- _: &CloseInactiveItems,
- cx: &mut ViewContext<Self>,
- ) -> Option<Task<Result<()>>> {
- if self.items.is_empty() {
- return None;
- }
-
- let active_item_id = self.items[self.active_item_index].id();
- Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
- item_id != active_item_id
- }))
- }
-
- pub fn close_clean_items(
- &mut self,
- _: &CloseCleanItems,
- cx: &mut ViewContext<Self>,
- ) -> Option<Task<Result<()>>> {
- let item_ids: Vec<_> = self
- .items()
- .filter(|item| !item.is_dirty(cx))
- .map(|item| item.id())
- .collect();
- Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
- item_ids.contains(&item_id)
- }))
- }
-
- pub fn close_items_to_the_left(
- &mut self,
- _: &CloseItemsToTheLeft,
- cx: &mut ViewContext<Self>,
- ) -> Option<Task<Result<()>>> {
- if self.items.is_empty() {
- return None;
- }
- let active_item_id = self.items[self.active_item_index].id();
- Some(self.close_items_to_the_left_by_id(active_item_id, cx))
- }
-
- pub fn close_items_to_the_left_by_id(
- &mut self,
- item_id: usize,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- let item_ids: Vec<_> = self
- .items()
- .take_while(|item| item.id() != item_id)
- .map(|item| item.id())
- .collect();
- self.close_items(cx, SaveIntent::Close, move |item_id| {
- item_ids.contains(&item_id)
- })
- }
-
- pub fn close_items_to_the_right(
- &mut self,
- _: &CloseItemsToTheRight,
- cx: &mut ViewContext<Self>,
- ) -> Option<Task<Result<()>>> {
- if self.items.is_empty() {
- return None;
- }
- let active_item_id = self.items[self.active_item_index].id();
- Some(self.close_items_to_the_right_by_id(active_item_id, cx))
- }
-
- pub fn close_items_to_the_right_by_id(
- &mut self,
- item_id: usize,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- let item_ids: Vec<_> = self
- .items()
- .rev()
- .take_while(|item| item.id() != item_id)
- .map(|item| item.id())
- .collect();
- self.close_items(cx, SaveIntent::Close, move |item_id| {
- item_ids.contains(&item_id)
- })
- }
-
- pub fn close_all_items(
- &mut self,
- action: &CloseAllItems,
- cx: &mut ViewContext<Self>,
- ) -> Option<Task<Result<()>>> {
- if self.items.is_empty() {
- return None;
- }
-
- Some(
- self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
- true
- }),
- )
- }
-
- pub(super) fn file_names_for_prompt(
- items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
- all_dirty_items: usize,
- cx: &AppContext,
- ) -> String {
- /// Quantity of item paths displayed in prompt prior to cutoff..
- const FILE_NAMES_CUTOFF_POINT: usize = 10;
- let mut file_names: Vec<_> = items
- .filter_map(|item| {
- item.project_path(cx).and_then(|project_path| {
- project_path
- .path
- .file_name()
- .and_then(|name| name.to_str().map(ToOwned::to_owned))
- })
- })
- .take(FILE_NAMES_CUTOFF_POINT)
- .collect();
- let should_display_followup_text =
- all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
- if should_display_followup_text {
- let not_shown_files = all_dirty_items - file_names.len();
- if not_shown_files == 1 {
- file_names.push(".. 1 file not shown".into());
- } else {
- file_names.push(format!(".. {} files not shown", not_shown_files).into());
- }
- }
- let file_names = file_names.join("\n");
- format!(
- "Do you want to save changes to the following {} files?\n{file_names}",
- all_dirty_items
- )
- }
-
- pub fn close_items(
- &mut self,
- cx: &mut ViewContext<Pane>,
- mut save_intent: SaveIntent,
- should_close: impl 'static + Fn(usize) -> bool,
- ) -> Task<Result<()>> {
- // Find the items to close.
- let mut items_to_close = Vec::new();
- let mut dirty_items = Vec::new();
- for item in &self.items {
- if should_close(item.id()) {
- items_to_close.push(item.boxed_clone());
- if item.is_dirty(cx) {
- dirty_items.push(item.boxed_clone());
- }
- }
- }
-
- // If a buffer is open both in a singleton editor and in a multibuffer, make sure
- // to focus the singleton buffer when prompting to save that buffer, as opposed
- // to focusing the multibuffer, because this gives the user a more clear idea
- // of what content they would be saving.
- items_to_close.sort_by_key(|item| !item.is_singleton(cx));
-
- let workspace = self.workspace.clone();
- cx.spawn(|pane, mut cx| async move {
- if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
- let mut answer = pane.update(&mut cx, |_, cx| {
- let prompt =
- Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
- cx.prompt(
- PromptLevel::Warning,
- &prompt,
- &["Save all", "Discard all", "Cancel"],
- )
- })?;
- match answer.next().await {
- Some(0) => save_intent = SaveIntent::SaveAll,
- Some(1) => save_intent = SaveIntent::Skip,
- _ => {}
- }
- }
- let mut saved_project_items_ids = HashSet::default();
- for item in items_to_close.clone() {
- // Find the item's current index and its set of project item models. Avoid
- // storing these in advance, in case they have changed since this task
- // was started.
- let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
- (pane.index_for_item(&*item), item.project_item_model_ids(cx))
- })?;
- let item_ix = if let Some(ix) = item_ix {
- ix
- } else {
- continue;
- };
-
- // Check if this view has any project items that are not open anywhere else
- // in the workspace, AND that the user has not already been prompted to save.
- // If there are any such project entries, prompt the user to save this item.
- let project = workspace.read_with(&cx, |workspace, cx| {
- for item in workspace.items(cx) {
- if !items_to_close
- .iter()
- .any(|item_to_close| item_to_close.id() == item.id())
- {
- let other_project_item_ids = item.project_item_model_ids(cx);
- project_item_ids.retain(|id| !other_project_item_ids.contains(id));
- }
- }
- workspace.project().clone()
- })?;
- let should_save = project_item_ids
- .iter()
- .any(|id| saved_project_items_ids.insert(*id));
-
- if should_save
- && !Self::save_item(
- project.clone(),
- &pane,
- item_ix,
- &*item,
- save_intent,
- &mut cx,
- )
- .await?
- {
- break;
- }
-
- // Remove the item from the pane.
- pane.update(&mut cx, |pane, cx| {
- if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
- pane.remove_item(item_ix, false, cx);
- }
- })?;
- }
-
- pane.update(&mut cx, |_, cx| cx.notify())?;
- Ok(())
- })
- }
-
- pub fn remove_item(
- &mut self,
- item_index: usize,
- activate_pane: bool,
- cx: &mut ViewContext<Self>,
- ) {
- self.activation_history
- .retain(|&history_entry| history_entry != self.items[item_index].id());
-
- if item_index == self.active_item_index {
- let index_to_activate = self
- .activation_history
- .pop()
- .and_then(|last_activated_item| {
- self.items.iter().enumerate().find_map(|(index, item)| {
- (item.id() == last_activated_item).then_some(index)
- })
- })
- // We didn't have a valid activation history entry, so fallback
- // to activating the item to the left
- .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
-
- let should_activate = activate_pane || self.has_focus;
- self.activate_item(index_to_activate, should_activate, should_activate, cx);
- }
-
- let item = self.items.remove(item_index);
-
- cx.emit(Event::RemoveItem { item_id: item.id() });
- if self.items.is_empty() {
- item.deactivated(cx);
- self.update_toolbar(cx);
- cx.emit(Event::Remove);
- }
-
- if item_index < self.active_item_index {
- self.active_item_index -= 1;
- }
-
- self.nav_history.set_mode(NavigationMode::ClosingItem);
- item.deactivated(cx);
- self.nav_history.set_mode(NavigationMode::Normal);
-
- if let Some(path) = item.project_path(cx) {
- let abs_path = self
- .nav_history
- .0
- .borrow()
- .paths_by_item
- .get(&item.id())
- .and_then(|(_, abs_path)| abs_path.clone());
-
- self.nav_history
- .0
- .borrow_mut()
- .paths_by_item
- .insert(item.id(), (path, abs_path));
- } else {
- self.nav_history
- .0
- .borrow_mut()
- .paths_by_item
- .remove(&item.id());
- }
-
- if self.items.is_empty() && self.zoomed {
- cx.emit(Event::ZoomOut);
- }
-
- cx.notify();
- }
-
- pub async fn save_item(
- project: ModelHandle<Project>,
- pane: &WeakViewHandle<Pane>,
- item_ix: usize,
- item: &dyn ItemHandle,
- save_intent: SaveIntent,
- cx: &mut AsyncAppContext,
- ) -> Result<bool> {
- const CONFLICT_MESSAGE: &str =
- "This file has changed on disk since you started editing it. Do you want to overwrite it?";
-
- if save_intent == SaveIntent::Skip {
- return Ok(true);
- }
-
- let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
- (
- item.has_conflict(cx),
- item.is_dirty(cx),
- item.can_save(cx),
- item.is_singleton(cx),
- )
- });
-
- // when saving a single buffer, we ignore whether or not it's dirty.
- if save_intent == SaveIntent::Save {
- is_dirty = true;
- }
-
- if save_intent == SaveIntent::SaveAs {
- is_dirty = true;
- has_conflict = false;
- can_save = false;
- }
-
- if save_intent == SaveIntent::Overwrite {
- has_conflict = false;
- }
-
- if has_conflict && can_save {
- let mut answer = pane.update(cx, |pane, cx| {
- pane.activate_item(item_ix, true, true, cx);
- cx.prompt(
- PromptLevel::Warning,
- CONFLICT_MESSAGE,
- &["Overwrite", "Discard", "Cancel"],
- )
- })?;
- match answer.next().await {
- Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
- Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
- _ => return Ok(false),
- }
- } else if is_dirty && (can_save || can_save_as) {
- if save_intent == SaveIntent::Close {
- let will_autosave = cx.read(|cx| {
- matches!(
- settings::get::<WorkspaceSettings>(cx).autosave,
- AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
- ) && Self::can_autosave_item(&*item, cx)
- });
- if !will_autosave {
- let mut answer = pane.update(cx, |pane, cx| {
- pane.activate_item(item_ix, true, true, cx);
- let prompt = dirty_message_for(item.project_path(cx));
- cx.prompt(
- PromptLevel::Warning,
- &prompt,
- &["Save", "Don't Save", "Cancel"],
- )
- })?;
- match answer.next().await {
- Some(0) => {}
- Some(1) => return Ok(true), // Don't save his file
- _ => return Ok(false), // Cancel
- }
- }
- }
-
- if can_save {
- pane.update(cx, |_, cx| item.save(project, cx))?.await?;
- } else if can_save_as {
- let start_abs_path = project
- .read_with(cx, |project, cx| {
- let worktree = project.visible_worktrees(cx).next()?;
- Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
- })
- .unwrap_or_else(|| Path::new("").into());
-
- let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
- if let Some(abs_path) = abs_path.next().await.flatten() {
- pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
- .await?;
- } else {
- return Ok(false);
- }
- }
- }
- Ok(true)
- }
-
- fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
- let is_deleted = item.project_entry_ids(cx).is_empty();
- item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
- }
-
- pub fn autosave_item(
- item: &dyn ItemHandle,
- project: ModelHandle<Project>,
- cx: &mut WindowContext,
- ) -> Task<Result<()>> {
- if Self::can_autosave_item(item, cx) {
- item.save(project, cx)
- } else {
- Task::ready(Ok(()))
- }
- }
-
- pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
- if let Some(active_item) = self.active_item() {
- cx.focus(active_item.as_any());
- }
- }
-
- pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Split(direction));
- }
-
- fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
- self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
- menu.toggle(
- Default::default(),
- AnchorCorner::TopRight,
- vec![
- ContextMenuItem::action("Split Right", SplitRight),
- ContextMenuItem::action("Split Left", SplitLeft),
- ContextMenuItem::action("Split Up", SplitUp),
- ContextMenuItem::action("Split Down", SplitDown),
- ],
- cx,
- );
- });
-
- self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
- }
-
- fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
- self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
- menu.toggle(
- Default::default(),
- AnchorCorner::TopRight,
- vec![
- ContextMenuItem::action("New File", NewFile),
- ContextMenuItem::action("New Terminal", NewCenterTerminal),
- ContextMenuItem::action("New Search", NewSearch),
- ],
- cx,
- );
- });
-
- self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
- }
-
- fn deploy_tab_context_menu(
- &mut self,
- position: Vector2F,
- target_item_id: usize,
- cx: &mut ViewContext<Self>,
- ) {
- let active_item_id = self.items[self.active_item_index].id();
- let is_active_item = target_item_id == active_item_id;
- let target_pane = cx.weak_handle();
-
- // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
-
- self.tab_context_menu.update(cx, |menu, cx| {
- menu.show(
- position,
- AnchorCorner::TopLeft,
- if is_active_item {
- vec![
- ContextMenuItem::action(
- "Close Active Item",
- CloseActiveItem { save_intent: None },
- ),
- ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
- ContextMenuItem::action("Close Clean Items", CloseCleanItems),
- ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
- ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
- ContextMenuItem::action(
- "Close All Items",
- CloseAllItems { save_intent: None },
- ),
- ]
- } else {
- // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
- vec![
- ContextMenuItem::handler("Close Inactive Item", {
- let pane = target_pane.clone();
- move |cx| {
- if let Some(pane) = pane.upgrade(cx) {
- pane.update(cx, |pane, cx| {
- pane.close_item_by_id(
- target_item_id,
- SaveIntent::Close,
- cx,
- )
- .detach_and_log_err(cx);
- })
- }
- }
- }),
- ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
- ContextMenuItem::action("Close Clean Items", CloseCleanItems),
- ContextMenuItem::handler("Close Items To The Left", {
- let pane = target_pane.clone();
- move |cx| {
- if let Some(pane) = pane.upgrade(cx) {
- pane.update(cx, |pane, cx| {
- pane.close_items_to_the_left_by_id(target_item_id, cx)
- .detach_and_log_err(cx);
- })
- }
- }
- }),
- ContextMenuItem::handler("Close Items To The Right", {
- let pane = target_pane.clone();
- move |cx| {
- if let Some(pane) = pane.upgrade(cx) {
- pane.update(cx, |pane, cx| {
- pane.close_items_to_the_right_by_id(target_item_id, cx)
- .detach_and_log_err(cx);
- })
- }
- }
- }),
- ContextMenuItem::action(
- "Close All Items",
- CloseAllItems { save_intent: None },
- ),
- ]
- },
- cx,
- );
- });
- }
-
- pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
- &self.toolbar
- }
-
- pub fn handle_deleted_project_item(
- &mut self,
- entry_id: ProjectEntryId,
- cx: &mut ViewContext<Pane>,
- ) -> Option<()> {
- let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
- if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
- Some((i, item.id()))
- } else {
- None
- }
- })?;
-
- self.remove_item(item_index_to_delete, false, cx);
- self.nav_history.remove_item(item_id);
-
- Some(())
- }
-
- fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
- let active_item = self
- .items
- .get(self.active_item_index)
- .map(|item| item.as_ref());
- self.toolbar.update(cx, |toolbar, cx| {
- toolbar.set_active_item(active_item, cx);
- });
- }
-
- fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
- let theme = theme::current(cx).clone();
-
- let pane = cx.handle().downgrade();
- let autoscroll = if mem::take(&mut self.autoscroll) {
- Some(self.active_item_index)
- } else {
- None
- };
-
- let pane_active = self.has_focus;
-
- enum Tabs {}
- let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
- for (ix, (item, detail)) in self
- .items
- .iter()
- .cloned()
- .zip(self.tab_details(cx))
- .enumerate()
- {
- let git_status = item
- .project_path(cx)
- .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
- .and_then(|entry| entry.git_status());
-
- let detail = if detail == 0 { None } else { Some(detail) };
- let tab_active = ix == self.active_item_index;
-
- row.add_child({
- enum TabDragReceiver {}
- let mut receiver =
- dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
- let item = item.clone();
- let pane = pane.clone();
- let detail = detail.clone();
-
- let theme = theme::current(cx).clone();
- let mut tooltip_theme = theme.tooltip.clone();
- tooltip_theme.max_text_width = None;
- let tab_tooltip_text =
- item.tab_tooltip_text(cx).map(|text| text.into_owned());
-
- let mut tab_style = theme
- .workspace
- .tab_bar
- .tab_style(pane_active, tab_active)
- .clone();
- let should_show_status = settings::get::<ItemSettings>(cx).git_status;
- if should_show_status && git_status != None {
- tab_style.label.text.color = match git_status.unwrap() {
- GitFileStatus::Added => tab_style.git.inserted,
- GitFileStatus::Modified => tab_style.git.modified,
- GitFileStatus::Conflict => tab_style.git.conflict,
- };
- }
-
- move |mouse_state, cx| {
- let hovered = mouse_state.hovered();
-
- enum Tab {}
- let mouse_event_handler =
- MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
- Self::render_tab(
- &item,
- pane.clone(),
- ix == 0,
- detail,
- hovered,
- &tab_style,
- cx,
- )
- })
- .on_down(MouseButton::Left, move |_, this, cx| {
- this.activate_item(ix, true, true, cx);
- })
- .on_click(MouseButton::Middle, {
- let item_id = item.id();
- move |_, pane, cx| {
- pane.close_item_by_id(item_id, SaveIntent::Close, cx)
- .detach_and_log_err(cx);
- }
- })
- .on_down(
- MouseButton::Right,
- move |event, pane, cx| {
- pane.deploy_tab_context_menu(event.position, item.id(), cx);
- },
- );
-
- if let Some(tab_tooltip_text) = tab_tooltip_text {
- mouse_event_handler
- .with_tooltip::<Self>(
- ix,
- tab_tooltip_text,
- None,
- tooltip_theme,
- cx,
- )
- .into_any()
- } else {
- mouse_event_handler.into_any()
- }
- }
- });
-
- if !pane_active || !tab_active {
- receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
- }
-
- receiver.as_draggable(
- DraggedItem {
- handle: item,
- pane: pane.clone(),
- },
- {
- let theme = theme::current(cx).clone();
-
- let detail = detail.clone();
- move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
- let tab_style = &theme.workspace.tab_bar.dragged_tab;
- Self::render_dragged_tab(
- &dragged_item.handle,
- dragged_item.pane.clone(),
- false,
- detail,
- false,
- &tab_style,
- cx,
- )
- }
- },
- )
- })
- }
-
- // Use the inactive tab style along with the current pane's active status to decide how to render
- // the filler
- let filler_index = self.items.len();
- let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
- enum Filler {}
- row.add_child(
- dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
- Empty::new()
- .contained()
- .with_style(filler_style.container)
- .with_border(filler_style.container.border)
- })
- .flex(1., true)
- .into_any_named("filler"),
- );
-
- row
- }
-
- fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
- let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
-
- let mut tab_descriptions = HashMap::default();
- let mut done = false;
- while !done {
- done = true;
-
- // Store item indices by their tab description.
- for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
- if let Some(description) = item.tab_description(*detail, cx) {
- if *detail == 0
- || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
- {
- tab_descriptions
- .entry(description)
- .or_insert(Vec::new())
- .push(ix);
- }
- }
- }
-
- // If two or more items have the same tab description, increase their level
- // of detail and try again.
- for (_, item_ixs) in tab_descriptions.drain() {
- if item_ixs.len() > 1 {
- done = false;
- for ix in item_ixs {
- tab_details[ix] += 1;
- }
- }
- }
- }
-
- tab_details
- }
-
- fn render_tab(
- item: &Box<dyn ItemHandle>,
- pane: WeakViewHandle<Pane>,
- first: bool,
- detail: Option<usize>,
- hovered: bool,
- tab_style: &theme::Tab,
- cx: &mut ViewContext<Self>,
- ) -> AnyElement<Self> {
- let title = item.tab_content(detail, &tab_style, cx);
- Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
- }
-
- fn render_dragged_tab(
- item: &Box<dyn ItemHandle>,
- pane: WeakViewHandle<Pane>,
- first: bool,
- detail: Option<usize>,
- hovered: bool,
- tab_style: &theme::Tab,
- cx: &mut ViewContext<Workspace>,
- ) -> AnyElement<Workspace> {
- let title = item.dragged_tab_content(detail, &tab_style, cx);
- Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
- }
-
- fn render_tab_with_title<T: View>(
- title: AnyElement<T>,
- item: &Box<dyn ItemHandle>,
- pane: WeakViewHandle<Pane>,
- first: bool,
- hovered: bool,
- tab_style: &theme::Tab,
- cx: &mut ViewContext<T>,
- ) -> AnyElement<T> {
- let mut container = tab_style.container.clone();
- if first {
- container.border.left = false;
- }
-
- let buffer_jewel_element = {
- let diameter = 7.0;
- let icon_color = if item.has_conflict(cx) {
- Some(tab_style.icon_conflict)
- } else if item.is_dirty(cx) {
- Some(tab_style.icon_dirty)
- } else {
- None
- };
-
- Canvas::new(move |bounds, _, _, cx| {
- if let Some(color) = icon_color {
- let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
- cx.scene().push_quad(Quad {
- bounds: square,
- background: Some(color),
- border: Default::default(),
- corner_radii: (diameter / 2.).into(),
- });
- }
- })
- .constrained()
- .with_width(diameter)
- .with_height(diameter)
- .aligned()
- };
-
- let title_element = title.aligned().contained().with_style(ContainerStyle {
- margin: Margin {
- left: tab_style.spacing,
- right: tab_style.spacing,
- ..Default::default()
- },
- ..Default::default()
- });
-
- let close_element = if hovered {
- let item_id = item.id();
- enum TabCloseButton {}
- let icon = Svg::new("icons/x.svg");
- MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
- if mouse_state.hovered() {
- icon.with_color(tab_style.icon_close_active)
- } else {
- icon.with_color(tab_style.icon_close)
- }
- })
- .with_padding(Padding::uniform(4.))
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, {
- let pane = pane.clone();
- move |_, _, cx| {
- let pane = pane.clone();
- cx.window_context().defer(move |cx| {
- if let Some(pane) = pane.upgrade(cx) {
- pane.update(cx, |pane, cx| {
- pane.close_item_by_id(item_id, SaveIntent::Close, cx)
- .detach_and_log_err(cx);
- });
- }
- });
- }
- })
- .into_any_named("close-tab-icon")
- .constrained()
- } else {
- Empty::new().constrained()
- }
- .with_width(tab_style.close_icon_width)
- .aligned();
-
- let close_right = settings::get::<ItemSettings>(cx).close_position.right();
-
- if close_right {
- Flex::row()
- .with_child(buffer_jewel_element)
- .with_child(title_element)
- .with_child(close_element)
- } else {
- Flex::row()
- .with_child(close_element)
- .with_child(title_element)
- .with_child(buffer_jewel_element)
- }
- .contained()
- .with_style(container)
- .constrained()
- .with_height(tab_style.height)
- .into_any()
- }
-
- pub fn render_tab_bar_button<
- F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
- F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
- >(
- index: usize,
- icon: &'static str,
- is_active: bool,
- tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
- cx: &mut ViewContext<Pane>,
- on_click: F1,
- on_down: F2,
- context_menu: Option<ViewHandle<ContextMenu>>,
- ) -> AnyElement<Pane> {
- enum TabBarButton {}
-
- let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
- let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
- let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
- Svg::new(icon)
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .constrained()
- .with_width(style.button_width)
- .with_height(style.button_width)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
- .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
- .into_any();
- if let Some((tooltip, action)) = tooltip {
- let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
- button = button
- .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
- .into_any();
- }
-
- Stack::new()
- .with_child(button)
- .with_children(
- context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
- )
- .flex(1., false)
- .into_any_named("tab bar button")
- }
-
- fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- let background = theme.workspace.background;
- Empty::new()
- .contained()
- .with_background_color(background)
- .into_any()
- }
-
- pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
- self.zoomed = zoomed;
- cx.notify();
- }
-
- pub fn is_zoomed(&self) -> bool {
- self.zoomed
- }
-}
-
-impl Entity for Pane {
- type Event = Event;
-}
-
-impl View for Pane {
- fn ui_name() -> &'static str {
- "Pane"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- enum MouseNavigationHandler {}
-
- MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
- let active_item_index = self.active_item_index;
-
- if let Some(active_item) = self.active_item() {
- Flex::column()
- .with_child({
- let theme = theme::current(cx).clone();
-
- let mut stack = Stack::new();
-
- enum TabBarEventHandler {}
- stack.add_child(
- MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
- Empty::new()
- .contained()
- .with_style(theme.workspace.tab_bar.container)
- })
- .on_down(
- MouseButton::Left,
- move |_, this, cx| {
- this.activate_item(active_item_index, true, true, cx);
- },
- ),
- );
- let tooltip_style = theme.tooltip.clone();
- let tab_bar_theme = theme.workspace.tab_bar.clone();
-
- let nav_button_height = tab_bar_theme.height;
- let button_style = tab_bar_theme.nav_button;
- let border_for_nav_buttons = tab_bar_theme
- .tab_style(false, false)
- .container
- .border
- .clone();
-
- let mut tab_row = Flex::row()
- .with_child(nav_button(
- "icons/arrow_left.svg",
- button_style.clone(),
- nav_button_height,
- tooltip_style.clone(),
- self.can_navigate_backward(),
- {
- move |pane, cx| {
- if let Some(workspace) = pane.workspace.upgrade(cx) {
- let pane = cx.weak_handle();
- cx.window_context().defer(move |cx| {
- workspace.update(cx, |workspace, cx| {
- workspace
- .go_back(pane, cx)
- .detach_and_log_err(cx)
- })
- })
- }
- }
- },
- super::GoBack,
- "Go Back",
- cx,
- ))
- .with_child(
- nav_button(
- "icons/arrow_right.svg",
- button_style.clone(),
- nav_button_height,
- tooltip_style,
- self.can_navigate_forward(),
- {
- move |pane, cx| {
- if let Some(workspace) = pane.workspace.upgrade(cx) {
- let pane = cx.weak_handle();
- cx.window_context().defer(move |cx| {
- workspace.update(cx, |workspace, cx| {
- workspace
- .go_forward(pane, cx)
- .detach_and_log_err(cx)
- })
- })
- }
- }
- },
- super::GoForward,
- "Go Forward",
- cx,
- )
- .contained()
- .with_border(border_for_nav_buttons),
- )
- .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
-
- if self.has_focus {
- let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
- tab_row.add_child(
- (render_tab_bar_buttons)(self, cx)
- .contained()
- .with_style(theme.workspace.tab_bar.pane_button_container)
- .flex(1., false)
- .into_any(),
- )
- }
-
- stack.add_child(tab_row);
- stack
- .constrained()
- .with_height(theme.workspace.tab_bar.height)
- .flex(1., false)
- .into_any_named("tab bar")
- })
- .with_child({
- enum PaneContentTabDropTarget {}
- dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
- self,
- 0,
- self.active_item_index + 1,
- !self.can_split,
- if self.can_split { Some(100.) } else { None },
- cx,
- {
- let toolbar = self.toolbar.clone();
- let toolbar_hidden = toolbar.read(cx).hidden();
- move |_, cx| {
- Flex::column()
- .with_children(
- (!toolbar_hidden)
- .then(|| ChildView::new(&toolbar, cx).expanded()),
- )
- .with_child(
- ChildView::new(active_item.as_any(), cx).flex(1., true),
- )
- }
- },
- )
- .flex(1., true)
- })
- .with_child(ChildView::new(&self.tab_context_menu, cx))
- .into_any()
- } else {
- enum EmptyPane {}
- let theme = theme::current(cx).clone();
-
- dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
- self.render_blank_pane(&theme, cx)
- })
- .on_down(MouseButton::Left, |_, _, cx| {
- cx.focus_parent();
- })
- .into_any()
- }
- })
- .on_down(
- MouseButton::Navigate(NavigationDirection::Back),
- move |_, pane, cx| {
- if let Some(workspace) = pane.workspace.upgrade(cx) {
- let pane = cx.weak_handle();
- cx.window_context().defer(move |cx| {
- workspace.update(cx, |workspace, cx| {
- workspace.go_back(pane, cx).detach_and_log_err(cx)
- })
- })
- }
- },
- )
- .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
- move |_, pane, cx| {
- if let Some(workspace) = pane.workspace.upgrade(cx) {
- let pane = cx.weak_handle();
- cx.window_context().defer(move |cx| {
- workspace.update(cx, |workspace, cx| {
- workspace.go_forward(pane, cx).detach_and_log_err(cx)
- })
- })
- }
- }
- })
- .into_any_named("pane")
- }
-
- fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if !self.has_focus {
- self.has_focus = true;
- cx.emit(Event::Focus);
- cx.notify();
- }
-
- self.toolbar.update(cx, |toolbar, cx| {
- toolbar.focus_changed(true, cx);
- });
-
- if let Some(active_item) = self.active_item() {
- if cx.is_self_focused() {
- // Pane was focused directly. We need to either focus a view inside the active item,
- // or focus the active item itself
- if let Some(weak_last_focused_view) =
- self.last_focused_view_by_item.get(&active_item.id())
- {
- if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
- cx.focus(&last_focused_view);
- return;
- } else {
- self.last_focused_view_by_item.remove(&active_item.id());
- }
- }
-
- cx.focus(active_item.as_any());
- } else if focused != self.tab_bar_context_menu.handle {
- self.last_focused_view_by_item
- .insert(active_item.id(), focused.downgrade());
- }
- }
- }
-
- fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- self.has_focus = false;
- self.toolbar.update(cx, |toolbar, cx| {
- toolbar.focus_changed(false, cx);
- });
- cx.notify();
- }
-
- fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
- Self::reset_to_default_keymap_context(keymap);
- }
-}
-
-impl ItemNavHistory {
- pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
- self.history.push(data, self.item.clone(), cx);
- }
-
- pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
- self.history.pop(NavigationMode::GoingBack, cx)
- }
-
- pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
- self.history.pop(NavigationMode::GoingForward, cx)
- }
-}
-
-impl NavHistory {
- pub fn for_each_entry(
- &self,
- cx: &AppContext,
- mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
- ) {
- let borrowed_history = self.0.borrow();
- borrowed_history
- .forward_stack
- .iter()
- .chain(borrowed_history.backward_stack.iter())
- .chain(borrowed_history.closed_stack.iter())
- .for_each(|entry| {
- if let Some(project_and_abs_path) =
- borrowed_history.paths_by_item.get(&entry.item.id())
- {
- f(entry, project_and_abs_path.clone());
- } else if let Some(item) = entry.item.upgrade(cx) {
- if let Some(path) = item.project_path(cx) {
- f(entry, (path, None));
- }
- }
- })
- }
-
- pub fn set_mode(&mut self, mode: NavigationMode) {
- self.0.borrow_mut().mode = mode;
- }
+ // pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
+ // let mut index = self.active_item_index;
+ // if index > 0 {
+ // index -= 1;
+ // } else if !self.items.is_empty() {
+ // index = self.items.len() - 1;
+ // }
+ // self.activate_item(index, activate_pane, activate_pane, cx);
+ // }
+
+ // pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
+ // let mut index = self.active_item_index;
+ // if index + 1 < self.items.len() {
+ // index += 1;
+ // } else {
+ // index = 0;
+ // }
+ // self.activate_item(index, activate_pane, activate_pane, cx);
+ // }
+
+ // pub fn close_active_item(
+ // &mut self,
+ // action: &CloseActiveItem,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
+ // }
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_item_by_id(
+ // active_item_id,
+ // action.save_intent.unwrap_or(SaveIntent::Close),
+ // cx,
+ // ))
+ // }
+
+ // pub fn close_item_by_id(
+ // &mut self,
+ // item_id_to_close: usize,
+ // save_intent: SaveIntent,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
+ // }
+
+ // pub fn close_inactive_items(
+ // &mut self,
+ // _: &CloseInactiveItems,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
+ // }
+
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_id != active_item_id
+ // }))
+ // }
+
+ // pub fn close_clean_items(
+ // &mut self,
+ // _: &CloseCleanItems,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let item_ids: Vec<_> = self
+ // .items()
+ // .filter(|item| !item.is_dirty(cx))
+ // .map(|item| item.id())
+ // .collect();
+ // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_ids.contains(&item_id)
+ // }))
+ // }
+
+ // pub fn close_items_to_the_left(
+ // &mut self,
+ // _: &CloseItemsToTheLeft,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
+ // }
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_items_to_the_left_by_id(active_item_id, cx))
+ // }
+
+ // pub fn close_items_to_the_left_by_id(
+ // &mut self,
+ // item_id: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // let item_ids: Vec<_> = self
+ // .items()
+ // .take_while(|item| item.id() != item_id)
+ // .map(|item| item.id())
+ // .collect();
+ // self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_ids.contains(&item_id)
+ // })
+ // }
+
+ // pub fn close_items_to_the_right(
+ // &mut self,
+ // _: &CloseItemsToTheRight,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
+ // }
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_items_to_the_right_by_id(active_item_id, cx))
+ // }
+
+ // pub fn close_items_to_the_right_by_id(
+ // &mut self,
+ // item_id: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // let item_ids: Vec<_> = self
+ // .items()
+ // .rev()
+ // .take_while(|item| item.id() != item_id)
+ // .map(|item| item.id())
+ // .collect();
+ // self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_ids.contains(&item_id)
+ // })
+ // }
+
+ // pub fn close_all_items(
+ // &mut self,
+ // action: &CloseAllItems,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
+ // }
+
+ // Some(
+ // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
+ // true
+ // }),
+ // )
+ // }
+
+ // pub(super) fn file_names_for_prompt(
+ // items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
+ // all_dirty_items: usize,
+ // cx: &AppContext,
+ // ) -> String {
+ // /// Quantity of item paths displayed in prompt prior to cutoff..
+ // const FILE_NAMES_CUTOFF_POINT: usize = 10;
+ // let mut file_names: Vec<_> = items
+ // .filter_map(|item| {
+ // item.project_path(cx).and_then(|project_path| {
+ // project_path
+ // .path
+ // .file_name()
+ // .and_then(|name| name.to_str().map(ToOwned::to_owned))
+ // })
+ // })
+ // .take(FILE_NAMES_CUTOFF_POINT)
+ // .collect();
+ // let should_display_followup_text =
+ // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
+ // if should_display_followup_text {
+ // let not_shown_files = all_dirty_items - file_names.len();
+ // if not_shown_files == 1 {
+ // file_names.push(".. 1 file not shown".into());
+ // } else {
+ // file_names.push(format!(".. {} files not shown", not_shown_files).into());
+ // }
+ // }
+ // let file_names = file_names.join("\n");
+ // format!(
+ // "Do you want to save changes to the following {} files?\n{file_names}",
+ // all_dirty_items
+ // )
+ // }
+
+ // pub fn close_items(
+ // &mut self,
+ // cx: &mut ViewContext<Pane>,
+ // mut save_intent: SaveIntent,
+ // should_close: impl 'static + Fn(usize) -> bool,
+ // ) -> Task<Result<()>> {
+ // // Find the items to close.
+ // let mut items_to_close = Vec::new();
+ // let mut dirty_items = Vec::new();
+ // for item in &self.items {
+ // if should_close(item.id()) {
+ // items_to_close.push(item.boxed_clone());
+ // if item.is_dirty(cx) {
+ // dirty_items.push(item.boxed_clone());
+ // }
+ // }
+ // }
+
+ // // If a buffer is open both in a singleton editor and in a multibuffer, make sure
+ // // to focus the singleton buffer when prompting to save that buffer, as opposed
+ // // to focusing the multibuffer, because this gives the user a more clear idea
+ // // of what content they would be saving.
+ // items_to_close.sort_by_key(|item| !item.is_singleton(cx));
+
+ // let workspace = self.workspace.clone();
+ // cx.spawn(|pane, mut cx| async move {
+ // if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+ // let mut answer = pane.update(&mut cx, |_, cx| {
+ // let prompt =
+ // Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
+ // cx.prompt(
+ // PromptLevel::Warning,
+ // &prompt,
+ // &["Save all", "Discard all", "Cancel"],
+ // )
+ // })?;
+ // match answer.next().await {
+ // Some(0) => save_intent = SaveIntent::SaveAll,
+ // Some(1) => save_intent = SaveIntent::Skip,
+ // _ => {}
+ // }
+ // }
+ // let mut saved_project_items_ids = HashSet::default();
+ // for item in items_to_close.clone() {
+ // // Find the item's current index and its set of project item models. Avoid
+ // // storing these in advance, in case they have changed since this task
+ // // was started.
+ // let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
+ // (pane.index_for_item(&*item), item.project_item_model_ids(cx))
+ // })?;
+ // let item_ix = if let Some(ix) = item_ix {
+ // ix
+ // } else {
+ // continue;
+ // };
+
+ // // Check if this view has any project items that are not open anywhere else
+ // // in the workspace, AND that the user has not already been prompted to save.
+ // // If there are any such project entries, prompt the user to save this item.
+ // let project = workspace.read_with(&cx, |workspace, cx| {
+ // for item in workspace.items(cx) {
+ // if !items_to_close
+ // .iter()
+ // .any(|item_to_close| item_to_close.id() == item.id())
+ // {
+ // let other_project_item_ids = item.project_item_model_ids(cx);
+ // project_item_ids.retain(|id| !other_project_item_ids.contains(id));
+ // }
+ // }
+ // workspace.project().clone()
+ // })?;
+ // let should_save = project_item_ids
+ // .iter()
+ // .any(|id| saved_project_items_ids.insert(*id));
+
+ // if should_save
+ // && !Self::save_item(
+ // project.clone(),
+ // &pane,
+ // item_ix,
+ // &*item,
+ // save_intent,
+ // &mut cx,
+ // )
+ // .await?
+ // {
+ // break;
+ // }
+
+ // // Remove the item from the pane.
+ // pane.update(&mut cx, |pane, cx| {
+ // if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
+ // pane.remove_item(item_ix, false, cx);
+ // }
+ // })?;
+ // }
+
+ // pane.update(&mut cx, |_, cx| cx.notify())?;
+ // Ok(())
+ // })
+ // }
+
+ // pub fn remove_item(
+ // &mut self,
+ // item_index: usize,
+ // activate_pane: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.activation_history
+ // .retain(|&history_entry| history_entry != self.items[item_index].id());
+
+ // if item_index == self.active_item_index {
+ // let index_to_activate = self
+ // .activation_history
+ // .pop()
+ // .and_then(|last_activated_item| {
+ // self.items.iter().enumerate().find_map(|(index, item)| {
+ // (item.id() == last_activated_item).then_some(index)
+ // })
+ // })
+ // // We didn't have a valid activation history entry, so fallback
+ // // to activating the item to the left
+ // .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
+
+ // let should_activate = activate_pane || self.has_focus;
+ // self.activate_item(index_to_activate, should_activate, should_activate, cx);
+ // }
+
+ // let item = self.items.remove(item_index);
+
+ // cx.emit(Event::RemoveItem { item_id: item.id() });
+ // if self.items.is_empty() {
+ // item.deactivated(cx);
+ // self.update_toolbar(cx);
+ // cx.emit(Event::Remove);
+ // }
+
+ // if item_index < self.active_item_index {
+ // self.active_item_index -= 1;
+ // }
+
+ // self.nav_history.set_mode(NavigationMode::ClosingItem);
+ // item.deactivated(cx);
+ // self.nav_history.set_mode(NavigationMode::Normal);
+
+ // if let Some(path) = item.project_path(cx) {
+ // let abs_path = self
+ // .nav_history
+ // .0
+ // .borrow()
+ // .paths_by_item
+ // .get(&item.id())
+ // .and_then(|(_, abs_path)| abs_path.clone());
+
+ // self.nav_history
+ // .0
+ // .borrow_mut()
+ // .paths_by_item
+ // .insert(item.id(), (path, abs_path));
+ // } else {
+ // self.nav_history
+ // .0
+ // .borrow_mut()
+ // .paths_by_item
+ // .remove(&item.id());
+ // }
+
+ // if self.items.is_empty() && self.zoomed {
+ // cx.emit(Event::ZoomOut);
+ // }
+
+ // cx.notify();
+ // }
+
+ // pub async fn save_item(
+ // project: ModelHandle<Project>,
+ // pane: &WeakViewHandle<Pane>,
+ // item_ix: usize,
+ // item: &dyn ItemHandle,
+ // save_intent: SaveIntent,
+ // cx: &mut AsyncAppContext,
+ // ) -> Result<bool> {
+ // const CONFLICT_MESSAGE: &str =
+ // "This file has changed on disk since you started editing it. Do you want to overwrite it?";
+
+ // if save_intent == SaveIntent::Skip {
+ // return Ok(true);
+ // }
+
+ // let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
+ // (
+ // item.has_conflict(cx),
+ // item.is_dirty(cx),
+ // item.can_save(cx),
+ // item.is_singleton(cx),
+ // )
+ // });
+
+ // // when saving a single buffer, we ignore whether or not it's dirty.
+ // if save_intent == SaveIntent::Save {
+ // is_dirty = true;
+ // }
+
+ // if save_intent == SaveIntent::SaveAs {
+ // is_dirty = true;
+ // has_conflict = false;
+ // can_save = false;
+ // }
+
+ // if save_intent == SaveIntent::Overwrite {
+ // has_conflict = false;
+ // }
+
+ // if has_conflict && can_save {
+ // let mut answer = pane.update(cx, |pane, cx| {
+ // pane.activate_item(item_ix, true, true, cx);
+ // cx.prompt(
+ // PromptLevel::Warning,
+ // CONFLICT_MESSAGE,
+ // &["Overwrite", "Discard", "Cancel"],
+ // )
+ // })?;
+ // match answer.next().await {
+ // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+ // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
+ // _ => return Ok(false),
+ // }
+ // } else if is_dirty && (can_save || can_save_as) {
+ // if save_intent == SaveIntent::Close {
+ // let will_autosave = cx.read(|cx| {
+ // matches!(
+ // settings::get::<WorkspaceSettings>(cx).autosave,
+ // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
+ // ) && Self::can_autosave_item(&*item, cx)
+ // });
+ // if !will_autosave {
+ // let mut answer = pane.update(cx, |pane, cx| {
+ // pane.activate_item(item_ix, true, true, cx);
+ // let prompt = dirty_message_for(item.project_path(cx));
+ // cx.prompt(
+ // PromptLevel::Warning,
+ // &prompt,
+ // &["Save", "Don't Save", "Cancel"],
+ // )
+ // })?;
+ // match answer.next().await {
+ // Some(0) => {}
+ // Some(1) => return Ok(true), // Don't save his file
+ // _ => return Ok(false), // Cancel
+ // }
+ // }
+ // }
+
+ // if can_save {
+ // pane.update(cx, |_, cx| item.save(project, cx))?.await?;
+ // } else if can_save_as {
+ // let start_abs_path = project
+ // .read_with(cx, |project, cx| {
+ // let worktree = project.visible_worktrees(cx).next()?;
+ // Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
+ // })
+ // .unwrap_or_else(|| Path::new("").into());
+
+ // let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
+ // if let Some(abs_path) = abs_path.next().await.flatten() {
+ // pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+ // .await?;
+ // } else {
+ // return Ok(false);
+ // }
+ // }
+ // }
+ // Ok(true)
+ // }
+
+ // fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
+ // let is_deleted = item.project_entry_ids(cx).is_empty();
+ // item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
+ // }
+
+ // pub fn autosave_item(
+ // item: &dyn ItemHandle,
+ // project: ModelHandle<Project>,
+ // cx: &mut WindowContext,
+ // ) -> Task<Result<()>> {
+ // if Self::can_autosave_item(item, cx) {
+ // item.save(project, cx)
+ // } else {
+ // Task::ready(Ok(()))
+ // }
+ // }
+
+ // pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
+ // if let Some(active_item) = self.active_item() {
+ // cx.focus(active_item.as_any());
+ // }
+ // }
+
+ // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
+ // cx.emit(Event::Split(direction));
+ // }
+
+ // fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
+ // self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
+ // menu.toggle(
+ // Default::default(),
+ // AnchorCorner::TopRight,
+ // vec![
+ // ContextMenuItem::action("Split Right", SplitRight),
+ // ContextMenuItem::action("Split Left", SplitLeft),
+ // ContextMenuItem::action("Split Up", SplitUp),
+ // ContextMenuItem::action("Split Down", SplitDown),
+ // ],
+ // cx,
+ // );
+ // });
+
+ // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
+ // }
+
+ // fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
+ // self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
+ // menu.toggle(
+ // Default::default(),
+ // AnchorCorner::TopRight,
+ // vec![
+ // ContextMenuItem::action("New File", NewFile),
+ // ContextMenuItem::action("New Terminal", NewCenterTerminal),
+ // ContextMenuItem::action("New Search", NewSearch),
+ // ],
+ // cx,
+ // );
+ // });
+
+ // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
+ // }
+
+ // fn deploy_tab_context_menu(
+ // &mut self,
+ // position: Vector2F,
+ // target_item_id: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // let active_item_id = self.items[self.active_item_index].id();
+ // let is_active_item = target_item_id == active_item_id;
+ // let target_pane = cx.weak_handle();
+
+ // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
+
+ // self.tab_context_menu.update(cx, |menu, cx| {
+ // menu.show(
+ // position,
+ // AnchorCorner::TopLeft,
+ // if is_active_item {
+ // vec![
+ // ContextMenuItem::action(
+ // "Close Active Item",
+ // CloseActiveItem { save_intent: None },
+ // ),
+ // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+ // ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+ // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
+ // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
+ // ContextMenuItem::action(
+ // "Close All Items",
+ // CloseAllItems { save_intent: None },
+ // ),
+ // ]
+ // } else {
+ // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
+ // vec![
+ // ContextMenuItem::handler("Close Inactive Item", {
+ // let pane = target_pane.clone();
+ // move |cx| {
+ // if let Some(pane) = pane.upgrade(cx) {
+ // pane.update(cx, |pane, cx| {
+ // pane.close_item_by_id(
+ // target_item_id,
+ // SaveIntent::Close,
+ // cx,
+ // )
+ // .detach_and_log_err(cx);
+ // })
+ // }
+ // }
+ // }),
+ // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+ // ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+ // ContextMenuItem::handler("Close Items To The Left", {
+ // let pane = target_pane.clone();
+ // move |cx| {
+ // if let Some(pane) = pane.upgrade(cx) {
+ // pane.update(cx, |pane, cx| {
+ // pane.close_items_to_the_left_by_id(target_item_id, cx)
+ // .detach_and_log_err(cx);
+ // })
+ // }
+ // }
+ // }),
+ // ContextMenuItem::handler("Close Items To The Right", {
+ // let pane = target_pane.clone();
+ // move |cx| {
+ // if let Some(pane) = pane.upgrade(cx) {
+ // pane.update(cx, |pane, cx| {
+ // pane.close_items_to_the_right_by_id(target_item_id, cx)
+ // .detach_and_log_err(cx);
+ // })
+ // }
+ // }
+ // }),
+ // ContextMenuItem::action(
+ // "Close All Items",
+ // CloseAllItems { save_intent: None },
+ // ),
+ // ]
+ // },
+ // cx,
+ // );
+ // });
+ // }
+
+ // pub fn toolbar(&self) -> &ViewHandle<Toolbar> {
+ // &self.toolbar
+ // }
+
+ // pub fn handle_deleted_project_item(
+ // &mut self,
+ // entry_id: ProjectEntryId,
+ // cx: &mut ViewContext<Pane>,
+ // ) -> Option<()> {
+ // let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
+ // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
+ // Some((i, item.id()))
+ // } else {
+ // None
+ // }
+ // })?;
+
+ // self.remove_item(item_index_to_delete, false, cx);
+ // self.nav_history.remove_item(item_id);
+
+ // Some(())
+ // }
+
+ // fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
+ // let active_item = self
+ // .items
+ // .get(self.active_item_index)
+ // .map(|item| item.as_ref());
+ // self.toolbar.update(cx, |toolbar, cx| {
+ // toolbar.set_active_item(active_item, cx);
+ // });
+ // }
+
+ // fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
+ // let theme = theme::current(cx).clone();
+
+ // let pane = cx.handle().downgrade();
+ // let autoscroll = if mem::take(&mut self.autoscroll) {
+ // Some(self.active_item_index)
+ // } else {
+ // None
+ // };
+
+ // let pane_active = self.has_focus;
+
+ // enum Tabs {}
+ // let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
+ // for (ix, (item, detail)) in self
+ // .items
+ // .iter()
+ // .cloned()
+ // .zip(self.tab_details(cx))
+ // .enumerate()
+ // {
+ // let git_status = item
+ // .project_path(cx)
+ // .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
+ // .and_then(|entry| entry.git_status());
+
+ // let detail = if detail == 0 { None } else { Some(detail) };
+ // let tab_active = ix == self.active_item_index;
+
+ // row.add_child({
+ // enum TabDragReceiver {}
+ // let mut receiver =
+ // dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
+ // let item = item.clone();
+ // let pane = pane.clone();
+ // let detail = detail.clone();
+
+ // let theme = theme::current(cx).clone();
+ // let mut tooltip_theme = theme.tooltip.clone();
+ // tooltip_theme.max_text_width = None;
+ // let tab_tooltip_text =
+ // item.tab_tooltip_text(cx).map(|text| text.into_owned());
+
+ // let mut tab_style = theme
+ // .workspace
+ // .tab_bar
+ // .tab_style(pane_active, tab_active)
+ // .clone();
+ // let should_show_status = settings::get::<ItemSettings>(cx).git_status;
+ // if should_show_status && git_status != None {
+ // tab_style.label.text.color = match git_status.unwrap() {
+ // GitFileStatus::Added => tab_style.git.inserted,
+ // GitFileStatus::Modified => tab_style.git.modified,
+ // GitFileStatus::Conflict => tab_style.git.conflict,
+ // };
+ // }
+
+ // move |mouse_state, cx| {
+ // let hovered = mouse_state.hovered();
+
+ // enum Tab {}
+ // let mouse_event_handler =
+ // MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
+ // Self::render_tab(
+ // &item,
+ // pane.clone(),
+ // ix == 0,
+ // detail,
+ // hovered,
+ // &tab_style,
+ // cx,
+ // )
+ // })
+ // .on_down(MouseButton::Left, move |_, this, cx| {
+ // this.activate_item(ix, true, true, cx);
+ // })
+ // .on_click(MouseButton::Middle, {
+ // let item_id = item.id();
+ // move |_, pane, cx| {
+ // pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+ // .detach_and_log_err(cx);
+ // }
+ // })
+ // .on_down(
+ // MouseButton::Right,
+ // move |event, pane, cx| {
+ // pane.deploy_tab_context_menu(event.position, item.id(), cx);
+ // },
+ // );
+
+ // if let Some(tab_tooltip_text) = tab_tooltip_text {
+ // mouse_event_handler
+ // .with_tooltip::<Self>(
+ // ix,
+ // tab_tooltip_text,
+ // None,
+ // tooltip_theme,
+ // cx,
+ // )
+ // .into_any()
+ // } else {
+ // mouse_event_handler.into_any()
+ // }
+ // }
+ // });
+
+ // if !pane_active || !tab_active {
+ // receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
+ // }
+
+ // receiver.as_draggable(
+ // DraggedItem {
+ // handle: item,
+ // pane: pane.clone(),
+ // },
+ // {
+ // let theme = theme::current(cx).clone();
+
+ // let detail = detail.clone();
+ // move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
+ // let tab_style = &theme.workspace.tab_bar.dragged_tab;
+ // Self::render_dragged_tab(
+ // &dragged_item.handle,
+ // dragged_item.pane.clone(),
+ // false,
+ // detail,
+ // false,
+ // &tab_style,
+ // cx,
+ // )
+ // }
+ // },
+ // )
+ // })
+ // }
+
+ // // Use the inactive tab style along with the current pane's active status to decide how to render
+ // // the filler
+ // let filler_index = self.items.len();
+ // let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
+ // enum Filler {}
+ // row.add_child(
+ // dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
+ // Empty::new()
+ // .contained()
+ // .with_style(filler_style.container)
+ // .with_border(filler_style.container.border)
+ // })
+ // .flex(1., true)
+ // .into_any_named("filler"),
+ // );
+
+ // row
+ // }
+
+ // fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
+ // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
+
+ // let mut tab_descriptions = HashMap::default();
+ // let mut done = false;
+ // while !done {
+ // done = true;
+
+ // // Store item indices by their tab description.
+ // for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
+ // if let Some(description) = item.tab_description(*detail, cx) {
+ // if *detail == 0
+ // || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
+ // {
+ // tab_descriptions
+ // .entry(description)
+ // .or_insert(Vec::new())
+ // .push(ix);
+ // }
+ // }
+ // }
+
+ // // If two or more items have the same tab description, increase their level
+ // // of detail and try again.
+ // for (_, item_ixs) in tab_descriptions.drain() {
+ // if item_ixs.len() > 1 {
+ // done = false;
+ // for ix in item_ixs {
+ // tab_details[ix] += 1;
+ // }
+ // }
+ // }
+ // }
+
+ // tab_details
+ // }
+
+ // fn render_tab(
+ // item: &Box<dyn ItemHandle>,
+ // pane: WeakViewHandle<Pane>,
+ // first: bool,
+ // detail: Option<usize>,
+ // hovered: bool,
+ // tab_style: &theme::Tab,
+ // cx: &mut ViewContext<Self>,
+ // ) -> AnyElement<Self> {
+ // let title = item.tab_content(detail, &tab_style, cx);
+ // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+ // }
+
+ // fn render_dragged_tab(
+ // item: &Box<dyn ItemHandle>,
+ // pane: WeakViewHandle<Pane>,
+ // first: bool,
+ // detail: Option<usize>,
+ // hovered: bool,
+ // tab_style: &theme::Tab,
+ // cx: &mut ViewContext<Workspace>,
+ // ) -> AnyElement<Workspace> {
+ // let title = item.dragged_tab_content(detail, &tab_style, cx);
+ // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+ // }
+
+ // fn render_tab_with_title<T: View>(
+ // title: AnyElement<T>,
+ // item: &Box<dyn ItemHandle>,
+ // pane: WeakViewHandle<Pane>,
+ // first: bool,
+ // hovered: bool,
+ // tab_style: &theme::Tab,
+ // cx: &mut ViewContext<T>,
+ // ) -> AnyElement<T> {
+ // let mut container = tab_style.container.clone();
+ // if first {
+ // container.border.left = false;
+ // }
+
+ // let buffer_jewel_element = {
+ // let diameter = 7.0;
+ // let icon_color = if item.has_conflict(cx) {
+ // Some(tab_style.icon_conflict)
+ // } else if item.is_dirty(cx) {
+ // Some(tab_style.icon_dirty)
+ // } else {
+ // None
+ // };
+
+ // Canvas::new(move |bounds, _, _, cx| {
+ // if let Some(color) = icon_color {
+ // let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
+ // cx.scene().push_quad(Quad {
+ // bounds: square,
+ // background: Some(color),
+ // border: Default::default(),
+ // corner_radii: (diameter / 2.).into(),
+ // });
+ // }
+ // })
+ // .constrained()
+ // .with_width(diameter)
+ // .with_height(diameter)
+ // .aligned()
+ // };
+
+ // let title_element = title.aligned().contained().with_style(ContainerStyle {
+ // margin: Margin {
+ // left: tab_style.spacing,
+ // right: tab_style.spacing,
+ // ..Default::default()
+ // },
+ // ..Default::default()
+ // });
+
+ // let close_element = if hovered {
+ // let item_id = item.id();
+ // enum TabCloseButton {}
+ // let icon = Svg::new("icons/x.svg");
+ // MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
+ // if mouse_state.hovered() {
+ // icon.with_color(tab_style.icon_close_active)
+ // } else {
+ // icon.with_color(tab_style.icon_close)
+ // }
+ // })
+ // .with_padding(Padding::uniform(4.))
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, {
+ // let pane = pane.clone();
+ // move |_, _, cx| {
+ // let pane = pane.clone();
+ // cx.window_context().defer(move |cx| {
+ // if let Some(pane) = pane.upgrade(cx) {
+ // pane.update(cx, |pane, cx| {
+ // pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+ // .detach_and_log_err(cx);
+ // });
+ // }
+ // });
+ // }
+ // })
+ // .into_any_named("close-tab-icon")
+ // .constrained()
+ // } else {
+ // Empty::new().constrained()
+ // }
+ // .with_width(tab_style.close_icon_width)
+ // .aligned();
+
+ // let close_right = settings::get::<ItemSettings>(cx).close_position.right();
+
+ // if close_right {
+ // Flex::row()
+ // .with_child(buffer_jewel_element)
+ // .with_child(title_element)
+ // .with_child(close_element)
+ // } else {
+ // Flex::row()
+ // .with_child(close_element)
+ // .with_child(title_element)
+ // .with_child(buffer_jewel_element)
+ // }
+ // .contained()
+ // .with_style(container)
+ // .constrained()
+ // .with_height(tab_style.height)
+ // .into_any()
+ // }
+
+ // pub fn render_tab_bar_button<
+ // F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
+ // F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
+ // >(
+ // index: usize,
+ // icon: &'static str,
+ // is_active: bool,
+ // tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
+ // cx: &mut ViewContext<Pane>,
+ // on_click: F1,
+ // on_down: F2,
+ // context_menu: Option<ViewHandle<ContextMenu>>,
+ // ) -> AnyElement<Pane> {
+ // enum TabBarButton {}
+
+ // let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
+ // let theme = &settings2::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
+ // let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
+ // Svg::new(icon)
+ // .with_color(style.color)
+ // .constrained()
+ // .with_width(style.icon_width)
+ // .aligned()
+ // .constrained()
+ // .with_width(style.button_width)
+ // .with_height(style.button_width)
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
+ // .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
+ // .into_any();
+ // if let Some((tooltip, action)) = tooltip {
+ // let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
+ // button = button
+ // .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
+ // .into_any();
+ // }
+
+ // Stack::new()
+ // .with_child(button)
+ // .with_children(
+ // context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
+ // )
+ // .flex(1., false)
+ // .into_any_named("tab bar button")
+ // }
+
+ // fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ // let background = theme.workspace.background;
+ // Empty::new()
+ // .contained()
+ // .with_background_color(background)
+ // .into_any()
+ // }
+
+ // pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
+ // self.zoomed = zoomed;
+ // cx.notify();
+ // }
+
+ // pub fn is_zoomed(&self) -> bool {
+ // self.zoomed
+ // }
+ // }
+
+ // impl Entity for Pane {
+ // type Event = Event;
+ // }
+
+ // impl View for Pane {
+ // fn ui_name() -> &'static str {
+ // "Pane"
+ // }
+
+ // fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ // enum MouseNavigationHandler {}
+
+ // MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
+ // let active_item_index = self.active_item_index;
+
+ // if let Some(active_item) = self.active_item() {
+ // Flex::column()
+ // .with_child({
+ // let theme = theme::current(cx).clone();
+
+ // let mut stack = Stack::new();
+
+ // enum TabBarEventHandler {}
+ // stack.add_child(
+ // MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
+ // Empty::new()
+ // .contained()
+ // .with_style(theme.workspace.tab_bar.container)
+ // })
+ // .on_down(
+ // MouseButton::Left,
+ // move |_, this, cx| {
+ // this.activate_item(active_item_index, true, true, cx);
+ // },
+ // ),
+ // );
+ // let tooltip_style = theme.tooltip.clone();
+ // let tab_bar_theme = theme.workspace.tab_bar.clone();
+
+ // let nav_button_height = tab_bar_theme.height;
+ // let button_style = tab_bar_theme.nav_button;
+ // let border_for_nav_buttons = tab_bar_theme
+ // .tab_style(false, false)
+ // .container
+ // .border
+ // .clone();
+
+ // let mut tab_row = Flex::row()
+ // .with_child(nav_button(
+ // "icons/arrow_left.svg",
+ // button_style.clone(),
+ // nav_button_height,
+ // tooltip_style.clone(),
+ // self.can_navigate_backward(),
+ // {
+ // move |pane, cx| {
+ // if let Some(workspace) = pane.workspace.upgrade(cx) {
+ // let pane = cx.weak_handle();
+ // cx.window_context().defer(move |cx| {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace
+ // .go_back(pane, cx)
+ // .detach_and_log_err(cx)
+ // })
+ // })
+ // }
+ // }
+ // },
+ // super::GoBack,
+ // "Go Back",
+ // cx,
+ // ))
+ // .with_child(
+ // nav_button(
+ // "icons/arrow_right.svg",
+ // button_style.clone(),
+ // nav_button_height,
+ // tooltip_style,
+ // self.can_navigate_forward(),
+ // {
+ // move |pane, cx| {
+ // if let Some(workspace) = pane.workspace.upgrade(cx) {
+ // let pane = cx.weak_handle();
+ // cx.window_context().defer(move |cx| {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace
+ // .go_forward(pane, cx)
+ // .detach_and_log_err(cx)
+ // })
+ // })
+ // }
+ // }
+ // },
+ // super::GoForward,
+ // "Go Forward",
+ // cx,
+ // )
+ // .contained()
+ // .with_border(border_for_nav_buttons),
+ // )
+ // .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
+
+ // if self.has_focus {
+ // let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
+ // tab_row.add_child(
+ // (render_tab_bar_buttons)(self, cx)
+ // .contained()
+ // .with_style(theme.workspace.tab_bar.pane_button_container)
+ // .flex(1., false)
+ // .into_any(),
+ // )
+ // }
+
+ // stack.add_child(tab_row);
+ // stack
+ // .constrained()
+ // .with_height(theme.workspace.tab_bar.height)
+ // .flex(1., false)
+ // .into_any_named("tab bar")
+ // })
+ // .with_child({
+ // enum PaneContentTabDropTarget {}
+ // dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
+ // self,
+ // 0,
+ // self.active_item_index + 1,
+ // !self.can_split,
+ // if self.can_split { Some(100.) } else { None },
+ // cx,
+ // {
+ // let toolbar = self.toolbar.clone();
+ // let toolbar_hidden = toolbar.read(cx).hidden();
+ // move |_, cx| {
+ // Flex::column()
+ // .with_children(
+ // (!toolbar_hidden)
+ // .then(|| ChildView::new(&toolbar, cx).expanded()),
+ // )
+ // .with_child(
+ // ChildView::new(active_item.as_any(), cx).flex(1., true),
+ // )
+ // }
+ // },
+ // )
+ // .flex(1., true)
+ // })
+ // .with_child(ChildView::new(&self.tab_context_menu, cx))
+ // .into_any()
+ // } else {
+ // enum EmptyPane {}
+ // let theme = theme::current(cx).clone();
+
+ // dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
+ // self.render_blank_pane(&theme, cx)
+ // })
+ // .on_down(MouseButton::Left, |_, _, cx| {
+ // cx.focus_parent();
+ // })
+ // .into_any()
+ // }
+ // })
+ // .on_down(
+ // MouseButton::Navigate(NavigationDirection::Back),
+ // move |_, pane, cx| {
+ // if let Some(workspace) = pane.workspace.upgrade(cx) {
+ // let pane = cx.weak_handle();
+ // cx.window_context().defer(move |cx| {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.go_back(pane, cx).detach_and_log_err(cx)
+ // })
+ // })
+ // }
+ // },
+ // )
+ // .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
+ // move |_, pane, cx| {
+ // if let Some(workspace) = pane.workspace.upgrade(cx) {
+ // let pane = cx.weak_handle();
+ // cx.window_context().defer(move |cx| {
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.go_forward(pane, cx).detach_and_log_err(cx)
+ // })
+ // })
+ // }
+ // }
+ // })
+ // .into_any_named("pane")
+ // }
+
+ // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+ // if !self.has_focus {
+ // self.has_focus = true;
+ // cx.emit(Event::Focus);
+ // cx.notify();
+ // }
+
+ // self.toolbar.update(cx, |toolbar, cx| {
+ // toolbar.focus_changed(true, cx);
+ // });
+
+ // if let Some(active_item) = self.active_item() {
+ // if cx.is_self_focused() {
+ // // Pane was focused directly. We need to either focus a view inside the active item,
+ // // or focus the active item itself
+ // if let Some(weak_last_focused_view) =
+ // self.last_focused_view_by_item.get(&active_item.id())
+ // {
+ // if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
+ // cx.focus(&last_focused_view);
+ // return;
+ // } else {
+ // self.last_focused_view_by_item.remove(&active_item.id());
+ // }
+ // }
+
+ // cx.focus(active_item.as_any());
+ // } else if focused != self.tab_bar_context_menu.handle {
+ // self.last_focused_view_by_item
+ // .insert(active_item.id(), focused.downgrade());
+ // }
+ // }
+ // }
+
+ // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+ // self.has_focus = false;
+ // self.toolbar.update(cx, |toolbar, cx| {
+ // toolbar.focus_changed(false, cx);
+ // });
+ // cx.notify();
+ // }
+
+ // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+ // Self::reset_to_default_keymap_context(keymap);
+ // }
+ // }
+
+ // impl ItemNavHistory {
+ // pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
+ // self.history.push(data, self.item.clone(), cx);
+ // }
+
+ // pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
+ // self.history.pop(NavigationMode::GoingBack, cx)
+ // }
+
+ // pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
+ // self.history.pop(NavigationMode::GoingForward, cx)
+ // }
+ // }
+
+ // impl NavHistory {
+ // pub fn for_each_entry(
+ // &self,
+ // cx: &AppContext,
+ // mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
+ // ) {
+ // let borrowed_history = self.0.borrow();
+ // borrowed_history
+ // .forward_stack
+ // .iter()
+ // .chain(borrowed_history.backward_stack.iter())
+ // .chain(borrowed_history.closed_stack.iter())
+ // .for_each(|entry| {
+ // if let Some(project_and_abs_path) =
+ // borrowed_history.paths_by_item.get(&entry.item.id())
+ // {
+ // f(entry, project_and_abs_path.clone());
+ // } else if let Some(item) = entry.item.upgrade(cx) {
+ // if let Some(path) = item.project_path(cx) {
+ // f(entry, (path, None));
+ // }
+ // }
+ // })
+ // }
+
+ // pub fn set_mode(&mut self, mode: NavigationMode) {
+ // self.0.borrow_mut().mode = mode;
+ // }
pub fn mode(&self) -> NavigationMode {
self.0.borrow().mode
}
- pub fn disable(&mut self) {
- self.0.borrow_mut().mode = NavigationMode::Disabled;
- }
-
- pub fn enable(&mut self) {
- self.0.borrow_mut().mode = NavigationMode::Normal;
- }
-
- pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
- let mut state = self.0.borrow_mut();
- let entry = match mode {
- NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
- return None
- }
- NavigationMode::GoingBack => &mut state.backward_stack,
- NavigationMode::GoingForward => &mut state.forward_stack,
- NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
- }
- .pop_back();
- if entry.is_some() {
- state.did_update(cx);
- }
- entry
- }
-
- pub fn push<D: 'static + Any>(
- &mut self,
- data: Option<D>,
- item: Rc<dyn WeakItemHandle>,
- cx: &mut WindowContext,
- ) {
- let state = &mut *self.0.borrow_mut();
- match state.mode {
- NavigationMode::Disabled => {}
- NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
- if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
- state.backward_stack.pop_front();
- }
- state.backward_stack.push_back(NavigationEntry {
- item,
- data: data.map(|data| Box::new(data) as Box<dyn Any>),
- timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
- });
- state.forward_stack.clear();
- }
- NavigationMode::GoingBack => {
- if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
- state.forward_stack.pop_front();
- }
- state.forward_stack.push_back(NavigationEntry {
- item,
- data: data.map(|data| Box::new(data) as Box<dyn Any>),
- timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
- });
- }
- NavigationMode::GoingForward => {
- if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
- state.backward_stack.pop_front();
- }
- state.backward_stack.push_back(NavigationEntry {
- item,
- data: data.map(|data| Box::new(data) as Box<dyn Any>),
- timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
- });
- }
- NavigationMode::ClosingItem => {
- if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
- state.closed_stack.pop_front();
- }
- state.closed_stack.push_back(NavigationEntry {
- item,
- data: data.map(|data| Box::new(data) as Box<dyn Any>),
- timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
- });
- }
- }
- state.did_update(cx);
- }
-
- pub fn remove_item(&mut self, item_id: usize) {
- let mut state = self.0.borrow_mut();
- state.paths_by_item.remove(&item_id);
- state
- .backward_stack
- .retain(|entry| entry.item.id() != item_id);
- state
- .forward_stack
- .retain(|entry| entry.item.id() != item_id);
- state
- .closed_stack
- .retain(|entry| entry.item.id() != item_id);
- }
-
- pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
- self.0.borrow().paths_by_item.get(&item_id).cloned()
- }
-}
-
-impl NavHistoryState {
- pub fn did_update(&self, cx: &mut WindowContext) {
- if let Some(pane) = self.pane.upgrade(cx) {
- cx.defer(move |cx| {
- pane.update(cx, |pane, cx| pane.history_updated(cx));
- });
- }
- }
-}
-
-pub struct PaneBackdrop<V> {
- child_view: usize,
- child: AnyElement<V>,
+ // pub fn disable(&mut self) {
+ // self.0.borrow_mut().mode = NavigationMode::Disabled;
+ // }
+
+ // pub fn enable(&mut self) {
+ // self.0.borrow_mut().mode = NavigationMode::Normal;
+ // }
+
+ // pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
+ // let mut state = self.0.borrow_mut();
+ // let entry = match mode {
+ // NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
+ // return None
+ // }
+ // NavigationMode::GoingBack => &mut state.backward_stack,
+ // NavigationMode::GoingForward => &mut state.forward_stack,
+ // NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
+ // }
+ // .pop_back();
+ // if entry.is_some() {
+ // state.did_update(cx);
+ // }
+ // entry
+ // }
+
+ // pub fn push<D: 'static + Any>(
+ // &mut self,
+ // data: Option<D>,
+ // item: Rc<dyn WeakItemHandle>,
+ // cx: &mut WindowContext,
+ // ) {
+ // let state = &mut *self.0.borrow_mut();
+ // match state.mode {
+ // NavigationMode::Disabled => {}
+ // NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
+ // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+ // state.backward_stack.pop_front();
+ // }
+ // state.backward_stack.push_back(NavigationEntry {
+ // item,
+ // data: data.map(|data| Box::new(data) as Box<dyn Any>),
+ // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+ // });
+ // state.forward_stack.clear();
+ // }
+ // NavigationMode::GoingBack => {
+ // if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+ // state.forward_stack.pop_front();
+ // }
+ // state.forward_stack.push_back(NavigationEntry {
+ // item,
+ // data: data.map(|data| Box::new(data) as Box<dyn Any>),
+ // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+ // });
+ // }
+ // NavigationMode::GoingForward => {
+ // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+ // state.backward_stack.pop_front();
+ // }
+ // state.backward_stack.push_back(NavigationEntry {
+ // item,
+ // data: data.map(|data| Box::new(data) as Box<dyn Any>),
+ // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+ // });
+ // }
+ // NavigationMode::ClosingItem => {
+ // if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
+ // state.closed_stack.pop_front();
+ // }
+ // state.closed_stack.push_back(NavigationEntry {
+ // item,
+ // data: data.map(|data| Box::new(data) as Box<dyn Any>),
+ // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
+ // });
+ // }
+ // }
+ // state.did_update(cx);
+ // }
+
+ // pub fn remove_item(&mut self, item_id: usize) {
+ // let mut state = self.0.borrow_mut();
+ // state.paths_by_item.remove(&item_id);
+ // state
+ // .backward_stack
+ // .retain(|entry| entry.item.id() != item_id);
+ // state
+ // .forward_stack
+ // .retain(|entry| entry.item.id() != item_id);
+ // state
+ // .closed_stack
+ // .retain(|entry| entry.item.id() != item_id);
+ // }
+
+ // pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
+ // self.0.borrow().paths_by_item.get(&item_id).cloned()
+ // }
}
-impl<V> PaneBackdrop<V> {
- pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
- PaneBackdrop {
- child,
- child_view: pane_item_view,
- }
- }
-}
-
-impl<V: 'static> Element<V> for PaneBackdrop<V> {
- type LayoutState = ();
-
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: gpui::SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, view, cx);
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let background = theme::current(cx).editor.background;
-
- let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
- cx.scene().push_quad(gpui::Quad {
- bounds: RectF::new(bounds.origin(), bounds.size()),
- background: Some(background),
- ..Default::default()
- });
-
- let child_view_id = self.child_view;
- cx.scene().push_mouse_region(
- MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
- gpui::platform::MouseButton::Left,
- move |_, _: &mut V, cx| {
- let window = cx.window();
- cx.app_context().focus(window, Some(child_view_id))
- },
- ),
- );
-
- cx.scene().push_layer(Some(bounds));
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- cx.scene().pop_layer();
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: std::ops::Range<usize>,
- _bounds: RectF,
- _visible_bounds: RectF,
- _layout: &Self::LayoutState,
- _paint: &Self::PaintState,
- view: &V,
- cx: &gpui::ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _bounds: RectF,
- _layout: &Self::LayoutState,
- _paint: &Self::PaintState,
- view: &V,
- cx: &gpui::ViewContext<V>,
- ) -> serde_json::Value {
- gpui::json::json!({
- "type": "Pane Back Drop",
- "view": self.child_view,
- "child": self.child.debug(view, cx),
- })
- }
-}
-
-fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
- let path = buffer_path
- .as_ref()
- .and_then(|p| p.path.to_str())
- .unwrap_or(&"This buffer");
- let path = truncate_and_remove_front(path, 80);
- format!("{path} contains unsaved edits. Do you want to save it?")
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::item::test::{TestItem, TestProjectItem};
- use gpui::TestAppContext;
- use project::FakeFs;
- use settings::SettingsStore;
-
- #[gpui::test]
- async fn test_remove_active_empty(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- pane.update(cx, |pane, cx| {
- assert!(pane
- .close_active_item(&CloseActiveItem { save_intent: None }, cx)
- .is_none())
- });
- }
-
- #[gpui::test]
- async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
- cx.foreground().forbid_parking();
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- // 1. Add with a destination index
- // a. Add before the active item
- set_labeled_items(&pane, ["A", "B*", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(
- Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
- false,
- false,
- Some(0),
- cx,
- );
- });
- assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
-
- // b. Add after the active item
- set_labeled_items(&pane, ["A", "B*", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(
- Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
- false,
- false,
- Some(2),
- cx,
- );
- });
- assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
-
- // c. Add at the end of the item list (including off the length)
- set_labeled_items(&pane, ["A", "B*", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(
- Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
- false,
- false,
- Some(5),
- cx,
- );
- });
- assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
- // 2. Add without a destination index
- // a. Add with active item at the start of the item list
- set_labeled_items(&pane, ["A*", "B", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(
- Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
- false,
- false,
- None,
- cx,
- );
- });
- set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
-
- // b. Add with active item at the end of the item list
- set_labeled_items(&pane, ["A", "B", "C*"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(
- Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
- false,
- false,
- None,
- cx,
- );
- });
- assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
- }
-
- #[gpui::test]
- async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
- cx.foreground().forbid_parking();
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- // 1. Add with a destination index
- // 1a. Add before the active item
- let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(d, false, false, Some(0), cx);
- });
- assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
-
- // 1b. Add after the active item
- let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(d, false, false, Some(2), cx);
- });
- assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
-
- // 1c. Add at the end of the item list (including off the length)
- let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(a, false, false, Some(5), cx);
- });
- assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
-
- // 1d. Add same item to active index
- let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(b, false, false, Some(1), cx);
- });
- assert_item_labels(&pane, ["A", "B*", "C"], cx);
-
- // 1e. Add item to index after same item in last position
- let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(c, false, false, Some(2), cx);
- });
- assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
- // 2. Add without a destination index
- // 2a. Add with active item at the start of the item list
- let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(d, false, false, None, cx);
- });
- assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
-
- // 2b. Add with active item at the end of the item list
- let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(a, false, false, None, cx);
- });
- assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
-
- // 2c. Add active item to active item at end of list
- let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(c, false, false, None, cx);
- });
- assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
- // 2d. Add active item to active item at start of list
- let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
- pane.update(cx, |pane, cx| {
- pane.add_item(a, false, false, None, cx);
- });
- assert_item_labels(&pane, ["A*", "B", "C"], cx);
- }
-
- #[gpui::test]
- async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
- cx.foreground().forbid_parking();
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- // singleton view
- pane.update(cx, |pane, cx| {
- let item = TestItem::new()
- .with_singleton(true)
- .with_label("buffer 1")
- .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
-
- pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
- });
- assert_item_labels(&pane, ["buffer 1*"], cx);
-
- // new singleton view with the same project entry
- pane.update(cx, |pane, cx| {
- let item = TestItem::new()
- .with_singleton(true)
- .with_label("buffer 1")
- .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
-
- pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
- });
- assert_item_labels(&pane, ["buffer 1*"], cx);
-
- // new singleton view with different project entry
- pane.update(cx, |pane, cx| {
- let item = TestItem::new()
- .with_singleton(true)
- .with_label("buffer 2")
- .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
- pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
- });
- assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
-
- // new multibuffer view with the same project entry
- pane.update(cx, |pane, cx| {
- let item = TestItem::new()
- .with_singleton(false)
- .with_label("multibuffer 1")
- .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
-
- pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
- });
- assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
-
- // another multibuffer view with the same project entry
- pane.update(cx, |pane, cx| {
- let item = TestItem::new()
- .with_singleton(false)
- .with_label("multibuffer 1b")
- .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
-
- pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
- });
- assert_item_labels(
- &pane,
- ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
- cx,
- );
- }
-
- #[gpui::test]
- async fn test_remove_item_ordering(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- add_labeled_item(&pane, "A", false, cx);
- add_labeled_item(&pane, "B", false, cx);
- add_labeled_item(&pane, "C", false, cx);
- add_labeled_item(&pane, "D", false, cx);
- assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
- pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
- add_labeled_item(&pane, "1", false, cx);
- assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
-
- pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
- assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["A", "B*", "C"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["A", "C*"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["A*"], cx);
- }
-
- #[gpui::test]
- async fn test_close_inactive_items(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_inactive_items(&CloseInactiveItems, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["C*"], cx);
- }
-
- #[gpui::test]
- async fn test_close_clean_items(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- add_labeled_item(&pane, "A", true, cx);
- add_labeled_item(&pane, "B", false, cx);
- add_labeled_item(&pane, "C", true, cx);
- add_labeled_item(&pane, "D", false, cx);
- add_labeled_item(&pane, "E", false, cx);
- assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
-
- pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["A^", "C*^"], cx);
- }
-
- #[gpui::test]
- async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["C*", "D", "E"], cx);
- }
-
- #[gpui::test]
- async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, ["A", "B", "C*"], cx);
- }
-
- #[gpui::test]
- async fn test_close_all_items(cx: &mut TestAppContext) {
- init_test(cx);
- let fs = FakeFs::new(cx.background());
-
- let project = Project::test(fs, None, cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
- add_labeled_item(&pane, "A", false, cx);
- add_labeled_item(&pane, "B", false, cx);
- add_labeled_item(&pane, "C", false, cx);
- assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
- pane.update(cx, |pane, cx| {
- pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
- })
- .unwrap()
- .await
- .unwrap();
- assert_item_labels(&pane, [], cx);
-
- add_labeled_item(&pane, "A", true, cx);
- add_labeled_item(&pane, "B", true, cx);
- add_labeled_item(&pane, "C", true, cx);
- assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
-
- let save = pane
- .update(cx, |pane, cx| {
- pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
- })
- .unwrap();
-
- cx.foreground().run_until_parked();
- window.simulate_prompt_answer(2, cx);
- save.await.unwrap();
- assert_item_labels(&pane, [], cx);
- }
-
- fn init_test(cx: &mut TestAppContext) {
- cx.update(|cx| {
- cx.set_global(SettingsStore::test(cx));
- theme::init((), cx);
- crate::init_settings(cx);
- Project::init_settings(cx);
- });
- }
-
- fn add_labeled_item(
- pane: &ViewHandle<Pane>,
- label: &str,
- is_dirty: bool,
- cx: &mut TestAppContext,
- ) -> Box<ViewHandle<TestItem>> {
- pane.update(cx, |pane, cx| {
- let labeled_item =
- Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
- pane.add_item(labeled_item.clone(), false, false, None, cx);
- labeled_item
- })
- }
-
- fn set_labeled_items<const COUNT: usize>(
- pane: &ViewHandle<Pane>,
- labels: [&str; COUNT],
- cx: &mut TestAppContext,
- ) -> [Box<ViewHandle<TestItem>>; COUNT] {
- pane.update(cx, |pane, cx| {
- pane.items.clear();
- let mut active_item_index = 0;
-
- let mut index = 0;
- let items = labels.map(|mut label| {
- if label.ends_with("*") {
- label = label.trim_end_matches("*");
- active_item_index = index;
- }
-
- let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
- pane.add_item(labeled_item.clone(), false, false, None, cx);
- index += 1;
- labeled_item
- });
-
- pane.activate_item(active_item_index, false, false, cx);
-
- items
- })
- }
-
- // Assert the item label, with the active item label suffixed with a '*'
- fn assert_item_labels<const COUNT: usize>(
- pane: &ViewHandle<Pane>,
- expected_states: [&str; COUNT],
- cx: &mut TestAppContext,
- ) {
- pane.read_with(cx, |pane, cx| {
- let actual_states = pane
- .items
- .iter()
- .enumerate()
- .map(|(ix, item)| {
- let mut state = item
- .as_any()
- .downcast_ref::<TestItem>()
- .unwrap()
- .read(cx)
- .label
- .clone();
- if ix == pane.active_item_index {
- state.push('*');
- }
- if item.is_dirty(cx) {
- state.push('^');
- }
- state
- })
- .collect::<Vec<_>>();
-
- assert_eq!(
- actual_states, expected_states,
- "pane items do not match expectation"
- );
- })
- }
-}
+// impl NavHistoryState {
+// pub fn did_update(&self, cx: &mut WindowContext) {
+// if let Some(pane) = self.pane.upgrade(cx) {
+// cx.defer(move |cx| {
+// pane.update(cx, |pane, cx| pane.history_updated(cx));
+// });
+// }
+// }
+// }
+
+// pub struct PaneBackdrop<V> {
+// child_view: usize,
+// child: AnyElement<V>,
+// }
+
+// impl<V> PaneBackdrop<V> {
+// pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
+// PaneBackdrop {
+// child,
+// child_view: pane_item_view,
+// }
+// }
+// }
+
+// impl<V: 'static> Element<V> for PaneBackdrop<V> {
+// type LayoutState = ();
+
+// type PaintState = ();
+
+// fn layout(
+// &mut self,
+// constraint: gpui::SizeConstraint,
+// view: &mut V,
+// cx: &mut ViewContext<V>,
+// ) -> (Vector2F, Self::LayoutState) {
+// let size = self.child.layout(constraint, view, cx);
+// (size, ())
+// }
+
+// fn paint(
+// &mut self,
+// bounds: RectF,
+// visible_bounds: RectF,
+// _: &mut Self::LayoutState,
+// view: &mut V,
+// cx: &mut ViewContext<V>,
+// ) -> Self::PaintState {
+// let background = theme::current(cx).editor.background;
+
+// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+// cx.scene().push_quad(gpui::Quad {
+// bounds: RectF::new(bounds.origin(), bounds.size()),
+// background: Some(background),
+// ..Default::default()
+// });
+
+// let child_view_id = self.child_view;
+// cx.scene().push_mouse_region(
+// MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
+// gpui::platform::MouseButton::Left,
+// move |_, _: &mut V, cx| {
+// let window = cx.window();
+// cx.app_context().focus(window, Some(child_view_id))
+// },
+// ),
+// );
+
+// cx.scene().push_layer(Some(bounds));
+// self.child.paint(bounds.origin(), visible_bounds, view, cx);
+// cx.scene().pop_layer();
+// }
+
+// fn rect_for_text_range(
+// &self,
+// range_utf16: std::ops::Range<usize>,
+// _bounds: RectF,
+// _visible_bounds: RectF,
+// _layout: &Self::LayoutState,
+// _paint: &Self::PaintState,
+// view: &V,
+// cx: &gpui::ViewContext<V>,
+// ) -> Option<RectF> {
+// self.child.rect_for_text_range(range_utf16, view, cx)
+// }
+
+// fn debug(
+// &self,
+// _bounds: RectF,
+// _layout: &Self::LayoutState,
+// _paint: &Self::PaintState,
+// view: &V,
+// cx: &gpui::ViewContext<V>,
+// ) -> serde_json::Value {
+// gpui::json::json!({
+// "type": "Pane Back Drop",
+// "view": self.child_view,
+// "child": self.child.debug(view, cx),
+// })
+// }
+// }
+
+// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
+// let path = buffer_path
+// .as_ref()
+// .and_then(|p| p.path.to_str())
+// .unwrap_or(&"This buffer");
+// let path = truncate_and_remove_front(path, 80);
+// format!("{path} contains unsaved edits. Do you want to save it?")
+// }
+
+// todo!("uncomment tests")
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+// use crate::item::test::{TestItem, TestProjectItem};
+// use gpui::TestAppContext;
+// use project::FakeFs;
+// use settings::SettingsStore;
+
+// #[gpui::test]
+// async fn test_remove_active_empty(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// pane.update(cx, |pane, cx| {
+// assert!(pane
+// .close_active_item(&CloseActiveItem { save_intent: None }, cx)
+// .is_none())
+// });
+// }
+
+// #[gpui::test]
+// async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
+// cx.foreground().forbid_parking();
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// // 1. Add with a destination index
+// // a. Add before the active item
+// set_labeled_items(&pane, ["A", "B*", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(
+// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+// false,
+// false,
+// Some(0),
+// cx,
+// );
+// });
+// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
+
+// // b. Add after the active item
+// set_labeled_items(&pane, ["A", "B*", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(
+// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+// false,
+// false,
+// Some(2),
+// cx,
+// );
+// });
+// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
+
+// // c. Add at the end of the item list (including off the length)
+// set_labeled_items(&pane, ["A", "B*", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(
+// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+// false,
+// false,
+// Some(5),
+// cx,
+// );
+// });
+// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+// // 2. Add without a destination index
+// // a. Add with active item at the start of the item list
+// set_labeled_items(&pane, ["A*", "B", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(
+// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+// false,
+// false,
+// None,
+// cx,
+// );
+// });
+// set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
+
+// // b. Add with active item at the end of the item list
+// set_labeled_items(&pane, ["A", "B", "C*"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(
+// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
+// false,
+// false,
+// None,
+// cx,
+// );
+// });
+// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
+// cx.foreground().forbid_parking();
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// // 1. Add with a destination index
+// // 1a. Add before the active item
+// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(d, false, false, Some(0), cx);
+// });
+// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
+
+// // 1b. Add after the active item
+// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(d, false, false, Some(2), cx);
+// });
+// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
+
+// // 1c. Add at the end of the item list (including off the length)
+// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(a, false, false, Some(5), cx);
+// });
+// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
+
+// // 1d. Add same item to active index
+// let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(b, false, false, Some(1), cx);
+// });
+// assert_item_labels(&pane, ["A", "B*", "C"], cx);
+
+// // 1e. Add item to index after same item in last position
+// let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(c, false, false, Some(2), cx);
+// });
+// assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+// // 2. Add without a destination index
+// // 2a. Add with active item at the start of the item list
+// let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(d, false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
+
+// // 2b. Add with active item at the end of the item list
+// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(a, false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
+
+// // 2c. Add active item to active item at end of list
+// let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(c, false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+// // 2d. Add active item to active item at start of list
+// let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
+// pane.update(cx, |pane, cx| {
+// pane.add_item(a, false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["A*", "B", "C"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
+// cx.foreground().forbid_parking();
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// // singleton view
+// pane.update(cx, |pane, cx| {
+// let item = TestItem::new()
+// .with_singleton(true)
+// .with_label("buffer 1")
+// .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
+
+// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["buffer 1*"], cx);
+
+// // new singleton view with the same project entry
+// pane.update(cx, |pane, cx| {
+// let item = TestItem::new()
+// .with_singleton(true)
+// .with_label("buffer 1")
+// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["buffer 1*"], cx);
+
+// // new singleton view with different project entry
+// pane.update(cx, |pane, cx| {
+// let item = TestItem::new()
+// .with_singleton(true)
+// .with_label("buffer 2")
+// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
+// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
+
+// // new multibuffer view with the same project entry
+// pane.update(cx, |pane, cx| {
+// let item = TestItem::new()
+// .with_singleton(false)
+// .with_label("multibuffer 1")
+// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+// });
+// assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
+
+// // another multibuffer view with the same project entry
+// pane.update(cx, |pane, cx| {
+// let item = TestItem::new()
+// .with_singleton(false)
+// .with_label("multibuffer 1b")
+// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
+
+// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
+// });
+// assert_item_labels(
+// &pane,
+// ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
+// cx,
+// );
+// }
+
+// #[gpui::test]
+// async fn test_remove_item_ordering(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// add_labeled_item(&pane, "A", false, cx);
+// add_labeled_item(&pane, "B", false, cx);
+// add_labeled_item(&pane, "C", false, cx);
+// add_labeled_item(&pane, "D", false, cx);
+// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+// pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
+// add_labeled_item(&pane, "1", false, cx);
+// assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
+
+// pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
+// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["A", "B*", "C"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["A", "C*"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["A*"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_close_inactive_items(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_inactive_items(&CloseInactiveItems, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["C*"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_close_clean_items(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// add_labeled_item(&pane, "A", true, cx);
+// add_labeled_item(&pane, "B", false, cx);
+// add_labeled_item(&pane, "C", true, cx);
+// add_labeled_item(&pane, "D", false, cx);
+// add_labeled_item(&pane, "E", false, cx);
+// assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
+
+// pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["A^", "C*^"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["C*", "D", "E"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, ["A", "B", "C*"], cx);
+// }
+
+// #[gpui::test]
+// async fn test_close_all_items(cx: &mut TestAppContext) {
+// init_test(cx);
+// let fs = FakeFs::new(cx.background());
+
+// let project = Project::test(fs, None, cx).await;
+// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+// let workspace = window.root(cx);
+// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+// add_labeled_item(&pane, "A", false, cx);
+// add_labeled_item(&pane, "B", false, cx);
+// add_labeled_item(&pane, "C", false, cx);
+// assert_item_labels(&pane, ["A", "B", "C*"], cx);
+
+// pane.update(cx, |pane, cx| {
+// pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap();
+// assert_item_labels(&pane, [], cx);
+
+// add_labeled_item(&pane, "A", true, cx);
+// add_labeled_item(&pane, "B", true, cx);
+// add_labeled_item(&pane, "C", true, cx);
+// assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
+
+// let save = pane
+// .update(cx, |pane, cx| {
+// pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
+// })
+// .unwrap();
+
+// cx.foreground().run_until_parked();
+// window.simulate_prompt_answer(2, cx);
+// save.await.unwrap();
+// assert_item_labels(&pane, [], cx);
+// }
+
+// fn init_test(cx: &mut TestAppContext) {
+// cx.update(|cx| {
+// cx.set_global(SettingsStore::test(cx));
+// theme::init((), cx);
+// crate::init_settings(cx);
+// Project::init_settings(cx);
+// });
+// }
+
+// fn add_labeled_item(
+// pane: &ViewHandle<Pane>,
+// label: &str,
+// is_dirty: bool,
+// cx: &mut TestAppContext,
+// ) -> Box<ViewHandle<TestItem>> {
+// pane.update(cx, |pane, cx| {
+// let labeled_item =
+// Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
+// pane.add_item(labeled_item.clone(), false, false, None, cx);
+// labeled_item
+// })
+// }
+
+// fn set_labeled_items<const COUNT: usize>(
+// pane: &ViewHandle<Pane>,
+// labels: [&str; COUNT],
+// cx: &mut TestAppContext,
+// ) -> [Box<ViewHandle<TestItem>>; COUNT] {
+// pane.update(cx, |pane, cx| {
+// pane.items.clear();
+// let mut active_item_index = 0;
+
+// let mut index = 0;
+// let items = labels.map(|mut label| {
+// if label.ends_with("*") {
+// label = label.trim_end_matches("*");
+// active_item_index = index;
+// }
+
+// let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
+// pane.add_item(labeled_item.clone(), false, false, None, cx);
+// index += 1;
+// labeled_item
+// });
+
+// pane.activate_item(active_item_index, false, false, cx);
+
+// items
+// })
+// }
+
+// // Assert the item label, with the active item label suffixed with a '*'
+// fn assert_item_labels<const COUNT: usize>(
+// pane: &ViewHandle<Pane>,
+// expected_states: [&str; COUNT],
+// cx: &mut TestAppContext,
+// ) {
+// pane.read_with(cx, |pane, cx| {
+// let actual_states = pane
+// .items
+// .iter()
+// .enumerate()
+// .map(|(ix, item)| {
+// let mut state = item
+// .as_any()
+// .downcast_ref::<TestItem>()
+// .unwrap()
+// .read(cx)
+// .label
+// .clone();
+// if ix == pane.active_item_index {
+// state.push('*');
+// }
+// if item.is_dirty(cx) {
+// state.push('^');
+// }
+// state
+// })
+// .collect::<Vec<_>>();
+
+// assert_eq!(
+// actual_states, expected_states,
+// "pane items do not match expectation"
+// );
+// })
+// }
+// }
@@ -1,22 +1,22 @@
use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, Result};
-use call::{ActiveCall, ParticipantLocation};
+use call2::{ActiveCall, ParticipantLocation};
use collections::HashMap;
-use gpui::{
- elements::*,
- geometry::{rect::RectF, vector::Vector2F},
- platform::{CursorStyle, MouseButton},
- AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
-};
-use project::Project;
+use gpui2::{Bounds, Handle, Pixels, Point, View, ViewContext};
+use project2::Project;
use serde::Deserialize;
use std::{cell::RefCell, rc::Rc, sync::Arc};
-use theme::Theme;
+use theme2::Theme;
const HANDLE_HITBOX_SIZE: f32 = 4.0;
const HORIZONTAL_MIN_SIZE: f32 = 80.;
const VERTICAL_MIN_SIZE: f32 = 100.;
+enum Axis {
+ Vertical,
+ Horizontal,
+}
+
#[derive(Clone, Debug, PartialEq)]
pub struct PaneGroup {
pub(crate) root: Member,
@@ -27,7 +27,7 @@ impl PaneGroup {
Self { root }
}
- pub fn new(pane: ViewHandle<Pane>) -> Self {
+ pub fn new(pane: View<Pane>) -> Self {
Self {
root: Member::Pane(pane),
}
@@ -35,8 +35,8 @@ impl PaneGroup {
pub fn split(
&mut self,
- old_pane: &ViewHandle<Pane>,
- new_pane: &ViewHandle<Pane>,
+ old_pane: &View<Pane>,
+ new_pane: &View<Pane>,
direction: SplitDirection,
) -> Result<()> {
match &mut self.root {
@@ -52,14 +52,14 @@ impl PaneGroup {
}
}
- pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
+ pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
match &self.root {
Member::Pane(_) => None,
Member::Axis(axis) => axis.bounding_box_for_pane(pane),
}
}
- pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
+ pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
match &self.root {
Member::Pane(pane) => Some(pane),
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
@@ -70,7 +70,7 @@ impl PaneGroup {
/// - Ok(true) if it found and removed a pane
/// - Ok(false) if it found but did not remove the pane
/// - Err(_) if it did not find the pane
- pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
+ pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
match &mut self.root {
Member::Pane(_) => Ok(false),
Member::Axis(axis) => {
@@ -82,7 +82,7 @@ impl PaneGroup {
}
}
- pub fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
+ pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
match &mut self.root {
Member::Pane(_) => {}
Member::Axis(axis) => axis.swap(from, to),
@@ -91,11 +91,11 @@ impl PaneGroup {
pub(crate) fn render(
&self,
- project: &ModelHandle<Project>,
+ project: &Handle<Project>,
theme: &Theme,
- follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
- active_call: Option<&ModelHandle<ActiveCall>>,
- active_pane: &ViewHandle<Pane>,
+ follower_states: &HashMap<View<Pane>, FollowerState>,
+ active_call: Option<&Handle<ActiveCall>>,
+ active_pane: &View<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
@@ -113,7 +113,7 @@ impl PaneGroup {
)
}
- pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
+ pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
let mut panes = Vec::new();
self.root.collect_panes(&mut panes);
panes
@@ -123,15 +123,11 @@ impl PaneGroup {
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Member {
Axis(PaneAxis),
- Pane(ViewHandle<Pane>),
+ Pane(View<Pane>),
}
impl Member {
- fn new_axis(
- old_pane: ViewHandle<Pane>,
- new_pane: ViewHandle<Pane>,
- direction: SplitDirection,
- ) -> Self {
+ fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
use Axis::*;
use SplitDirection::*;
@@ -148,7 +144,7 @@ impl Member {
Member::Axis(PaneAxis::new(axis, members))
}
- fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
+ fn contains(&self, needle: &View<Pane>) -> bool {
match self {
Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
Member::Pane(pane) => pane == needle,
@@ -157,12 +153,12 @@ impl Member {
pub fn render(
&self,
- project: &ModelHandle<Project>,
+ project: &Handle<Project>,
basis: usize,
theme: &Theme,
- follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
- active_call: Option<&ModelHandle<ActiveCall>>,
- active_pane: &ViewHandle<Pane>,
+ follower_states: &HashMap<View<Pane>, FollowerState>,
+ active_call: Option<&Handle<ActiveCall>>,
+ active_pane: &View<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
@@ -295,7 +291,7 @@ impl Member {
}
}
- fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
+ fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
match self {
Member::Axis(axis) => {
for member in &axis.members {
@@ -343,8 +339,8 @@ impl PaneAxis {
fn split(
&mut self,
- old_pane: &ViewHandle<Pane>,
- new_pane: &ViewHandle<Pane>,
+ old_pane: &View<Pane>,
+ new_pane: &View<Pane>,
direction: SplitDirection,
) -> Result<()> {
for (mut idx, member) in self.members.iter_mut().enumerate() {
@@ -375,7 +371,7 @@ impl PaneAxis {
Err(anyhow!("Pane not found"))
}
- fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
+ fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
let mut found_pane = false;
let mut remove_member = None;
for (idx, member) in self.members.iter_mut().enumerate() {
@@ -417,7 +413,7 @@ impl PaneAxis {
}
}
- fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
+ fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
for member in self.members.iter_mut() {
match member {
Member::Axis(axis) => axis.swap(from, to),
@@ -432,7 +428,7 @@ impl PaneAxis {
}
}
- fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
+ fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<RectF> {
debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
for (idx, member) in self.members.iter().enumerate() {
@@ -452,7 +448,7 @@ impl PaneAxis {
None
}
- fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
+ fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&View<Pane>> {
debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
let bounding_boxes = self.bounding_boxes.borrow();
@@ -472,12 +468,12 @@ impl PaneAxis {
fn render(
&self,
- project: &ModelHandle<Project>,
+ project: &Handle<Project>,
basis: usize,
theme: &Theme,
- follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
- active_call: Option<&ModelHandle<ActiveCall>>,
- active_pane: &ViewHandle<Pane>,
+ follower_states: &HashMap<View<Pane>, FollowerState>,
+ active_call: Option<&Handle<ActiveCall>>,
+ active_pane: &View<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
@@ -1,12 +1,12 @@
use std::{any::Any, sync::Arc};
-use gpui::{
- AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
- WeakViewHandle, WindowContext,
-};
-use project::search::SearchQuery;
+use gpui2::{AppContext, Subscription, Task, View, ViewContext, WindowContext};
+use project2::search::SearchQuery;
-use crate::{item::WeakItemHandle, Item, ItemHandle};
+use crate::{
+ item::{Item, WeakItemHandle},
+ ItemHandle,
+};
#[derive(Debug)]
pub enum SearchEvent {
@@ -128,7 +128,7 @@ pub trait SearchableItemHandle: ItemHandle {
) -> Option<usize>;
}
-impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
+impl<T: SearchableItem> SearchableItemHandle for View<T> {
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
Box::new(self.downgrade())
}
@@ -231,17 +231,19 @@ fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T
)
}
-impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
- fn from(this: Box<dyn SearchableItemHandle>) -> Self {
- this.as_any().clone()
- }
-}
+// todo!()
+// impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
+// fn from(this: Box<dyn SearchableItemHandle>) -> Self {
+// this.as_any().clone()
+// }
+// }
-impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
- fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
- this.as_any().clone()
- }
-}
+// todo!()
+// impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
+// fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
+// this.as_any().clone()
+// }
+// }
impl PartialEq for Box<dyn SearchableItemHandle> {
fn eq(&self, other: &Self) -> bool {
@@ -254,18 +256,20 @@ impl Eq for Box<dyn SearchableItemHandle> {}
pub trait WeakSearchableItemHandle: WeakItemHandle {
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
- fn into_any(self) -> AnyWeakViewHandle;
+ // todo!()
+ // fn into_any(self) -> AnyWeakViewHandle;
}
-impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
- fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
- Some(Box::new(self.upgrade(cx)?))
- }
+// todo!()
+// impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
+// fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+// Some(Box::new(self.upgrade(cx)?))
+// }
- fn into_any(self) -> AnyWeakViewHandle {
- self.into_any()
- }
-}
+// fn into_any(self) -> AnyWeakViewHandle {
+// self.into_any()
+// }
+// }
impl PartialEq for Box<dyn WeakSearchableItemHandle> {
fn eq(&self, other: &Self) -> bool {
@@ -1,16 +1,16 @@
// pub mod dock;
pub mod item;
// pub mod notifications;
-// pub mod pane;
-// pub mod pane_group;
+pub mod pane;
+pub mod pane_group;
// mod persistence;
-// pub mod searchable;
+pub mod searchable;
// pub mod shared_screen;
// mod status_bar;
-// mod toolbar;
+mod toolbar;
// mod workspace_settings;
-// use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Result};
// use call2::ActiveCall;
// use client2::{
// proto::{self, PeerId},
@@ -52,8 +52,8 @@ pub mod item;
// use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
// use lazy_static::lazy_static;
// use notifications::{NotificationHandle, NotifyResultExt};
-// pub use pane::*;
-// pub use pane_group::*;
+pub use pane::*;
+pub use pane_group::*;
// use persistence::{model::SerializedItem, DB};
// pub use persistence::{
// model::{ItemId, WorkspaceLocation},
@@ -66,7 +66,7 @@ pub mod item;
// use status_bar::StatusBar;
// pub use status_bar::StatusItemView;
// use theme::{Theme, ThemeSettings};
-// pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
+pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
// use util::ResultExt;
// pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
@@ -234,7 +234,7 @@ pub mod item;
// ]
// );
-// pub type WorkspaceId = i64;
+pub type WorkspaceId = i64;
// pub fn init_settings(cx: &mut AppContext) {
// settings::register::<WorkspaceSettings>(cx);
@@ -365,3381 +365,3381 @@ pub mod item;
// });
// }
-// type ProjectItemBuilders = HashMap<
-// TypeId,
-// fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
-// >;
-// pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
-// cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
-// builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
-// let item = model.downcast::<I::Item>().unwrap();
-// Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
-// });
-// });
-// }
-
-// type FollowableItemBuilder = fn(
-// ViewHandle<Pane>,
-// ViewHandle<Workspace>,
-// ViewId,
-// &mut Option<proto::view::Variant>,
-// &mut AppContext,
-// ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
-// type FollowableItemBuilders = HashMap<
-// TypeId,
-// (
-// FollowableItemBuilder,
-// fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
-// ),
-// >;
-// pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
-// cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
-// builders.insert(
-// TypeId::of::<I>(),
-// (
-// |pane, workspace, id, state, cx| {
-// I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
-// cx.foreground()
-// .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
-// })
-// },
-// |this| Box::new(this.clone().downcast::<I>().unwrap()),
-// ),
-// );
-// });
-// }
-
-// type ItemDeserializers = HashMap<
-// Arc<str>,
-// fn(
-// ModelHandle<Project>,
-// WeakViewHandle<Workspace>,
-// WorkspaceId,
-// ItemId,
-// &mut ViewContext<Pane>,
-// ) -> Task<Result<Box<dyn ItemHandle>>>,
-// >;
-// pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
-// cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
-// if let Some(serialized_item_kind) = I::serialized_item_kind() {
-// deserializers.insert(
-// Arc::from(serialized_item_kind),
-// |project, workspace, workspace_id, item_id, cx| {
-// let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
-// cx.foreground()
-// .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
-// },
-// );
-// }
-// });
-// }
-
-pub struct AppState {
- pub languages: Arc<LanguageRegistry>,
- pub client: Arc<Client>,
- pub user_store: Handle<UserStore>,
- pub workspace_store: Handle<WorkspaceStore>,
- pub fs: Arc<dyn fs2::Fs>,
- pub build_window_options:
- fn(Option<WindowBounds>, Option<DisplayId>, &MainThread<AppContext>) -> WindowOptions,
- pub initialize_workspace:
- fn(WeakHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<anyhow::Result<()>>,
- pub node_runtime: Arc<dyn NodeRuntime>,
-}
-
-pub struct WorkspaceStore {
- workspaces: HashSet<WeakHandle<Workspace>>,
- followers: Vec<Follower>,
- client: Arc<Client>,
- _subscriptions: Vec<client2::Subscription>,
-}
-
-#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
-struct Follower {
- project_id: Option<u64>,
- peer_id: PeerId,
-}
-
-// impl AppState {
-// #[cfg(any(test, feature = "test-support"))]
-// pub fn test(cx: &mut AppContext) -> Arc<Self> {
-// use node_runtime::FakeNodeRuntime;
-// use settings::SettingsStore;
-
-// if !cx.has_global::<SettingsStore>() {
-// cx.set_global(SettingsStore::test(cx));
-// }
-
-// let fs = fs::FakeFs::new(cx.background().clone());
-// let languages = Arc::new(LanguageRegistry::test());
-// let http_client = util::http::FakeHttpClient::with_404_response();
-// let client = Client::new(http_client.clone(), cx);
-// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
-
-// theme::init((), cx);
-// client::init(&client, cx);
-// crate::init_settings(cx);
-
-// Arc::new(Self {
-// client,
-// fs,
-// languages,
-// user_store,
-// // channel_store,
-// workspace_store,
-// node_runtime: FakeNodeRuntime::new(),
-// initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
-// build_window_options: |_, _, _| Default::default(),
-// })
-// }
-// }
-
-// struct DelayedDebouncedEditAction {
-// task: Option<Task<()>>,
-// cancel_channel: Option<oneshot::Sender<()>>,
-// }
-
-// impl DelayedDebouncedEditAction {
-// fn new() -> DelayedDebouncedEditAction {
-// DelayedDebouncedEditAction {
-// task: None,
-// cancel_channel: None,
-// }
-// }
-
-// fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
-// where
-// F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
-// {
-// if let Some(channel) = self.cancel_channel.take() {
-// _ = channel.send(());
-// }
-
-// let (sender, mut receiver) = oneshot::channel::<()>();
-// self.cancel_channel = Some(sender);
-
-// let previous_task = self.task.take();
-// self.task = Some(cx.spawn(|workspace, mut cx| async move {
-// let mut timer = cx.background().timer(delay).fuse();
-// if let Some(previous_task) = previous_task {
-// previous_task.await;
-// }
-
-// futures::select_biased! {
-// _ = receiver => return,
-// _ = timer => {}
-// }
-
-// if let Some(result) = workspace
-// .update(&mut cx, |workspace, cx| (func)(workspace, cx))
-// .log_err()
-// {
-// result.await.log_err();
-// }
-// }));
-// }
-// }
-
-// pub enum Event {
-// PaneAdded(ViewHandle<Pane>),
-// ContactRequestedJoin(u64),
-// }
-
-pub struct Workspace {
- weak_self: WeakHandle<Self>,
- // modal: Option<ActiveModal>,
- // zoomed: Option<AnyWeakViewHandle>,
- // zoomed_position: Option<DockPosition>,
- // center: PaneGroup,
- // left_dock: ViewHandle<Dock>,
- // bottom_dock: ViewHandle<Dock>,
- // right_dock: ViewHandle<Dock>,
- // panes: Vec<ViewHandle<Pane>>,
- // panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
- // active_pane: ViewHandle<Pane>,
- // last_active_center_pane: Option<WeakViewHandle<Pane>>,
- // last_active_view_id: Option<proto::ViewId>,
- // status_bar: ViewHandle<StatusBar>,
- // titlebar_item: Option<AnyViewHandle>,
- // notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
- project: Handle<Project>,
- // follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
- // last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
- // window_edited: bool,
- // active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
- // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
- // database_id: WorkspaceId,
- // app_state: Arc<AppState>,
- // subscriptions: Vec<Subscription>,
- // _apply_leader_updates: Task<Result<()>>,
- // _observe_current_user: Task<Result<()>>,
- // _schedule_serialize: Option<Task<()>>,
- // pane_history_timestamp: Arc<AtomicUsize>,
-}
-
-// struct ActiveModal {
-// view: Box<dyn ModalHandle>,
-// previously_focused_view_id: Option<usize>,
-// }
-
-// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-// pub struct ViewId {
-// pub creator: PeerId,
-// pub id: u64,
-// }
-
-// #[derive(Default)]
-// struct FollowerState {
-// leader_id: PeerId,
-// active_view_id: Option<ViewId>,
-// items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
-// }
-
-// enum WorkspaceBounds {}
-
-// impl Workspace {
-// pub fn new(
-// workspace_id: WorkspaceId,
-// project: ModelHandle<Project>,
-// app_state: Arc<AppState>,
-// cx: &mut ViewContext<Self>,
-// ) -> Self {
-// cx.observe(&project, |_, _, cx| cx.notify()).detach();
-// cx.subscribe(&project, move |this, _, event, cx| {
-// match event {
-// project::Event::RemoteIdChanged(_) => {
-// this.update_window_title(cx);
-// }
-
-// project::Event::CollaboratorLeft(peer_id) => {
-// this.collaborator_left(*peer_id, cx);
-// }
-
-// project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
-// this.update_window_title(cx);
-// this.serialize_workspace(cx);
-// }
-
-// project::Event::DisconnectedFromHost => {
-// this.update_window_edited(cx);
-// cx.blur();
-// }
-
-// project::Event::Closed => {
-// cx.remove_window();
-// }
-
-// project::Event::DeletedEntry(entry_id) => {
-// for pane in this.panes.iter() {
-// pane.update(cx, |pane, cx| {
-// pane.handle_deleted_project_item(*entry_id, cx)
-// });
-// }
-// }
-
-// project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
-// cx.add_view(|_| MessageNotification::new(message.clone()))
-// }),
-
-// _ => {}
-// }
-// cx.notify()
-// })
-// .detach();
-
-// let weak_handle = cx.weak_handle();
-// let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
-
-// let center_pane = cx.add_view(|cx| {
-// Pane::new(
-// weak_handle.clone(),
-// project.clone(),
-// pane_history_timestamp.clone(),
-// cx,
-// )
-// });
-// cx.subscribe(¢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::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
-// drag_and_drop.register_container(weak_handle.clone());
-// });
-
-// let mut active_call = None;
-// if cx.has_global::<ModelHandle<ActiveCall>>() {
-// let call = cx.global::<ModelHandle<ActiveCall>>().clone();
-// let mut subscriptions = Vec::new();
-// subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
-// active_call = Some((call, subscriptions));
-// }
-
-// let subscriptions = vec![
-// cx.observe_fullscreen(|_, _, cx| cx.notify()),
-// cx.observe_window_activation(Self::on_window_activation_changed),
-// cx.observe_window_bounds(move |_, mut bounds, display, cx| {
-// // Transform fixed bounds to be stored in terms of the containing display
-// if let WindowBounds::Fixed(mut window_bounds) = bounds {
-// if let Some(screen) = cx.platform().screen_by_id(display) {
-// let screen_bounds = screen.bounds();
-// window_bounds
-// .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
-// window_bounds
-// .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
-// bounds = WindowBounds::Fixed(window_bounds);
-// }
-// }
-
-// cx.background()
-// .spawn(DB.set_window_bounds(workspace_id, bounds, display))
-// .detach_and_log_err(cx);
-// }),
-// cx.observe(&left_dock, |this, _, cx| {
-// this.serialize_workspace(cx);
-// cx.notify();
-// }),
-// cx.observe(&bottom_dock, |this, _, cx| {
-// this.serialize_workspace(cx);
-// cx.notify();
-// }),
-// cx.observe(&right_dock, |this, _, cx| {
-// this.serialize_workspace(cx);
-// cx.notify();
-// }),
-// ];
-
-// cx.defer(|this, cx| this.update_window_title(cx));
-// Workspace {
-// weak_self: weak_handle.clone(),
-// modal: None,
-// zoomed: None,
-// zoomed_position: None,
-// center: PaneGroup::new(center_pane.clone()),
-// panes: vec![center_pane.clone()],
-// panes_by_item: Default::default(),
-// active_pane: center_pane.clone(),
-// last_active_center_pane: Some(center_pane.downgrade()),
-// last_active_view_id: None,
-// status_bar,
-// titlebar_item: None,
-// notifications: Default::default(),
-// left_dock,
-// bottom_dock,
-// right_dock,
-// project: project.clone(),
-// follower_states: Default::default(),
-// last_leaders_by_pane: Default::default(),
-// window_edited: false,
-// active_call,
-// database_id: workspace_id,
-// app_state,
-// _observe_current_user,
-// _apply_leader_updates,
-// _schedule_serialize: None,
-// leader_updates_tx,
-// subscriptions,
-// pane_history_timestamp,
-// }
-// }
-
-// fn new_local(
-// abs_paths: Vec<PathBuf>,
-// app_state: Arc<AppState>,
-// requesting_window: Option<WindowHandle<Workspace>>,
-// cx: &mut AppContext,
-// ) -> Task<(
-// WeakViewHandle<Workspace>,
-// Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-// )> {
-// let project_handle = Project::local(
-// app_state.client.clone(),
-// app_state.node_runtime.clone(),
-// app_state.user_store.clone(),
-// app_state.languages.clone(),
-// app_state.fs.clone(),
-// cx,
-// );
-
-// cx.spawn(|mut cx| async move {
-// let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
-
-// let paths_to_open = Arc::new(abs_paths);
-
-// // Get project paths for all of the abs_paths
-// let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
-// let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
-// Vec::with_capacity(paths_to_open.len());
-// for path in paths_to_open.iter().cloned() {
-// if let Some((worktree, project_entry)) = cx
-// .update(|cx| {
-// Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
-// })
-// .await
-// .log_err()
-// {
-// worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
-// project_paths.push((path, Some(project_entry)));
-// } else {
-// project_paths.push((path, None));
-// }
-// }
-
-// let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
-// serialized_workspace.id
-// } else {
-// DB.next_id().await.unwrap_or(0)
-// };
-
-// let window = if let Some(window) = requesting_window {
-// window.replace_root(&mut cx, |cx| {
-// Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-// });
-// window
-// } else {
-// {
-// let window_bounds_override = window_bounds_env_override(&cx);
-// let (bounds, display) = if let Some(bounds) = window_bounds_override {
-// (Some(bounds), None)
-// } else {
-// serialized_workspace
-// .as_ref()
-// .and_then(|serialized_workspace| {
-// let display = serialized_workspace.display?;
-// let mut bounds = serialized_workspace.bounds?;
-
-// // Stored bounds are relative to the containing display.
-// // So convert back to global coordinates if that screen still exists
-// if let WindowBounds::Fixed(mut window_bounds) = bounds {
-// if let Some(screen) = cx.platform().screen_by_id(display) {
-// let screen_bounds = screen.bounds();
-// window_bounds.set_origin_x(
-// window_bounds.origin_x() + screen_bounds.origin_x(),
-// );
-// window_bounds.set_origin_y(
-// window_bounds.origin_y() + screen_bounds.origin_y(),
-// );
-// bounds = WindowBounds::Fixed(window_bounds);
-// } else {
-// // Screen no longer exists. Return none here.
-// return None;
-// }
-// }
-
-// Some((bounds, display))
-// })
-// .unzip()
-// };
-
-// // Use the serialized workspace to construct the new window
-// cx.add_window(
-// (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
-// |cx| {
-// Workspace::new(
-// workspace_id,
-// project_handle.clone(),
-// app_state.clone(),
-// cx,
-// )
-// },
-// )
-// }
-// };
-
-// // We haven't yielded the main thread since obtaining the window handle,
-// // so the window exists.
-// let workspace = window.root(&cx).unwrap();
-
-// (app_state.initialize_workspace)(
-// workspace.downgrade(),
-// serialized_workspace.is_some(),
-// app_state.clone(),
-// cx.clone(),
-// )
-// .await
-// .log_err();
-
-// window.update(&mut cx, |cx| cx.activate_window());
-
-// let workspace = workspace.downgrade();
-// notify_if_database_failed(&workspace, &mut cx);
-// let opened_items = open_items(
-// serialized_workspace,
-// &workspace,
-// project_paths,
-// app_state,
-// cx,
-// )
-// .await
-// .unwrap_or_default();
-
-// (workspace, opened_items)
-// })
-// }
-
-// pub fn weak_handle(&self) -> WeakViewHandle<Self> {
-// self.weak_self.clone()
-// }
-
-// pub fn left_dock(&self) -> &ViewHandle<Dock> {
-// &self.left_dock
-// }
-
-// pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
-// &self.bottom_dock
-// }
-
-// pub fn right_dock(&self) -> &ViewHandle<Dock> {
-// &self.right_dock
-// }
-
-// pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
-// where
-// T::Event: std::fmt::Debug,
-// {
-// self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
-// }
-
-// pub fn add_panel_with_extra_event_handler<T: Panel, F>(
-// &mut self,
-// panel: ViewHandle<T>,
-// cx: &mut ViewContext<Self>,
-// handler: F,
-// ) where
-// T::Event: std::fmt::Debug,
-// F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
-// {
-// let dock = match panel.position(cx) {
-// DockPosition::Left => &self.left_dock,
-// DockPosition::Bottom => &self.bottom_dock,
-// DockPosition::Right => &self.right_dock,
-// };
-
-// self.subscriptions.push(cx.subscribe(&panel, {
-// let mut dock = dock.clone();
-// let mut prev_position = panel.position(cx);
-// move |this, panel, event, cx| {
-// if T::should_change_position_on_event(event) {
-// let new_position = panel.read(cx).position(cx);
-// let mut was_visible = false;
-// dock.update(cx, |dock, cx| {
-// prev_position = new_position;
-
-// was_visible = dock.is_open()
-// && dock
-// .visible_panel()
-// .map_or(false, |active_panel| active_panel.id() == panel.id());
-// dock.remove_panel(&panel, cx);
-// });
-
-// if panel.is_zoomed(cx) {
-// this.zoomed_position = Some(new_position);
-// }
-
-// dock = match panel.read(cx).position(cx) {
-// DockPosition::Left => &this.left_dock,
-// DockPosition::Bottom => &this.bottom_dock,
-// DockPosition::Right => &this.right_dock,
-// }
-// .clone();
-// dock.update(cx, |dock, cx| {
-// dock.add_panel(panel.clone(), cx);
-// if was_visible {
-// dock.set_open(true, cx);
-// dock.activate_panel(dock.panels_len() - 1, cx);
-// }
-// });
-// } else if T::should_zoom_in_on_event(event) {
-// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
-// if !panel.has_focus(cx) {
-// cx.focus(&panel);
-// }
-// this.zoomed = Some(panel.downgrade().into_any());
-// this.zoomed_position = Some(panel.read(cx).position(cx));
-// } else if T::should_zoom_out_on_event(event) {
-// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
-// if this.zoomed_position == Some(prev_position) {
-// this.zoomed = None;
-// this.zoomed_position = None;
-// }
-// cx.notify();
-// } else if T::is_focus_event(event) {
-// let position = panel.read(cx).position(cx);
-// this.dismiss_zoomed_items_to_reveal(Some(position), cx);
-// if panel.is_zoomed(cx) {
-// this.zoomed = Some(panel.downgrade().into_any());
-// this.zoomed_position = Some(position);
-// } else {
-// this.zoomed = None;
-// this.zoomed_position = None;
-// }
-// this.update_active_view_for_followers(cx);
-// cx.notify();
-// } else {
-// handler(this, &panel, event, cx)
-// }
-// }
-// }));
-
-// dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
-// }
-
-// pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
-// &self.status_bar
-// }
-
-// pub fn app_state(&self) -> &Arc<AppState> {
-// &self.app_state
-// }
-
-// pub fn user_store(&self) -> &ModelHandle<UserStore> {
-// &self.app_state.user_store
-// }
-
-// pub fn project(&self) -> &ModelHandle<Project> {
-// &self.project
-// }
-
-// pub fn recent_navigation_history(
-// &self,
-// limit: Option<usize>,
-// cx: &AppContext,
-// ) -> Vec<(ProjectPath, Option<PathBuf>)> {
-// let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
-// let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
-// for pane in &self.panes {
-// let pane = pane.read(cx);
-// pane.nav_history()
-// .for_each_entry(cx, |entry, (project_path, fs_path)| {
-// if let Some(fs_path) = &fs_path {
-// abs_paths_opened
-// .entry(fs_path.clone())
-// .or_default()
-// .insert(project_path.clone());
-// }
-// let timestamp = entry.timestamp;
-// match history.entry(project_path) {
-// hash_map::Entry::Occupied(mut entry) => {
-// let (_, old_timestamp) = entry.get();
-// if ×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<Pane>,
-// mode: NavigationMode,
-// cx: &mut ViewContext<Workspace>,
-// ) -> Task<Result<()>> {
-// let to_load = if let Some(pane) = pane.upgrade(cx) {
-// cx.focus(&pane);
-
-// pane.update(cx, |pane, cx| {
-// loop {
-// // Retrieve the weak item handle from the history.
-// let entry = pane.nav_history_mut().pop(mode, cx)?;
-
-// // If the item is still present in this pane, then activate it.
-// if let Some(index) = entry
-// .item
-// .upgrade(cx)
-// .and_then(|v| pane.index_for_item(v.as_ref()))
-// {
-// let prev_active_item_index = pane.active_item_index();
-// pane.nav_history_mut().set_mode(mode);
-// pane.activate_item(index, true, true, cx);
-// pane.nav_history_mut().set_mode(NavigationMode::Normal);
-
-// let mut navigated = prev_active_item_index != pane.active_item_index();
-// if let Some(data) = entry.data {
-// navigated |= pane.active_item()?.navigate(data, cx);
-// }
-
-// if navigated {
-// break None;
-// }
-// }
-// // If the item is no longer present in this pane, then retrieve its
-// // project path in order to reopen it.
-// else {
-// break pane
-// .nav_history()
-// .path_for_item(entry.item.id())
-// .map(|(project_path, _)| (project_path, entry));
-// }
-// }
-// })
-// } else {
-// None
-// };
-
-// if let Some((project_path, entry)) = to_load {
-// // If the item was no longer present, then load it again from its previous path.
-// let task = self.load_path(project_path, cx);
-// cx.spawn(|workspace, mut cx| async move {
-// let task = task.await;
-// let mut navigated = false;
-// if let Some((project_entry_id, build_item)) = task.log_err() {
-// let prev_active_item_id = pane.update(&mut cx, |pane, _| {
-// pane.nav_history_mut().set_mode(mode);
-// pane.active_item().map(|p| p.id())
-// })?;
-
-// pane.update(&mut cx, |pane, cx| {
-// let item = pane.open_item(project_entry_id, true, cx, build_item);
-// navigated |= Some(item.id()) != prev_active_item_id;
-// pane.nav_history_mut().set_mode(NavigationMode::Normal);
-// if let Some(data) = entry.data {
-// navigated |= item.navigate(data, cx);
-// }
-// })?;
-// }
-
-// if !navigated {
-// workspace
-// .update(&mut cx, |workspace, cx| {
-// Self::navigate_history(workspace, pane, mode, cx)
-// })?
-// .await?;
-// }
-
-// Ok(())
-// })
-// } else {
-// Task::ready(Ok(()))
-// }
-// }
-
-// pub fn go_back(
-// &mut self,
-// pane: WeakViewHandle<Pane>,
-// cx: &mut ViewContext<Workspace>,
-// ) -> Task<Result<()>> {
-// self.navigate_history(pane, NavigationMode::GoingBack, cx)
-// }
-
-// pub fn go_forward(
-// &mut self,
-// pane: WeakViewHandle<Pane>,
-// cx: &mut ViewContext<Workspace>,
-// ) -> Task<Result<()>> {
-// self.navigate_history(pane, NavigationMode::GoingForward, cx)
-// }
-
-// pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
-// self.navigate_history(
-// self.active_pane().downgrade(),
-// NavigationMode::ReopeningClosedItem,
-// cx,
-// )
-// }
-
-// pub fn client(&self) -> &Client {
-// &self.app_state.client
-// }
-
-// pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
-// self.titlebar_item = Some(item);
-// cx.notify();
-// }
-
-// pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
-// self.titlebar_item.clone()
-// }
-
-// /// Call the given callback with a workspace whose project is local.
-// ///
-// /// If the given workspace has a local project, then it will be passed
-// /// to the callback. Otherwise, a new empty window will be created.
-// pub fn with_local_workspace<T, F>(
-// &mut self,
-// cx: &mut ViewContext<Self>,
-// callback: F,
-// ) -> Task<Result<T>>
-// where
-// T: 'static,
-// F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
-// {
-// if self.project.read(cx).is_local() {
-// Task::Ready(Some(Ok(callback(self, cx))))
-// } else {
-// let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
-// cx.spawn(|_vh, mut cx| async move {
-// let (workspace, _) = task.await;
-// workspace.update(&mut cx, callback)
-// })
-// }
-// }
-
-// pub fn worktrees<'a>(
-// &self,
-// cx: &'a AppContext,
-// ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
-// self.project.read(cx).worktrees(cx)
-// }
-
-// pub fn visible_worktrees<'a>(
-// &self,
-// cx: &'a AppContext,
-// ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
-// self.project.read(cx).visible_worktrees(cx)
-// }
-
-// pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
-// let futures = self
-// .worktrees(cx)
-// .filter_map(|worktree| worktree.read(cx).as_local())
-// .map(|worktree| worktree.scan_complete())
-// .collect::<Vec<_>>();
-// async move {
-// for future in futures {
-// future.await;
-// }
-// }
-// }
-
-// pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
-// cx.spawn(|mut cx| async move {
-// let window = cx
-// .windows()
-// .into_iter()
-// .find(|window| window.is_active(&cx).unwrap_or(false));
-// if let Some(window) = window {
-// //This can only get called when the window's project connection has been lost
-// //so we don't need to prompt the user for anything and instead just close the window
-// window.remove(&mut cx);
-// }
-// })
-// .detach();
-// }
-
-// pub fn close(
-// &mut self,
-// _: &CloseWindow,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let window = cx.window();
-// let prepare = self.prepare_to_close(false, cx);
-// Some(cx.spawn(|_, mut cx| async move {
-// if prepare.await? {
-// window.remove(&mut cx);
-// }
-// Ok(())
-// }))
-// }
-
-// pub fn prepare_to_close(
-// &mut self,
-// quitting: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<bool>> {
-// let active_call = self.active_call().cloned();
-// let window = cx.window();
-
-// cx.spawn(|this, mut cx| async move {
-// let workspace_count = cx
-// .windows()
-// .into_iter()
-// .filter(|window| window.root_is::<Workspace>())
-// .count();
-
-// if let Some(active_call) = active_call {
-// if !quitting
-// && workspace_count == 1
-// && active_call.read_with(&cx, |call, _| call.room().is_some())
-// {
-// let answer = window.prompt(
-// PromptLevel::Warning,
-// "Do you want to leave the current call?",
-// &["Close window and hang up", "Cancel"],
-// &mut cx,
-// );
-
-// if let Some(mut answer) = answer {
-// if answer.next().await == Some(1) {
-// return anyhow::Ok(false);
-// } else {
-// active_call
-// .update(&mut cx, |call, cx| call.hang_up(cx))
-// .await
-// .log_err();
-// }
-// }
-// }
-// }
-
-// Ok(this
-// .update(&mut cx, |this, cx| {
-// this.save_all_internal(SaveIntent::Close, cx)
-// })?
-// .await?)
-// })
-// }
-
-// fn save_all(
-// &mut self,
-// action: &SaveAll,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let save_all =
-// self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
-// Some(cx.foreground().spawn(async move {
-// save_all.await?;
-// Ok(())
-// }))
-// }
-
-// fn save_all_internal(
-// &mut self,
-// mut save_intent: SaveIntent,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<bool>> {
-// if self.project.read(cx).is_read_only() {
-// return Task::ready(Ok(true));
-// }
-// let dirty_items = self
-// .panes
-// .iter()
-// .flat_map(|pane| {
-// pane.read(cx).items().filter_map(|item| {
-// if item.is_dirty(cx) {
-// Some((pane.downgrade(), item.boxed_clone()))
-// } else {
-// None
-// }
-// })
-// })
-// .collect::<Vec<_>>();
-
-// let project = self.project.clone();
-// cx.spawn(|workspace, mut cx| async move {
-// // Override save mode and display "Save all files" prompt
-// if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-// let mut answer = workspace.update(&mut cx, |_, cx| {
-// let prompt = Pane::file_names_for_prompt(
-// &mut dirty_items.iter().map(|(_, handle)| handle),
-// dirty_items.len(),
-// cx,
-// );
-// cx.prompt(
-// PromptLevel::Warning,
-// &prompt,
-// &["Save all", "Discard all", "Cancel"],
-// )
-// })?;
-// match answer.next().await {
-// Some(0) => save_intent = SaveIntent::SaveAll,
-// Some(1) => save_intent = SaveIntent::Skip,
-// _ => {}
-// }
-// }
-// for (pane, item) in dirty_items {
-// let (singleton, project_entry_ids) =
-// cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
-// if singleton || !project_entry_ids.is_empty() {
-// if let Some(ix) =
-// pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
-// {
-// if !Pane::save_item(
-// project.clone(),
-// &pane,
-// ix,
-// &*item,
-// save_intent,
-// &mut cx,
-// )
-// .await?
-// {
-// return Ok(false);
-// }
-// }
-// }
-// }
-// Ok(true)
-// })
-// }
-
-// pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
-// let mut paths = cx.prompt_for_paths(PathPromptOptions {
-// files: true,
-// directories: true,
-// multiple: true,
-// });
-
-// Some(cx.spawn(|this, mut cx| async move {
-// if let Some(paths) = paths.recv().await.flatten() {
-// if let Some(task) = this
-// .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
-// .log_err()
-// {
-// task.await?
-// }
-// }
-// Ok(())
-// }))
-// }
-
-// pub fn open_workspace_for_paths(
-// &mut self,
-// paths: Vec<PathBuf>,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<()>> {
-// let window = cx.window().downcast::<Self>();
-// let is_remote = self.project.read(cx).is_remote();
-// let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
-// let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
-// let close_task = if is_remote || has_worktree || has_dirty_items {
-// None
-// } else {
-// Some(self.prepare_to_close(false, cx))
-// };
-// let app_state = self.app_state.clone();
-
-// cx.spawn(|_, mut cx| async move {
-// let window_to_replace = if let Some(close_task) = close_task {
-// if !close_task.await? {
-// return Ok(());
-// }
-// window
-// } else {
-// None
-// };
-// cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
-// .await?;
-// Ok(())
-// })
-// }
-
-// #[allow(clippy::type_complexity)]
-// pub fn open_paths(
-// &mut self,
-// mut abs_paths: Vec<PathBuf>,
-// visible: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
-// log::info!("open paths {:?}", abs_paths);
-
-// let fs = self.app_state.fs.clone();
-
-// // Sort the paths to ensure we add worktrees for parents before their children.
-// abs_paths.sort_unstable();
-// cx.spawn(|this, mut cx| async move {
-// let mut tasks = Vec::with_capacity(abs_paths.len());
-// for abs_path in &abs_paths {
-// let project_path = match this
-// .update(&mut cx, |this, cx| {
-// Workspace::project_path_for_path(
-// this.project.clone(),
-// abs_path,
-// visible,
-// cx,
-// )
-// })
-// .log_err()
-// {
-// Some(project_path) => project_path.await.log_err(),
-// None => None,
-// };
-
-// let this = this.clone();
-// let task = cx.spawn(|mut cx| {
-// let fs = fs.clone();
-// let abs_path = abs_path.clone();
-// async move {
-// let (worktree, project_path) = project_path?;
-// if fs.is_file(&abs_path).await {
-// Some(
-// this.update(&mut cx, |this, cx| {
-// this.open_path(project_path, None, true, cx)
-// })
-// .log_err()?
-// .await,
-// )
-// } else {
-// this.update(&mut cx, |workspace, cx| {
-// let worktree = worktree.read(cx);
-// let worktree_abs_path = worktree.abs_path();
-// let entry_id = if abs_path == worktree_abs_path.as_ref() {
-// worktree.root_entry()
-// } else {
-// abs_path
-// .strip_prefix(worktree_abs_path.as_ref())
-// .ok()
-// .and_then(|relative_path| {
-// worktree.entry_for_path(relative_path)
-// })
-// }
-// .map(|entry| entry.id);
-// if let Some(entry_id) = entry_id {
-// workspace.project().update(cx, |_, cx| {
-// cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
-// })
-// }
-// })
-// .log_err()?;
-// None
-// }
-// }
-// });
-// tasks.push(task);
-// }
-
-// futures::future::join_all(tasks).await
-// })
-// }
-
-// fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
-// let mut paths = cx.prompt_for_paths(PathPromptOptions {
-// files: false,
-// directories: true,
-// multiple: true,
-// });
-// cx.spawn(|this, mut cx| async move {
-// if let Some(paths) = paths.recv().await.flatten() {
-// let results = this
-// .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
-// .await;
-// for result in results.into_iter().flatten() {
-// result.log_err();
-// }
-// }
-// anyhow::Ok(())
-// })
-// .detach_and_log_err(cx);
-// }
-
-// fn project_path_for_path(
-// project: ModelHandle<Project>,
-// abs_path: &Path,
-// visible: bool,
-// cx: &mut AppContext,
-// ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
-// let entry = project.update(cx, |project, cx| {
-// project.find_or_create_local_worktree(abs_path, visible, cx)
-// });
-// cx.spawn(|cx| async move {
-// let (worktree, path) = entry.await?;
-// let worktree_id = worktree.read_with(&cx, |t, _| t.id());
-// Ok((
-// worktree,
-// ProjectPath {
-// worktree_id,
-// path: path.into(),
-// },
-// ))
-// })
-// }
-
-// /// Returns the modal that was toggled closed if it was open.
-// pub fn toggle_modal<V, F>(
-// &mut self,
-// cx: &mut ViewContext<Self>,
-// add_view: F,
-// ) -> Option<ViewHandle<V>>
-// where
-// V: 'static + Modal,
-// F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
-// {
-// cx.notify();
-// // Whatever modal was visible is getting clobbered. If its the same type as V, then return
-// // it. Otherwise, create a new modal and set it as active.
-// if let Some(already_open_modal) = self
-// .dismiss_modal(cx)
-// .and_then(|modal| modal.downcast::<V>())
-// {
-// cx.focus_self();
-// Some(already_open_modal)
-// } else {
-// let modal = add_view(self, cx);
-// cx.subscribe(&modal, |this, _, event, cx| {
-// if V::dismiss_on_event(event) {
-// this.dismiss_modal(cx);
-// }
-// })
-// .detach();
-// let previously_focused_view_id = cx.focused_view_id();
-// cx.focus(&modal);
-// self.modal = Some(ActiveModal {
-// view: Box::new(modal),
-// previously_focused_view_id,
-// });
-// None
-// }
-// }
-
-// pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
-// self.modal
-// .as_ref()
-// .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
-// }
-
-// pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
-// if let Some(modal) = self.modal.take() {
-// if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
-// if modal.view.has_focus(cx) {
-// cx.window_context().focus(Some(previously_focused_view_id));
-// }
-// }
-// cx.notify();
-// Some(modal.view.as_any().clone())
-// } else {
-// None
-// }
-// }
-
-// pub fn items<'a>(
-// &'a self,
-// cx: &'a AppContext,
-// ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
-// self.panes.iter().flat_map(|pane| pane.read(cx).items())
-// }
-
-// pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
-// self.items_of_type(cx).max_by_key(|item| item.id())
-// }
-
-// pub fn items_of_type<'a, T: Item>(
-// &'a self,
-// cx: &'a AppContext,
-// ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
-// self.panes
-// .iter()
-// .flat_map(|pane| pane.read(cx).items_of_type())
-// }
-
-// pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-// self.active_pane().read(cx).active_item()
-// }
-
-// fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
-// self.active_item(cx).and_then(|item| item.project_path(cx))
-// }
-
-// pub fn save_active_item(
-// &mut self,
-// save_intent: SaveIntent,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<()>> {
-// let project = self.project.clone();
-// let pane = self.active_pane();
-// let item_ix = pane.read(cx).active_item_index();
-// let item = pane.read(cx).active_item();
-// let pane = pane.downgrade();
-
-// cx.spawn(|_, mut cx| async move {
-// if let Some(item) = item {
-// Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
-// .await
-// .map(|_| ())
-// } else {
-// Ok(())
-// }
-// })
-// }
-
-// pub fn close_inactive_items_and_panes(
-// &mut self,
-// _: &CloseInactiveTabsAndPanes,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// self.close_all_internal(true, SaveIntent::Close, cx)
-// }
-
-// pub fn close_all_items_and_panes(
-// &mut self,
-// action: &CloseAllItemsAndPanes,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
-// }
-
-// fn close_all_internal(
-// &mut self,
-// retain_active_pane: bool,
-// save_intent: SaveIntent,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let current_pane = self.active_pane();
-
-// let mut tasks = Vec::new();
-
-// if retain_active_pane {
-// if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
-// pane.close_inactive_items(&CloseInactiveItems, cx)
-// }) {
-// tasks.push(current_pane_close);
-// };
-// }
-
-// for pane in self.panes() {
-// if retain_active_pane && pane.id() == current_pane.id() {
-// continue;
-// }
-
-// if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
-// pane.close_all_items(
-// &CloseAllItems {
-// save_intent: Some(save_intent),
-// },
-// cx,
-// )
-// }) {
-// tasks.push(close_pane_items)
-// }
-// }
-
-// if tasks.is_empty() {
-// None
-// } else {
-// Some(cx.spawn(|_, _| async move {
-// for task in tasks {
-// task.await?
-// }
-// Ok(())
-// }))
-// }
-// }
-
-// pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
-// let dock = match dock_side {
-// DockPosition::Left => &self.left_dock,
-// DockPosition::Bottom => &self.bottom_dock,
-// DockPosition::Right => &self.right_dock,
-// };
-// let mut focus_center = false;
-// let mut reveal_dock = false;
-// dock.update(cx, |dock, cx| {
-// let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
-// let was_visible = dock.is_open() && !other_is_zoomed;
-// dock.set_open(!was_visible, cx);
-
-// if let Some(active_panel) = dock.active_panel() {
-// if was_visible {
-// if active_panel.has_focus(cx) {
-// focus_center = true;
-// }
-// } else {
-// cx.focus(active_panel.as_any());
-// reveal_dock = true;
-// }
-// }
-// });
-
-// if reveal_dock {
-// self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
-// }
-
-// if focus_center {
-// cx.focus_self();
-// }
-
-// cx.notify();
-// self.serialize_workspace(cx);
-// }
-
-// pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
-// let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
-
-// for dock in docks {
-// dock.update(cx, |dock, cx| {
-// dock.set_open(false, cx);
-// });
-// }
-
-// cx.focus_self();
-// cx.notify();
-// self.serialize_workspace(cx);
-// }
-
-// /// Transfer focus to the panel of the given type.
-// pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
-// self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
-// .as_any()
-// .clone()
-// .downcast()
-// }
-
-// /// Focus the panel of the given type if it isn't already focused. If it is
-// /// already focused, then transfer focus back to the workspace center.
-// pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
-// self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
-// }
-
-// /// Focus or unfocus the given panel type, depending on the given callback.
-// fn focus_or_unfocus_panel<T: Panel>(
-// &mut self,
-// cx: &mut ViewContext<Self>,
-// should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
-// ) -> Option<Rc<dyn PanelHandle>> {
-// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
-// if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
-// let mut focus_center = false;
-// let mut reveal_dock = false;
-// let panel = dock.update(cx, |dock, cx| {
-// dock.activate_panel(panel_index, cx);
-
-// let panel = dock.active_panel().cloned();
-// if let Some(panel) = panel.as_ref() {
-// if should_focus(&**panel, cx) {
-// dock.set_open(true, cx);
-// cx.focus(panel.as_any());
-// reveal_dock = true;
-// } else {
-// // if panel.is_zoomed(cx) {
-// // dock.set_open(false, cx);
-// // }
-// focus_center = true;
-// }
-// }
-// panel
-// });
-
-// if focus_center {
-// cx.focus_self();
-// }
-
-// self.serialize_workspace(cx);
-// cx.notify();
-// return panel;
-// }
-// }
-// None
-// }
-
-// pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
-// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
-// let dock = dock.read(cx);
-// if let Some(panel) = dock.panel::<T>() {
-// return Some(panel);
-// }
-// }
-// None
-// }
-
-// fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
-// for pane in &self.panes {
-// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-// }
-
-// self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-// self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-// self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-// self.zoomed = None;
-// self.zoomed_position = None;
-
-// cx.notify();
-// }
-
-// #[cfg(any(test, feature = "test-support"))]
-// pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
-// self.zoomed.and_then(|view| view.upgrade(cx))
-// }
-
-// fn dismiss_zoomed_items_to_reveal(
-// &mut self,
-// dock_to_reveal: Option<DockPosition>,
-// cx: &mut ViewContext<Self>,
-// ) {
-// // If a center pane is zoomed, unzoom it.
-// for pane in &self.panes {
-// if pane != &self.active_pane || dock_to_reveal.is_some() {
-// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-// }
-// }
-
-// // If another dock is zoomed, hide it.
-// let mut focus_center = false;
-// for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
-// dock.update(cx, |dock, cx| {
-// if Some(dock.position()) != dock_to_reveal {
-// if let Some(panel) = dock.active_panel() {
-// if panel.is_zoomed(cx) {
-// focus_center |= panel.has_focus(cx);
-// dock.set_open(false, cx);
-// }
-// }
-// }
-// });
-// }
-
-// if focus_center {
-// cx.focus_self();
-// }
-
-// if self.zoomed_position != dock_to_reveal {
-// self.zoomed = None;
-// self.zoomed_position = None;
-// }
-
-// cx.notify();
-// }
-
-// fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
-// let pane = cx.add_view(|cx| {
-// Pane::new(
-// self.weak_handle(),
-// self.project.clone(),
-// self.pane_history_timestamp.clone(),
-// cx,
-// )
-// });
-// cx.subscribe(&pane, Self::handle_pane_event).detach();
-// self.panes.push(pane.clone());
-// cx.focus(&pane);
-// cx.emit(Event::PaneAdded(pane.clone()));
-// pane
-// }
-
-// pub fn add_item_to_center(
-// &mut self,
-// item: Box<dyn ItemHandle>,
-// cx: &mut ViewContext<Self>,
-// ) -> bool {
-// if let Some(center_pane) = self.last_active_center_pane.clone() {
-// if let Some(center_pane) = center_pane.upgrade(cx) {
-// center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
-// true
-// } else {
-// false
-// }
-// } else {
-// false
-// }
-// }
-
-// pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-// self.active_pane
-// .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
-// }
-
-// pub fn split_item(
-// &mut self,
-// split_direction: SplitDirection,
-// item: Box<dyn ItemHandle>,
-// cx: &mut ViewContext<Self>,
-// ) {
-// let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
-// new_pane.update(cx, move |new_pane, cx| {
-// new_pane.add_item(item, true, true, None, cx)
-// })
-// }
-
-// pub fn open_abs_path(
-// &mut self,
-// abs_path: PathBuf,
-// visible: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
-// cx.spawn(|workspace, mut cx| async move {
-// let open_paths_task_result = workspace
-// .update(&mut cx, |workspace, cx| {
-// workspace.open_paths(vec![abs_path.clone()], visible, cx)
-// })
-// .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
-// .await;
-// anyhow::ensure!(
-// open_paths_task_result.len() == 1,
-// "open abs path {abs_path:?} task returned incorrect number of results"
-// );
-// match open_paths_task_result
-// .into_iter()
-// .next()
-// .expect("ensured single task result")
-// {
-// Some(open_result) => {
-// open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
-// }
-// None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
-// }
-// })
-// }
-
-// pub fn split_abs_path(
-// &mut self,
-// abs_path: PathBuf,
-// visible: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
-// let project_path_task =
-// Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
-// cx.spawn(|this, mut cx| async move {
-// let (_, path) = project_path_task.await?;
-// this.update(&mut cx, |this, cx| this.split_path(path, cx))?
-// .await
-// })
-// }
-
-// pub fn open_path(
-// &mut self,
-// path: impl Into<ProjectPath>,
-// pane: Option<WeakViewHandle<Pane>>,
-// focus_item: bool,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
-// let pane = pane.unwrap_or_else(|| {
-// self.last_active_center_pane.clone().unwrap_or_else(|| {
-// self.panes
-// .first()
-// .expect("There must be an active pane")
-// .downgrade()
-// })
-// });
-
-// let task = self.load_path(path.into(), cx);
-// cx.spawn(|_, mut cx| async move {
-// let (project_entry_id, build_item) = task.await?;
-// pane.update(&mut cx, |pane, cx| {
-// pane.open_item(project_entry_id, focus_item, cx, build_item)
-// })
-// })
-// }
-
-// pub fn split_path(
-// &mut self,
-// path: impl Into<ProjectPath>,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
-// let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
-// self.panes
-// .first()
-// .expect("There must be an active pane")
-// .downgrade()
-// });
-
-// if let Member::Pane(center_pane) = &self.center.root {
-// if center_pane.read(cx).items_len() == 0 {
-// return self.open_path(path, Some(pane), true, cx);
-// }
-// }
-
-// let task = self.load_path(path.into(), cx);
-// cx.spawn(|this, mut cx| async move {
-// let (project_entry_id, build_item) = task.await?;
-// this.update(&mut cx, move |this, cx| -> Option<_> {
-// let pane = pane.upgrade(cx)?;
-// let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
-// new_pane.update(cx, |new_pane, cx| {
-// Some(new_pane.open_item(project_entry_id, true, cx, build_item))
-// })
-// })
-// .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
-// })
-// }
-
-// pub(crate) fn load_path(
-// &mut self,
-// path: ProjectPath,
-// cx: &mut ViewContext<Self>,
-// ) -> Task<
-// Result<(
-// ProjectEntryId,
-// impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
-// )>,
-// > {
-// let project = self.project().clone();
-// let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
-// cx.spawn(|_, mut cx| async move {
-// let (project_entry_id, project_item) = project_item.await?;
-// let build_item = cx.update(|cx| {
-// cx.default_global::<ProjectItemBuilders>()
-// .get(&project_item.model_type())
-// .ok_or_else(|| anyhow!("no item builder for project item"))
-// .cloned()
-// })?;
-// let build_item =
-// move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
-// Ok((project_entry_id, build_item))
-// })
-// }
-
-// pub fn open_project_item<T>(
-// &mut self,
-// project_item: ModelHandle<T::Item>,
-// cx: &mut ViewContext<Self>,
-// ) -> ViewHandle<T>
-// where
-// T: ProjectItem,
-// {
-// use project::Item as _;
-
-// let entry_id = project_item.read(cx).entry_id(cx);
-// if let Some(item) = entry_id
-// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
-// .and_then(|item| item.downcast())
-// {
-// self.activate_item(&item, cx);
-// return item;
-// }
-
-// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
-// self.add_item(Box::new(item.clone()), cx);
-// item
-// }
-
-// pub fn split_project_item<T>(
-// &mut self,
-// project_item: ModelHandle<T::Item>,
-// cx: &mut ViewContext<Self>,
-// ) -> ViewHandle<T>
-// where
-// T: ProjectItem,
-// {
-// use project::Item as _;
-
-// let entry_id = project_item.read(cx).entry_id(cx);
-// if let Some(item) = entry_id
-// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
-// .and_then(|item| item.downcast())
-// {
-// self.activate_item(&item, cx);
-// return item;
-// }
-
-// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
-// self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
-// item
-// }
-
-// pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-// if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
-// self.active_pane.update(cx, |pane, cx| {
-// pane.add_item(Box::new(shared_screen), false, true, None, cx)
-// });
-// }
-// }
-
-// pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
-// let result = self.panes.iter().find_map(|pane| {
-// pane.read(cx)
-// .index_for_item(item)
-// .map(|ix| (pane.clone(), ix))
-// });
-// if let Some((pane, ix)) = result {
-// pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
-// true
-// } else {
-// false
-// }
-// }
-
-// fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
-// let panes = self.center.panes();
-// if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
-// cx.focus(&pane);
-// } else {
-// self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
-// }
-// }
-
-// pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
-// let panes = self.center.panes();
-// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
-// let next_ix = (ix + 1) % panes.len();
-// let next_pane = panes[next_ix].clone();
-// cx.focus(&next_pane);
-// }
-// }
-
-// pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
-// let panes = self.center.panes();
-// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
-// let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
-// let prev_pane = panes[prev_ix].clone();
-// cx.focus(&prev_pane);
-// }
-// }
-
-// pub fn activate_pane_in_direction(
-// &mut self,
-// direction: SplitDirection,
-// cx: &mut ViewContext<Self>,
-// ) {
-// if let Some(pane) = self.find_pane_in_direction(direction, cx) {
-// cx.focus(pane);
-// }
-// }
-
-// pub fn swap_pane_in_direction(
-// &mut self,
-// direction: SplitDirection,
-// cx: &mut ViewContext<Self>,
-// ) {
-// if let Some(to) = self
-// .find_pane_in_direction(direction, cx)
-// .map(|pane| pane.clone())
-// {
-// self.center.swap(&self.active_pane.clone(), &to);
-// cx.notify();
-// }
-// }
-
-// fn find_pane_in_direction(
-// &mut self,
-// direction: SplitDirection,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<&ViewHandle<Pane>> {
-// let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
-// return None;
-// };
-// let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
-// let center = match cursor {
-// Some(cursor) if bounding_box.contains_point(cursor) => cursor,
-// _ => bounding_box.center(),
-// };
-
-// let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
-
-// let target = match direction {
-// SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
-// SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
-// SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
-// SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
-// };
-// self.center.pane_at_pixel_position(target)
-// }
-
-// fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
-// if self.active_pane != pane {
-// self.active_pane = pane.clone();
-// self.status_bar.update(cx, |status_bar, cx| {
-// status_bar.set_active_pane(&self.active_pane, cx);
-// });
-// self.active_item_path_changed(cx);
-// self.last_active_center_pane = Some(pane.downgrade());
-// }
-
-// self.dismiss_zoomed_items_to_reveal(None, cx);
-// if pane.read(cx).is_zoomed() {
-// self.zoomed = Some(pane.downgrade().into_any());
-// } else {
-// self.zoomed = None;
-// }
-// self.zoomed_position = None;
-// self.update_active_view_for_followers(cx);
-
-// cx.notify();
-// }
-
-// fn handle_pane_event(
-// &mut self,
-// pane: ViewHandle<Pane>,
-// event: &pane::Event,
-// cx: &mut ViewContext<Self>,
-// ) {
-// match event {
-// pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
-// pane::Event::Split(direction) => {
-// self.split_and_clone(pane, *direction, cx);
-// }
-// pane::Event::Remove => self.remove_pane(pane, cx),
-// pane::Event::ActivateItem { local } => {
-// if *local {
-// self.unfollow(&pane, cx);
-// }
-// if &pane == self.active_pane() {
-// self.active_item_path_changed(cx);
-// }
-// }
-// pane::Event::ChangeItemTitle => {
-// if pane == self.active_pane {
-// self.active_item_path_changed(cx);
-// }
-// self.update_window_edited(cx);
-// }
-// pane::Event::RemoveItem { item_id } => {
-// self.update_window_edited(cx);
-// if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
-// if entry.get().id() == pane.id() {
-// entry.remove();
-// }
-// }
-// }
-// pane::Event::Focus => {
-// self.handle_pane_focused(pane.clone(), cx);
-// }
-// pane::Event::ZoomIn => {
-// if pane == self.active_pane {
-// pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
-// if pane.read(cx).has_focus() {
-// self.zoomed = Some(pane.downgrade().into_any());
-// self.zoomed_position = None;
-// }
-// cx.notify();
-// }
-// }
-// pane::Event::ZoomOut => {
-// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-// if self.zoomed_position.is_none() {
-// self.zoomed = None;
-// }
-// cx.notify();
-// }
-// }
-
-// self.serialize_workspace(cx);
-// }
-
-// pub fn split_pane(
-// &mut self,
-// pane_to_split: ViewHandle<Pane>,
-// split_direction: SplitDirection,
-// cx: &mut ViewContext<Self>,
-// ) -> ViewHandle<Pane> {
-// let new_pane = self.add_pane(cx);
-// self.center
-// .split(&pane_to_split, &new_pane, split_direction)
-// .unwrap();
-// cx.notify();
-// new_pane
-// }
-
-// pub fn split_and_clone(
-// &mut self,
-// pane: ViewHandle<Pane>,
-// direction: SplitDirection,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<ViewHandle<Pane>> {
-// let item = pane.read(cx).active_item()?;
-// let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
-// let new_pane = self.add_pane(cx);
-// new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
-// self.center.split(&pane, &new_pane, direction).unwrap();
-// Some(new_pane)
-// } else {
-// None
-// };
-// cx.notify();
-// maybe_pane_handle
-// }
-
-// pub fn split_pane_with_item(
-// &mut self,
-// pane_to_split: WeakViewHandle<Pane>,
-// split_direction: SplitDirection,
-// from: WeakViewHandle<Pane>,
-// item_id_to_move: usize,
-// cx: &mut ViewContext<Self>,
-// ) {
-// let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
-// return;
-// };
-// let Some(from) = from.upgrade(cx) else {
-// return;
-// };
-
-// let new_pane = self.add_pane(cx);
-// self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
-// self.center
-// .split(&pane_to_split, &new_pane, split_direction)
-// .unwrap();
-// cx.notify();
-// }
-
-// pub fn split_pane_with_project_entry(
-// &mut self,
-// pane_to_split: WeakViewHandle<Pane>,
-// split_direction: SplitDirection,
-// project_entry: ProjectEntryId,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let pane_to_split = pane_to_split.upgrade(cx)?;
-// let new_pane = self.add_pane(cx);
-// self.center
-// .split(&pane_to_split, &new_pane, split_direction)
-// .unwrap();
-
-// let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
-// let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
-// Some(cx.foreground().spawn(async move {
-// task.await?;
-// Ok(())
-// }))
-// }
-
-// pub fn move_item(
-// &mut self,
-// source: ViewHandle<Pane>,
-// destination: ViewHandle<Pane>,
-// item_id_to_move: usize,
-// destination_index: usize,
-// cx: &mut ViewContext<Self>,
-// ) {
-// let item_to_move = source
-// .read(cx)
-// .items()
-// .enumerate()
-// .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
-
-// if item_to_move.is_none() {
-// log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
-// return;
-// }
-// let (item_ix, item_handle) = item_to_move.unwrap();
-// let item_handle = item_handle.clone();
-
-// if source != destination {
-// // Close item from previous pane
-// source.update(cx, |source, cx| {
-// source.remove_item(item_ix, false, cx);
-// });
-// }
-
-// // This automatically removes duplicate items in the pane
-// destination.update(cx, |destination, cx| {
-// destination.add_item(item_handle, true, true, Some(destination_index), cx);
-// cx.focus_self();
-// });
-// }
-
-// fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
-// if self.center.remove(&pane).unwrap() {
-// self.force_remove_pane(&pane, cx);
-// self.unfollow(&pane, cx);
-// self.last_leaders_by_pane.remove(&pane.downgrade());
-// for removed_item in pane.read(cx).items() {
-// self.panes_by_item.remove(&removed_item.id());
-// }
-
-// cx.notify();
-// } else {
-// self.active_item_path_changed(cx);
-// }
-// }
-
-// pub fn panes(&self) -> &[ViewHandle<Pane>] {
-// &self.panes
-// }
-
-// pub fn active_pane(&self) -> &ViewHandle<Pane> {
-// &self.active_pane
-// }
-
-// fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-// self.follower_states.retain(|_, state| {
-// if state.leader_id == peer_id {
-// for item in state.items_by_leader_view_id.values() {
-// item.set_leader_peer_id(None, cx);
-// }
-// false
-// } else {
-// true
-// }
-// });
-// cx.notify();
-// }
-
-// fn start_following(
-// &mut self,
-// leader_id: PeerId,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let pane = self.active_pane().clone();
-
-// self.last_leaders_by_pane
-// .insert(pane.downgrade(), leader_id);
-// self.unfollow(&pane, cx);
-// self.follower_states.insert(
-// pane.clone(),
-// FollowerState {
-// leader_id,
-// active_view_id: None,
-// items_by_leader_view_id: Default::default(),
-// },
-// );
-// cx.notify();
-
-// let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-// let project_id = self.project.read(cx).remote_id();
-// let request = self.app_state.client.request(proto::Follow {
-// room_id,
-// project_id,
-// leader_id: Some(leader_id),
-// });
-
-// Some(cx.spawn(|this, mut cx| async move {
-// let response = request.await?;
-// this.update(&mut cx, |this, _| {
-// let state = this
-// .follower_states
-// .get_mut(&pane)
-// .ok_or_else(|| anyhow!("following interrupted"))?;
-// state.active_view_id = if let Some(active_view_id) = response.active_view_id {
-// Some(ViewId::from_proto(active_view_id)?)
-// } else {
-// None
-// };
-// Ok::<_, anyhow::Error>(())
-// })??;
-// Self::add_views_from_leader(
-// this.clone(),
-// leader_id,
-// vec![pane],
-// response.views,
-// &mut cx,
-// )
-// .await?;
-// this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
-// Ok(())
-// }))
-// }
-
-// pub fn follow_next_collaborator(
-// &mut self,
-// _: &FollowNextCollaborator,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let collaborators = self.project.read(cx).collaborators();
-// let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
-// let mut collaborators = collaborators.keys().copied();
-// for peer_id in collaborators.by_ref() {
-// if peer_id == leader_id {
-// break;
-// }
-// }
-// collaborators.next()
-// } else if let Some(last_leader_id) =
-// self.last_leaders_by_pane.get(&self.active_pane.downgrade())
-// {
-// if collaborators.contains_key(last_leader_id) {
-// Some(*last_leader_id)
-// } else {
-// None
-// }
-// } else {
-// None
-// };
-
-// let pane = self.active_pane.clone();
-// let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
-// else {
-// return None;
-// };
-// if Some(leader_id) == self.unfollow(&pane, cx) {
-// return None;
-// }
-// self.follow(leader_id, cx)
-// }
-
-// pub fn follow(
-// &mut self,
-// leader_id: PeerId,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<Task<Result<()>>> {
-// let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
-// let project = self.project.read(cx);
-
-// let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
-// return None;
-// };
-
-// let other_project_id = match remote_participant.location {
-// call::ParticipantLocation::External => None,
-// call::ParticipantLocation::UnsharedProject => None,
-// call::ParticipantLocation::SharedProject { project_id } => {
-// if Some(project_id) == project.remote_id() {
-// None
-// } else {
-// Some(project_id)
-// }
-// }
-// };
-
-// // if they are active in another project, follow there.
-// if let Some(project_id) = other_project_id {
-// let app_state = self.app_state.clone();
-// return Some(crate::join_remote_project(
-// project_id,
-// remote_participant.user.id,
-// app_state,
-// cx,
-// ));
-// }
-
-// // if you're already following, find the right pane and focus it.
-// for (pane, state) in &self.follower_states {
-// if leader_id == state.leader_id {
-// cx.focus(pane);
-// return None;
-// }
-// }
-
-// // Otherwise, follow.
-// self.start_following(leader_id, cx)
-// }
-
-// pub fn unfollow(
-// &mut self,
-// pane: &ViewHandle<Pane>,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<PeerId> {
-// let state = self.follower_states.remove(pane)?;
-// let leader_id = state.leader_id;
-// for (_, item) in state.items_by_leader_view_id {
-// item.set_leader_peer_id(None, cx);
-// }
-
-// if self
-// .follower_states
-// .values()
-// .all(|state| state.leader_id != state.leader_id)
-// {
-// let project_id = self.project.read(cx).remote_id();
-// let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-// self.app_state
-// .client
-// .send(proto::Unfollow {
-// room_id,
-// project_id,
-// leader_id: Some(leader_id),
-// })
-// .log_err();
-// }
-
-// cx.notify();
-// Some(leader_id)
-// }
-
-// pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
-// self.follower_states
-// .values()
-// .any(|state| state.leader_id == peer_id)
-// }
-
-// fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// // TODO: There should be a better system in place for this
-// // (https://github.com/zed-industries/zed/issues/1290)
-// let is_fullscreen = cx.window_is_fullscreen();
-// let container_theme = if is_fullscreen {
-// let mut container_theme = theme.titlebar.container;
-// container_theme.padding.left = container_theme.padding.right;
-// container_theme
-// } else {
-// theme.titlebar.container
-// };
-
-// enum TitleBar {}
-// MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
-// Stack::new()
-// .with_children(
-// self.titlebar_item
-// .as_ref()
-// .map(|item| ChildView::new(item, cx)),
-// )
-// .contained()
-// .with_style(container_theme)
-// })
-// .on_click(MouseButton::Left, |event, _, cx| {
-// if event.click_count == 2 {
-// cx.zoom_window();
-// }
-// })
-// .constrained()
-// .with_height(theme.titlebar.height)
-// .into_any_named("titlebar")
-// }
-
-// fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
-// let active_entry = self.active_project_path(cx);
-// self.project
-// .update(cx, |project, cx| project.set_active_path(active_entry, cx));
-// self.update_window_title(cx);
-// }
-
-// fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
-// let project = self.project().read(cx);
-// let mut title = String::new();
-
-// if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
-// let filename = path
-// .path
-// .file_name()
-// .map(|s| s.to_string_lossy())
-// .or_else(|| {
-// Some(Cow::Borrowed(
-// project
-// .worktree_for_id(path.worktree_id, cx)?
-// .read(cx)
-// .root_name(),
-// ))
-// });
-
-// if let Some(filename) = filename {
-// title.push_str(filename.as_ref());
-// title.push_str(" — ");
-// }
-// }
-
-// for (i, name) in project.worktree_root_names(cx).enumerate() {
-// if i > 0 {
-// title.push_str(", ");
-// }
-// title.push_str(name);
-// }
-
-// if title.is_empty() {
-// title = "empty project".to_string();
-// }
-
-// if project.is_remote() {
-// title.push_str(" ↙");
-// } else if project.is_shared() {
-// title.push_str(" ↗");
-// }
-
-// cx.set_window_title(&title);
-// }
-
-// fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
-// let is_edited = !self.project.read(cx).is_read_only()
-// && self
-// .items(cx)
-// .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
-// if is_edited != self.window_edited {
-// self.window_edited = is_edited;
-// cx.set_window_edited(self.window_edited)
-// }
-// }
-
-// fn render_disconnected_overlay(
-// &self,
-// cx: &mut ViewContext<Workspace>,
-// ) -> Option<AnyElement<Workspace>> {
-// if self.project.read(cx).is_read_only() {
-// enum DisconnectedOverlay {}
-// Some(
-// MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
-// let theme = &theme::current(cx);
-// Label::new(
-// "Your connection to the remote project has been lost.",
-// theme.workspace.disconnected_overlay.text.clone(),
-// )
-// .aligned()
-// .contained()
-// .with_style(theme.workspace.disconnected_overlay.container)
-// })
-// .with_cursor_style(CursorStyle::Arrow)
-// .capture_all()
-// .into_any_named("disconnected overlay"),
-// )
-// } else {
-// None
-// }
-// }
-
-// fn render_notifications(
-// &self,
-// theme: &theme::Workspace,
-// cx: &AppContext,
-// ) -> Option<AnyElement<Workspace>> {
-// if self.notifications.is_empty() {
-// None
-// } else {
-// Some(
-// Flex::column()
-// .with_children(self.notifications.iter().map(|(_, _, notification)| {
-// ChildView::new(notification.as_any(), cx)
-// .contained()
-// .with_style(theme.notification)
-// }))
-// .constrained()
-// .with_width(theme.notifications.width)
-// .contained()
-// .with_style(theme.notifications.container)
-// .aligned()
-// .bottom()
-// .right()
-// .into_any(),
-// )
-// }
-// }
-
-// // RPC handlers
-
-// fn handle_follow(
-// &mut self,
-// follower_project_id: Option<u64>,
-// cx: &mut ViewContext<Self>,
-// ) -> proto::FollowResponse {
-// let client = &self.app_state.client;
-// let project_id = self.project.read(cx).remote_id();
-
-// let active_view_id = self.active_item(cx).and_then(|i| {
-// Some(
-// i.to_followable_item_handle(cx)?
-// .remote_id(client, cx)?
-// .to_proto(),
-// )
-// });
-
-// cx.notify();
-
-// self.last_active_view_id = active_view_id.clone();
-// proto::FollowResponse {
-// active_view_id,
-// views: self
-// .panes()
-// .iter()
-// .flat_map(|pane| {
-// let leader_id = self.leader_for_pane(pane);
-// pane.read(cx).items().filter_map({
-// let cx = &cx;
-// move |item| {
-// let item = item.to_followable_item_handle(cx)?;
-// if (project_id.is_none() || project_id != follower_project_id)
-// && item.is_project_item(cx)
-// {
-// return None;
-// }
-// let id = item.remote_id(client, cx)?.to_proto();
-// let variant = item.to_state_proto(cx)?;
-// Some(proto::View {
-// id: Some(id),
-// leader_id,
-// variant: Some(variant),
-// })
-// }
-// })
-// })
-// .collect(),
-// }
-// }
-
-// fn handle_update_followers(
-// &mut self,
-// leader_id: PeerId,
-// message: proto::UpdateFollowers,
-// _cx: &mut ViewContext<Self>,
-// ) {
-// self.leader_updates_tx
-// .unbounded_send((leader_id, message))
-// .ok();
-// }
-
-// async fn process_leader_update(
-// this: &WeakViewHandle<Self>,
-// leader_id: PeerId,
-// update: proto::UpdateFollowers,
-// cx: &mut AsyncAppContext,
-// ) -> Result<()> {
-// match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
-// proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
-// this.update(cx, |this, _| {
-// for (_, state) in &mut this.follower_states {
-// if state.leader_id == leader_id {
-// state.active_view_id =
-// if let Some(active_view_id) = update_active_view.id.clone() {
-// Some(ViewId::from_proto(active_view_id)?)
-// } else {
-// None
-// };
-// }
-// }
-// anyhow::Ok(())
-// })??;
-// }
-// proto::update_followers::Variant::UpdateView(update_view) => {
-// let variant = update_view
-// .variant
-// .ok_or_else(|| anyhow!("missing update view variant"))?;
-// let id = update_view
-// .id
-// .ok_or_else(|| anyhow!("missing update view id"))?;
-// let mut tasks = Vec::new();
-// this.update(cx, |this, cx| {
-// let project = this.project.clone();
-// for (_, state) in &mut this.follower_states {
-// if state.leader_id == leader_id {
-// let view_id = ViewId::from_proto(id.clone())?;
-// if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
-// tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
-// }
-// }
-// }
-// anyhow::Ok(())
-// })??;
-// try_join_all(tasks).await.log_err();
-// }
-// proto::update_followers::Variant::CreateView(view) => {
-// let panes = this.read_with(cx, |this, _| {
-// this.follower_states
-// .iter()
-// .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
-// .cloned()
-// .collect()
-// })?;
-// Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
-// }
-// }
-// this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
-// Ok(())
-// }
-
-// async fn add_views_from_leader(
-// this: WeakViewHandle<Self>,
-// leader_id: PeerId,
-// panes: Vec<ViewHandle<Pane>>,
-// views: Vec<proto::View>,
-// cx: &mut AsyncAppContext,
-// ) -> Result<()> {
-// let this = this
-// .upgrade(cx)
-// .ok_or_else(|| anyhow!("workspace dropped"))?;
-
-// let item_builders = cx.update(|cx| {
-// cx.default_global::<FollowableItemBuilders>()
-// .values()
-// .map(|b| b.0)
-// .collect::<Vec<_>>()
-// });
-
-// let mut item_tasks_by_pane = HashMap::default();
-// for pane in panes {
-// let mut item_tasks = Vec::new();
-// let mut leader_view_ids = Vec::new();
-// for view in &views {
-// let Some(id) = &view.id else { continue };
-// let id = ViewId::from_proto(id.clone())?;
-// let mut variant = view.variant.clone();
-// if variant.is_none() {
-// Err(anyhow!("missing view variant"))?;
-// }
-// for build_item in &item_builders {
-// let task = cx
-// .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
-// if let Some(task) = task {
-// item_tasks.push(task);
-// leader_view_ids.push(id);
-// break;
-// } else {
-// assert!(variant.is_some());
-// }
-// }
-// }
-
-// item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
-// }
-
-// for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
-// let items = futures::future::try_join_all(item_tasks).await?;
-// this.update(cx, |this, cx| {
-// let state = this.follower_states.get_mut(&pane)?;
-// for (id, item) in leader_view_ids.into_iter().zip(items) {
-// item.set_leader_peer_id(Some(leader_id), cx);
-// state.items_by_leader_view_id.insert(id, item);
-// }
-
-// Some(())
-// });
-// }
-// Ok(())
-// }
-
-// fn update_active_view_for_followers(&mut self, cx: &AppContext) {
-// let mut is_project_item = true;
-// let mut update = proto::UpdateActiveView::default();
-// if self.active_pane.read(cx).has_focus() {
-// let item = self
-// .active_item(cx)
-// .and_then(|item| item.to_followable_item_handle(cx));
-// if let Some(item) = item {
-// is_project_item = item.is_project_item(cx);
-// update = proto::UpdateActiveView {
-// id: item
-// .remote_id(&self.app_state.client, cx)
-// .map(|id| id.to_proto()),
-// leader_id: self.leader_for_pane(&self.active_pane),
-// };
-// }
-// }
-
-// if update.id != self.last_active_view_id {
-// self.last_active_view_id = update.id.clone();
-// self.update_followers(
-// is_project_item,
-// proto::update_followers::Variant::UpdateActiveView(update),
-// cx,
-// );
-// }
-// }
-
-// fn update_followers(
-// &self,
-// project_only: bool,
-// update: proto::update_followers::Variant,
-// cx: &AppContext,
-// ) -> Option<()> {
-// let project_id = if project_only {
-// self.project.read(cx).remote_id()
-// } else {
-// None
-// };
-// self.app_state().workspace_store.read_with(cx, |store, cx| {
-// store.update_followers(project_id, update, cx)
-// })
-// }
-
-// pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
-// self.follower_states.get(pane).map(|state| state.leader_id)
-// }
-
-// fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
-// cx.notify();
-
-// let call = self.active_call()?;
-// let room = call.read(cx).room()?.read(cx);
-// let participant = room.remote_participant_for_peer_id(leader_id)?;
-// let mut items_to_activate = Vec::new();
-
-// let leader_in_this_app;
-// let leader_in_this_project;
-// match participant.location {
-// call::ParticipantLocation::SharedProject { project_id } => {
-// leader_in_this_app = true;
-// leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
-// }
-// call::ParticipantLocation::UnsharedProject => {
-// leader_in_this_app = true;
-// leader_in_this_project = false;
-// }
-// call::ParticipantLocation::External => {
-// leader_in_this_app = false;
-// leader_in_this_project = false;
-// }
-// };
-
-// for (pane, state) in &self.follower_states {
-// if state.leader_id != leader_id {
-// continue;
-// }
-// if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
-// if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
-// if leader_in_this_project || !item.is_project_item(cx) {
-// items_to_activate.push((pane.clone(), item.boxed_clone()));
-// }
-// } else {
-// log::warn!(
-// "unknown view id {:?} for leader {:?}",
-// active_view_id,
-// leader_id
-// );
-// }
-// continue;
-// }
-// if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
-// items_to_activate.push((pane.clone(), Box::new(shared_screen)));
-// }
-// }
-
-// for (pane, item) in items_to_activate {
-// let pane_was_focused = pane.read(cx).has_focus();
-// if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
-// pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
-// } else {
-// pane.update(cx, |pane, cx| {
-// pane.add_item(item.boxed_clone(), false, false, None, cx)
-// });
-// }
-
-// if pane_was_focused {
-// pane.update(cx, |pane, cx| pane.focus_active_item(cx));
-// }
-// }
-
-// None
-// }
-
-// fn shared_screen_for_peer(
-// &self,
-// peer_id: PeerId,
-// pane: &ViewHandle<Pane>,
-// cx: &mut ViewContext<Self>,
-// ) -> Option<ViewHandle<SharedScreen>> {
-// let call = self.active_call()?;
-// let room = call.read(cx).room()?.read(cx);
-// let participant = room.remote_participant_for_peer_id(peer_id)?;
-// let track = participant.video_tracks.values().next()?.clone();
-// let user = participant.user.clone();
-
-// for item in pane.read(cx).items_of_type::<SharedScreen>() {
-// if item.read(cx).peer_id == peer_id {
-// return Some(item);
-// }
-// }
-
-// Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
-// }
-
-// pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
-// if active {
-// self.update_active_view_for_followers(cx);
-// cx.background()
-// .spawn(persistence::DB.update_timestamp(self.database_id()))
-// .detach();
-// } else {
-// for pane in &self.panes {
-// pane.update(cx, |pane, cx| {
-// if let Some(item) = pane.active_item() {
-// item.workspace_deactivated(cx);
-// }
-// if matches!(
-// settings::get::<WorkspaceSettings>(cx).autosave,
-// AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
-// ) {
-// for item in pane.items() {
-// Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
-// .detach_and_log_err(cx);
-// }
-// }
-// });
-// }
-// }
-// }
-
-// fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
-// self.active_call.as_ref().map(|(call, _)| call)
-// }
-
-// fn on_active_call_event(
-// &mut self,
-// _: ModelHandle<ActiveCall>,
-// event: &call::room::Event,
-// cx: &mut ViewContext<Self>,
-// ) {
-// match event {
-// call::room::Event::ParticipantLocationChanged { participant_id }
-// | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
-// self.leader_updated(*participant_id, cx);
-// }
-// _ => {}
-// }
-// }
-
-// pub fn database_id(&self) -> WorkspaceId {
-// self.database_id
-// }
-
-// fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
-// let project = self.project().read(cx);
-
-// if project.is_local() {
-// Some(
-// project
-// .visible_worktrees(cx)
-// .map(|worktree| worktree.read(cx).abs_path())
-// .collect::<Vec<_>>()
-// .into(),
-// )
-// } else {
-// None
-// }
-// }
-
-// fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
-// match member {
-// Member::Axis(PaneAxis { members, .. }) => {
-// for child in members.iter() {
-// self.remove_panes(child.clone(), cx)
-// }
-// }
-// Member::Pane(pane) => {
-// self.force_remove_pane(&pane, cx);
-// }
-// }
-// }
-
-// fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
-// self.panes.retain(|p| p != pane);
-// cx.focus(self.panes.last().unwrap());
-// if self.last_active_center_pane == Some(pane.downgrade()) {
-// self.last_active_center_pane = None;
-// }
-// cx.notify();
-// }
-
-// fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
-// self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
-// cx.background().timer(Duration::from_millis(100)).await;
-// this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
-// .ok();
-// }));
-// }
-
-// fn serialize_workspace(&self, cx: &ViewContext<Self>) {
-// fn serialize_pane_handle(
-// pane_handle: &ViewHandle<Pane>,
-// cx: &AppContext,
-// ) -> SerializedPane {
-// let (items, active) = {
-// let pane = pane_handle.read(cx);
-// let active_item_id = pane.active_item().map(|item| item.id());
-// (
-// pane.items()
-// .filter_map(|item_handle| {
-// Some(SerializedItem {
-// kind: Arc::from(item_handle.serialized_item_kind()?),
-// item_id: item_handle.id(),
-// active: Some(item_handle.id()) == active_item_id,
-// })
-// })
-// .collect::<Vec<_>>(),
-// pane.has_focus(),
-// )
-// };
-
-// SerializedPane::new(items, active)
-// }
-
-// fn build_serialized_pane_group(
-// pane_group: &Member,
-// cx: &AppContext,
-// ) -> SerializedPaneGroup {
-// match pane_group {
-// Member::Axis(PaneAxis {
-// axis,
-// members,
-// flexes,
-// bounding_boxes: _,
-// }) => SerializedPaneGroup::Group {
-// axis: *axis,
-// children: members
-// .iter()
-// .map(|member| build_serialized_pane_group(member, cx))
-// .collect::<Vec<_>>(),
-// flexes: Some(flexes.borrow().clone()),
-// },
-// Member::Pane(pane_handle) => {
-// SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
-// }
-// }
-// }
-
-// fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
-// let left_dock = this.left_dock.read(cx);
-// let left_visible = left_dock.is_open();
-// let left_active_panel = left_dock.visible_panel().and_then(|panel| {
-// Some(
-// cx.view_ui_name(panel.as_any().window(), panel.id())?
-// .to_string(),
-// )
-// });
-// let left_dock_zoom = left_dock
-// .visible_panel()
-// .map(|panel| panel.is_zoomed(cx))
-// .unwrap_or(false);
-
-// let right_dock = this.right_dock.read(cx);
-// let right_visible = right_dock.is_open();
-// let right_active_panel = right_dock.visible_panel().and_then(|panel| {
-// Some(
-// cx.view_ui_name(panel.as_any().window(), panel.id())?
-// .to_string(),
-// )
-// });
-// let right_dock_zoom = right_dock
-// .visible_panel()
-// .map(|panel| panel.is_zoomed(cx))
-// .unwrap_or(false);
-
-// let bottom_dock = this.bottom_dock.read(cx);
-// let bottom_visible = bottom_dock.is_open();
-// let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
-// Some(
-// cx.view_ui_name(panel.as_any().window(), panel.id())?
-// .to_string(),
-// )
-// });
-// let bottom_dock_zoom = bottom_dock
-// .visible_panel()
-// .map(|panel| panel.is_zoomed(cx))
-// .unwrap_or(false);
-
-// DockStructure {
-// left: DockData {
-// visible: left_visible,
-// active_panel: left_active_panel,
-// zoom: left_dock_zoom,
-// },
-// right: DockData {
-// visible: right_visible,
-// active_panel: right_active_panel,
-// zoom: right_dock_zoom,
-// },
-// bottom: DockData {
-// visible: bottom_visible,
-// active_panel: bottom_active_panel,
-// zoom: bottom_dock_zoom,
-// },
-// }
-// }
-
-// if let Some(location) = self.location(cx) {
-// // Load bearing special case:
-// // - with_local_workspace() relies on this to not have other stuff open
-// // when you open your log
-// if !location.paths().is_empty() {
-// let center_group = build_serialized_pane_group(&self.center.root, cx);
-// let docks = build_serialized_docks(self, cx);
-
-// let serialized_workspace = SerializedWorkspace {
-// id: self.database_id,
-// location,
-// center_group,
-// bounds: Default::default(),
-// display: Default::default(),
-// docks,
-// };
-
-// cx.background()
-// .spawn(persistence::DB.save_workspace(serialized_workspace))
-// .detach();
-// }
-// }
-// }
-
-// pub(crate) fn load_workspace(
-// workspace: WeakViewHandle<Workspace>,
-// serialized_workspace: SerializedWorkspace,
-// paths_to_open: Vec<Option<ProjectPath>>,
-// cx: &mut AppContext,
-// ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
-// cx.spawn(|mut cx| async move {
-// let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
-// (
-// workspace.project().clone(),
-// workspace.last_active_center_pane.clone(),
-// )
-// })?;
-
-// let mut center_group = None;
-// let mut center_items = None;
-// // Traverse the splits tree and add to things
-// if let Some((group, active_pane, items)) = serialized_workspace
-// .center_group
-// .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
-// .await
-// {
-// center_items = Some(items);
-// center_group = Some((group, active_pane))
-// }
-
-// let mut items_by_project_path = cx.read(|cx| {
-// center_items
-// .unwrap_or_default()
-// .into_iter()
-// .filter_map(|item| {
-// let item = item?;
-// let project_path = item.project_path(cx)?;
-// Some((project_path, item))
-// })
-// .collect::<HashMap<_, _>>()
-// });
-
-// let opened_items = paths_to_open
-// .into_iter()
-// .map(|path_to_open| {
-// path_to_open
-// .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
-// })
-// .collect::<Vec<_>>();
-
-// // Remove old panes from workspace panes list
-// workspace.update(&mut cx, |workspace, cx| {
-// if let Some((center_group, active_pane)) = center_group {
-// workspace.remove_panes(workspace.center.root.clone(), cx);
-
-// // Swap workspace center group
-// workspace.center = PaneGroup::with_root(center_group);
-
-// // Change the focus to the workspace first so that we retrigger focus in on the pane.
-// cx.focus_self();
-
-// if let Some(active_pane) = active_pane {
-// cx.focus(&active_pane);
-// } else {
-// cx.focus(workspace.panes.last().unwrap());
-// }
-// } else {
-// let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
-// if let Some(old_center_handle) = old_center_handle {
-// cx.focus(&old_center_handle)
-// } else {
-// cx.focus_self()
-// }
-// }
-
-// let docks = serialized_workspace.docks;
-// workspace.left_dock.update(cx, |dock, cx| {
-// dock.set_open(docks.left.visible, cx);
-// if let Some(active_panel) = docks.left.active_panel {
-// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
-// dock.activate_panel(ix, cx);
-// }
-// }
-// dock.active_panel()
-// .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
-// if docks.left.visible && docks.left.zoom {
-// cx.focus_self()
-// }
-// });
-// // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
-// workspace.right_dock.update(cx, |dock, cx| {
-// dock.set_open(docks.right.visible, cx);
-// if let Some(active_panel) = docks.right.active_panel {
-// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
-// dock.activate_panel(ix, cx);
-// }
-// }
-// dock.active_panel()
-// .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
-
-// if docks.right.visible && docks.right.zoom {
-// cx.focus_self()
-// }
-// });
-// workspace.bottom_dock.update(cx, |dock, cx| {
-// dock.set_open(docks.bottom.visible, cx);
-// if let Some(active_panel) = docks.bottom.active_panel {
-// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
-// dock.activate_panel(ix, cx);
-// }
-// }
+type ProjectItemBuilders =
+ HashMap<TypeId, fn(Handle<Project>, AnyHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
+pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
+ cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
+ builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
+ let item = model.downcast::<I::Item>().unwrap();
+ Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
+ });
+ });
+}
-// dock.active_panel()
-// .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
+// type FollowableItemBuilder = fn(
+// ViewHandle<Pane>,
+// ViewHandle<Workspace>,
+// ViewId,
+// &mut Option<proto::view::Variant>,
+// &mut AppContext,
+// ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
+// type FollowableItemBuilders = HashMap<
+// TypeId,
+// (
+// FollowableItemBuilder,
+// fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
+// ),
+// >;
+// pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
+// cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
+// builders.insert(
+// TypeId::of::<I>(),
+// (
+// |pane, workspace, id, state, cx| {
+// I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
+// cx.foreground()
+// .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
+// })
+// },
+// |this| Box::new(this.clone().downcast::<I>().unwrap()),
+// ),
+// );
+// });
+// }
-// if docks.bottom.visible && docks.bottom.zoom {
-// cx.focus_self()
-// }
-// });
+// type ItemDeserializers = HashMap<
+// Arc<str>,
+// fn(
+// ModelHandle<Project>,
+// WeakViewHandle<Workspace>,
+// WorkspaceId,
+// ItemId,
+// &mut ViewContext<Pane>,
+// ) -> Task<Result<Box<dyn ItemHandle>>>,
+// >;
+// pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
+// cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
+// if let Some(serialized_item_kind) = I::serialized_item_kind() {
+// deserializers.insert(
+// Arc::from(serialized_item_kind),
+// |project, workspace, workspace_id, item_id, cx| {
+// let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
+// cx.foreground()
+// .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
+// },
+// );
+// }
+// });
+// }
-// cx.notify();
-// })?;
+pub struct AppState {
+ pub languages: Arc<LanguageRegistry>,
+ pub client: Arc<Client>,
+ pub user_store: Handle<UserStore>,
+ pub workspace_store: Handle<WorkspaceStore>,
+ pub fs: Arc<dyn fs2::Fs>,
+ pub build_window_options:
+ fn(Option<WindowBounds>, Option<DisplayId>, &MainThread<AppContext>) -> WindowOptions,
+ pub initialize_workspace:
+ fn(WeakHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<anyhow::Result<()>>,
+ pub node_runtime: Arc<dyn NodeRuntime>,
+}
-// // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
-// workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
+pub struct WorkspaceStore {
+ workspaces: HashSet<WeakHandle<Workspace>>,
+ followers: Vec<Follower>,
+ client: Arc<Client>,
+ _subscriptions: Vec<client2::Subscription>,
+}
-// Ok(opened_items)
-// })
-// }
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
+struct Follower {
+ project_id: Option<u64>,
+ peer_id: PeerId,
+}
+// impl AppState {
// #[cfg(any(test, feature = "test-support"))]
-// pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+// pub fn test(cx: &mut AppContext) -> Arc<Self> {
// use node_runtime::FakeNodeRuntime;
+// use settings::SettingsStore;
-// let client = project.read(cx).client();
-// let user_store = project.read(cx).user_store();
+// if !cx.has_global::<SettingsStore>() {
+// cx.set_global(SettingsStore::test(cx));
+// }
+// let fs = fs::FakeFs::new(cx.background().clone());
+// let languages = Arc::new(LanguageRegistry::test());
+// let http_client = util::http::FakeHttpClient::with_404_response();
+// let client = Client::new(http_client.clone(), cx);
+// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
-// let app_state = Arc::new(AppState {
-// languages: project.read(cx).languages().clone(),
-// workspace_store,
+
+// theme::init((), cx);
+// client::init(&client, cx);
+// crate::init_settings(cx);
+
+// Arc::new(Self {
// client,
+// fs,
+// languages,
// user_store,
-// fs: project.read(cx).fs().clone(),
-// build_window_options: |_, _, _| Default::default(),
-// initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+// // channel_store,
+// workspace_store,
// node_runtime: FakeNodeRuntime::new(),
-// });
-// Self::new(0, project, app_state, cx)
-// }
-
-// fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
-// let dock = match position {
-// DockPosition::Left => &self.left_dock,
-// DockPosition::Right => &self.right_dock,
-// DockPosition::Bottom => &self.bottom_dock,
-// };
-// let active_panel = dock.read(cx).visible_panel()?;
-// let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
-// dock.read(cx).render_placeholder(cx)
-// } else {
-// ChildView::new(dock, cx).into_any()
-// };
-
-// Some(
-// element
-// .constrained()
-// .dynamically(move |constraint, _, cx| match position {
-// DockPosition::Left | DockPosition::Right => SizeConstraint::new(
-// Vector2F::new(20., constraint.min.y()),
-// Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
-// ),
-// DockPosition::Bottom => SizeConstraint::new(
-// Vector2F::new(constraint.min.x(), 20.),
-// Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
-// ),
-// })
-// .into_any(),
-// )
+// initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+// build_window_options: |_, _, _| Default::default(),
+// })
// }
// }
-// fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
-// ZED_WINDOW_POSITION
-// .zip(*ZED_WINDOW_SIZE)
-// .map(|(position, size)| {
-// WindowBounds::Fixed(RectF::new(
-// cx.platform().screens()[0].bounds().origin() + position,
-// size,
-// ))
-// })
+// struct DelayedDebouncedEditAction {
+// task: Option<Task<()>>,
+// cancel_channel: Option<oneshot::Sender<()>>,
// }
-// async fn open_items(
-// serialized_workspace: Option<SerializedWorkspace>,
-// workspace: &WeakViewHandle<Workspace>,
-// mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
-// app_state: Arc<AppState>,
-// mut cx: AsyncAppContext,
-// ) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
-// let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
-
-// if let Some(serialized_workspace) = serialized_workspace {
-// let workspace = workspace.clone();
-// let restored_items = cx
-// .update(|cx| {
-// Workspace::load_workspace(
-// workspace,
-// serialized_workspace,
-// project_paths_to_open
-// .iter()
-// .map(|(_, project_path)| project_path)
-// .cloned()
-// .collect(),
-// cx,
-// )
-// })
-// .await?;
-
-// let restored_project_paths = cx.read(|cx| {
-// restored_items
-// .iter()
-// .filter_map(|item| item.as_ref()?.project_path(cx))
-// .collect::<HashSet<_>>()
-// });
-
-// for restored_item in restored_items {
-// opened_items.push(restored_item.map(Ok));
-// }
-
-// project_paths_to_open
-// .iter_mut()
-// .for_each(|(_, project_path)| {
-// if let Some(project_path_to_open) = project_path {
-// if restored_project_paths.contains(project_path_to_open) {
-// *project_path = None;
-// }
-// }
-// });
-// } else {
-// for _ in 0..project_paths_to_open.len() {
-// opened_items.push(None);
+// impl DelayedDebouncedEditAction {
+// fn new() -> DelayedDebouncedEditAction {
+// DelayedDebouncedEditAction {
+// task: None,
+// cancel_channel: None,
// }
// }
-// assert!(opened_items.len() == project_paths_to_open.len());
-
-// let tasks =
-// project_paths_to_open
-// .into_iter()
-// .enumerate()
-// .map(|(i, (abs_path, project_path))| {
-// let workspace = workspace.clone();
-// cx.spawn(|mut cx| {
-// let fs = app_state.fs.clone();
-// async move {
-// let file_project_path = project_path?;
-// if fs.is_file(&abs_path).await {
-// Some((
-// i,
-// workspace
-// .update(&mut cx, |workspace, cx| {
-// workspace.open_path(file_project_path, None, true, cx)
-// })
-// .log_err()?
-// .await,
-// ))
-// } else {
-// None
-// }
-// }
-// })
-// });
-// for maybe_opened_path in futures::future::join_all(tasks.into_iter())
-// .await
-// .into_iter()
+// fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
+// where
+// F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
// {
-// if let Some((i, path_open_result)) = maybe_opened_path {
-// opened_items[i] = Some(path_open_result);
+// if let Some(channel) = self.cancel_channel.take() {
+// _ = channel.send(());
// }
-// }
-// Ok(opened_items)
-// }
+// let (sender, mut receiver) = oneshot::channel::<()>();
+// self.cancel_channel = Some(sender);
+
+// let previous_task = self.task.take();
+// self.task = Some(cx.spawn(|workspace, mut cx| async move {
+// let mut timer = cx.background().timer(delay).fuse();
+// if let Some(previous_task) = previous_task {
+// previous_task.await;
+// }
-// fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
-// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
-// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
-// const MESSAGE_ID: usize = 2;
+// futures::select_biased! {
+// _ = receiver => return,
+// _ = timer => {}
+// }
-// if workspace
-// .read_with(cx, |workspace, cx| {
-// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-// })
-// .unwrap_or(false)
-// {
-// return;
+// if let Some(result) = workspace
+// .update(&mut cx, |workspace, cx| (func)(workspace, cx))
+// .log_err()
+// {
+// result.await.log_err();
+// }
+// }));
// }
+// }
-// if db::kvp::KEY_VALUE_STORE
-// .read_kvp(NEW_DOCK_HINT_KEY)
-// .ok()
-// .flatten()
-// .is_some()
-// {
-// if !workspace
-// .read_with(cx, |workspace, cx| {
-// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-// })
-// .unwrap_or(false)
-// {
-// cx.update(|cx| {
-// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
-// let entry = tracker
-// .entry(TypeId::of::<MessageNotification>())
-// .or_default();
-// if !entry.contains(&MESSAGE_ID) {
-// entry.push(MESSAGE_ID);
-// }
-// });
-// });
-// }
+// pub enum Event {
+// PaneAdded(ViewHandle<Pane>),
+// ContactRequestedJoin(u64),
+// }
-// return;
-// }
+pub struct Workspace {
+ weak_self: WeakHandle<Self>,
+ // modal: Option<ActiveModal>,
+ // zoomed: Option<AnyWeakViewHandle>,
+ // zoomed_position: Option<DockPosition>,
+ // center: PaneGroup,
+ // left_dock: ViewHandle<Dock>,
+ // bottom_dock: ViewHandle<Dock>,
+ // right_dock: ViewHandle<Dock>,
+ panes: Vec<View<Pane>>,
+ // panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
+ // active_pane: ViewHandle<Pane>,
+ last_active_center_pane: Option<WeakView<Pane>>,
+ // last_active_view_id: Option<proto::ViewId>,
+ // status_bar: ViewHandle<StatusBar>,
+ // titlebar_item: Option<AnyViewHandle>,
+ // notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
+ project: Handle<Project>,
+ // follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
+ // last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
+ // window_edited: bool,
+ // active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
+ // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
+ // database_id: WorkspaceId,
+ app_state: Arc<AppState>,
+ // subscriptions: Vec<Subscription>,
+ // _apply_leader_updates: Task<Result<()>>,
+ // _observe_current_user: Task<Result<()>>,
+ // _schedule_serialize: Option<Task<()>>,
+ // pane_history_timestamp: Arc<AtomicUsize>,
+}
-// cx.spawn(|_| async move {
-// db::kvp::KEY_VALUE_STORE
-// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
-// .await
-// .ok();
-// })
-// .detach();
+// struct ActiveModal {
+// view: Box<dyn ModalHandle>,
+// previously_focused_view_id: Option<usize>,
+// }
-// workspace
-// .update(cx, |workspace, cx| {
-// workspace.show_notification_once(2, cx, |cx| {
-// cx.add_view(|_| {
-// MessageNotification::new_element(|text, _| {
-// Text::new(
-// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
-// text,
-// )
-// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
-// let code_span_background_color = settings::get::<ThemeSettings>(cx)
-// .theme
-// .editor
-// .document_highlight_read_background;
-
-// cx.scene().push_quad(gpui::Quad {
-// bounds,
-// background: Some(code_span_background_color),
-// border: Default::default(),
-// corner_radii: (2.0).into(),
-// })
-// })
-// .into_any()
-// })
-// .with_click_message("Read more about the new panel system")
-// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
-// })
-// })
-// })
-// .ok();
+// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+// pub struct ViewId {
+// pub creator: PeerId,
+// pub id: u64,
// }
+#[derive(Default)]
+struct FollowerState {
+ leader_id: PeerId,
+ active_view_id: Option<ViewId>,
+ items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
+}
+
+// enum WorkspaceBounds {}
+
+impl Workspace {
+ // pub fn new(
+ // workspace_id: WorkspaceId,
+ // project: ModelHandle<Project>,
+ // app_state: Arc<AppState>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Self {
+ // cx.observe(&project, |_, _, cx| cx.notify()).detach();
+ // cx.subscribe(&project, move |this, _, event, cx| {
+ // match event {
+ // project::Event::RemoteIdChanged(_) => {
+ // this.update_window_title(cx);
+ // }
+
+ // project::Event::CollaboratorLeft(peer_id) => {
+ // this.collaborator_left(*peer_id, cx);
+ // }
+
+ // project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
+ // this.update_window_title(cx);
+ // this.serialize_workspace(cx);
+ // }
+
+ // project::Event::DisconnectedFromHost => {
+ // this.update_window_edited(cx);
+ // cx.blur();
+ // }
+
+ // project::Event::Closed => {
+ // cx.remove_window();
+ // }
+
+ // project::Event::DeletedEntry(entry_id) => {
+ // for pane in this.panes.iter() {
+ // pane.update(cx, |pane, cx| {
+ // pane.handle_deleted_project_item(*entry_id, cx)
+ // });
+ // }
+ // }
+
+ // project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+ // cx.add_view(|_| MessageNotification::new(message.clone()))
+ // }),
+
+ // _ => {}
+ // }
+ // cx.notify()
+ // })
+ // .detach();
+
+ // let weak_handle = cx.weak_handle();
+ // let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
+
+ // let center_pane = cx.add_view(|cx| {
+ // Pane::new(
+ // weak_handle.clone(),
+ // project.clone(),
+ // pane_history_timestamp.clone(),
+ // cx,
+ // )
+ // });
+ // cx.subscribe(¢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::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
+ // drag_and_drop.register_container(weak_handle.clone());
+ // });
+
+ // let mut active_call = None;
+ // if cx.has_global::<ModelHandle<ActiveCall>>() {
+ // let call = cx.global::<ModelHandle<ActiveCall>>().clone();
+ // let mut subscriptions = Vec::new();
+ // subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
+ // active_call = Some((call, subscriptions));
+ // }
+
+ // let subscriptions = vec![
+ // cx.observe_fullscreen(|_, _, cx| cx.notify()),
+ // cx.observe_window_activation(Self::on_window_activation_changed),
+ // cx.observe_window_bounds(move |_, mut bounds, display, cx| {
+ // // Transform fixed bounds to be stored in terms of the containing display
+ // if let WindowBounds::Fixed(mut window_bounds) = bounds {
+ // if let Some(screen) = cx.platform().screen_by_id(display) {
+ // let screen_bounds = screen.bounds();
+ // window_bounds
+ // .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
+ // window_bounds
+ // .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
+ // bounds = WindowBounds::Fixed(window_bounds);
+ // }
+ // }
+
+ // cx.background()
+ // .spawn(DB.set_window_bounds(workspace_id, bounds, display))
+ // .detach_and_log_err(cx);
+ // }),
+ // cx.observe(&left_dock, |this, _, cx| {
+ // this.serialize_workspace(cx);
+ // cx.notify();
+ // }),
+ // cx.observe(&bottom_dock, |this, _, cx| {
+ // this.serialize_workspace(cx);
+ // cx.notify();
+ // }),
+ // cx.observe(&right_dock, |this, _, cx| {
+ // this.serialize_workspace(cx);
+ // cx.notify();
+ // }),
+ // ];
+
+ // cx.defer(|this, cx| this.update_window_title(cx));
+ // Workspace {
+ // weak_self: weak_handle.clone(),
+ // modal: None,
+ // zoomed: None,
+ // zoomed_position: None,
+ // center: PaneGroup::new(center_pane.clone()),
+ // panes: vec![center_pane.clone()],
+ // panes_by_item: Default::default(),
+ // active_pane: center_pane.clone(),
+ // last_active_center_pane: Some(center_pane.downgrade()),
+ // last_active_view_id: None,
+ // status_bar,
+ // titlebar_item: None,
+ // notifications: Default::default(),
+ // left_dock,
+ // bottom_dock,
+ // right_dock,
+ // project: project.clone(),
+ // follower_states: Default::default(),
+ // last_leaders_by_pane: Default::default(),
+ // window_edited: false,
+ // active_call,
+ // database_id: workspace_id,
+ // app_state,
+ // _observe_current_user,
+ // _apply_leader_updates,
+ // _schedule_serialize: None,
+ // leader_updates_tx,
+ // subscriptions,
+ // pane_history_timestamp,
+ // }
+ // }
+
+ // fn new_local(
+ // abs_paths: Vec<PathBuf>,
+ // app_state: Arc<AppState>,
+ // requesting_window: Option<WindowHandle<Workspace>>,
+ // cx: &mut AppContext,
+ // ) -> Task<(
+ // WeakViewHandle<Workspace>,
+ // Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+ // )> {
+ // let project_handle = Project::local(
+ // app_state.client.clone(),
+ // app_state.node_runtime.clone(),
+ // app_state.user_store.clone(),
+ // app_state.languages.clone(),
+ // app_state.fs.clone(),
+ // cx,
+ // );
+
+ // cx.spawn(|mut cx| async move {
+ // let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+
+ // let paths_to_open = Arc::new(abs_paths);
+
+ // // Get project paths for all of the abs_paths
+ // let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
+ // let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
+ // Vec::with_capacity(paths_to_open.len());
+ // for path in paths_to_open.iter().cloned() {
+ // if let Some((worktree, project_entry)) = cx
+ // .update(|cx| {
+ // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
+ // })
+ // .await
+ // .log_err()
+ // {
+ // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
+ // project_paths.push((path, Some(project_entry)));
+ // } else {
+ // project_paths.push((path, None));
+ // }
+ // }
+
+ // let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
+ // serialized_workspace.id
+ // } else {
+ // DB.next_id().await.unwrap_or(0)
+ // };
+
+ // let window = if let Some(window) = requesting_window {
+ // window.replace_root(&mut cx, |cx| {
+ // Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+ // });
+ // window
+ // } else {
+ // {
+ // let window_bounds_override = window_bounds_env_override(&cx);
+ // let (bounds, display) = if let Some(bounds) = window_bounds_override {
+ // (Some(bounds), None)
+ // } else {
+ // serialized_workspace
+ // .as_ref()
+ // .and_then(|serialized_workspace| {
+ // let display = serialized_workspace.display?;
+ // let mut bounds = serialized_workspace.bounds?;
+
+ // // Stored bounds are relative to the containing display.
+ // // So convert back to global coordinates if that screen still exists
+ // if let WindowBounds::Fixed(mut window_bounds) = bounds {
+ // if let Some(screen) = cx.platform().screen_by_id(display) {
+ // let screen_bounds = screen.bounds();
+ // window_bounds.set_origin_x(
+ // window_bounds.origin_x() + screen_bounds.origin_x(),
+ // );
+ // window_bounds.set_origin_y(
+ // window_bounds.origin_y() + screen_bounds.origin_y(),
+ // );
+ // bounds = WindowBounds::Fixed(window_bounds);
+ // } else {
+ // // Screen no longer exists. Return none here.
+ // return None;
+ // }
+ // }
+
+ // Some((bounds, display))
+ // })
+ // .unzip()
+ // };
+
+ // // Use the serialized workspace to construct the new window
+ // cx.add_window(
+ // (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+ // |cx| {
+ // Workspace::new(
+ // workspace_id,
+ // project_handle.clone(),
+ // app_state.clone(),
+ // cx,
+ // )
+ // },
+ // )
+ // }
+ // };
+
+ // // We haven't yielded the main thread since obtaining the window handle,
+ // // so the window exists.
+ // let workspace = window.root(&cx).unwrap();
+
+ // (app_state.initialize_workspace)(
+ // workspace.downgrade(),
+ // serialized_workspace.is_some(),
+ // app_state.clone(),
+ // cx.clone(),
+ // )
+ // .await
+ // .log_err();
+
+ // window.update(&mut cx, |cx| cx.activate_window());
+
+ // let workspace = workspace.downgrade();
+ // notify_if_database_failed(&workspace, &mut cx);
+ // let opened_items = open_items(
+ // serialized_workspace,
+ // &workspace,
+ // project_paths,
+ // app_state,
+ // cx,
+ // )
+ // .await
+ // .unwrap_or_default();
+
+ // (workspace, opened_items)
+ // })
+ // }
+
+ // pub fn weak_handle(&self) -> WeakViewHandle<Self> {
+ // self.weak_self.clone()
+ // }
+
+ // pub fn left_dock(&self) -> &ViewHandle<Dock> {
+ // &self.left_dock
+ // }
+
+ // pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
+ // &self.bottom_dock
+ // }
+
+ // pub fn right_dock(&self) -> &ViewHandle<Dock> {
+ // &self.right_dock
+ // }
+
+ // pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
+ // where
+ // T::Event: std::fmt::Debug,
+ // {
+ // self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
+ // }
+
+ // pub fn add_panel_with_extra_event_handler<T: Panel, F>(
+ // &mut self,
+ // panel: ViewHandle<T>,
+ // cx: &mut ViewContext<Self>,
+ // handler: F,
+ // ) where
+ // T::Event: std::fmt::Debug,
+ // F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
+ // {
+ // let dock = match panel.position(cx) {
+ // DockPosition::Left => &self.left_dock,
+ // DockPosition::Bottom => &self.bottom_dock,
+ // DockPosition::Right => &self.right_dock,
+ // };
+
+ // self.subscriptions.push(cx.subscribe(&panel, {
+ // let mut dock = dock.clone();
+ // let mut prev_position = panel.position(cx);
+ // move |this, panel, event, cx| {
+ // if T::should_change_position_on_event(event) {
+ // let new_position = panel.read(cx).position(cx);
+ // let mut was_visible = false;
+ // dock.update(cx, |dock, cx| {
+ // prev_position = new_position;
+
+ // was_visible = dock.is_open()
+ // && dock
+ // .visible_panel()
+ // .map_or(false, |active_panel| active_panel.id() == panel.id());
+ // dock.remove_panel(&panel, cx);
+ // });
+
+ // if panel.is_zoomed(cx) {
+ // this.zoomed_position = Some(new_position);
+ // }
+
+ // dock = match panel.read(cx).position(cx) {
+ // DockPosition::Left => &this.left_dock,
+ // DockPosition::Bottom => &this.bottom_dock,
+ // DockPosition::Right => &this.right_dock,
+ // }
+ // .clone();
+ // dock.update(cx, |dock, cx| {
+ // dock.add_panel(panel.clone(), cx);
+ // if was_visible {
+ // dock.set_open(true, cx);
+ // dock.activate_panel(dock.panels_len() - 1, cx);
+ // }
+ // });
+ // } else if T::should_zoom_in_on_event(event) {
+ // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
+ // if !panel.has_focus(cx) {
+ // cx.focus(&panel);
+ // }
+ // this.zoomed = Some(panel.downgrade().into_any());
+ // this.zoomed_position = Some(panel.read(cx).position(cx));
+ // } else if T::should_zoom_out_on_event(event) {
+ // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
+ // if this.zoomed_position == Some(prev_position) {
+ // this.zoomed = None;
+ // this.zoomed_position = None;
+ // }
+ // cx.notify();
+ // } else if T::is_focus_event(event) {
+ // let position = panel.read(cx).position(cx);
+ // this.dismiss_zoomed_items_to_reveal(Some(position), cx);
+ // if panel.is_zoomed(cx) {
+ // this.zoomed = Some(panel.downgrade().into_any());
+ // this.zoomed_position = Some(position);
+ // } else {
+ // this.zoomed = None;
+ // this.zoomed_position = None;
+ // }
+ // this.update_active_view_for_followers(cx);
+ // cx.notify();
+ // } else {
+ // handler(this, &panel, event, cx)
+ // }
+ // }
+ // }));
+
+ // dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
+ // }
+
+ // pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
+ // &self.status_bar
+ // }
+
+ // pub fn app_state(&self) -> &Arc<AppState> {
+ // &self.app_state
+ // }
+
+ // pub fn user_store(&self) -> &ModelHandle<UserStore> {
+ // &self.app_state.user_store
+ // }
+
+ pub fn project(&self) -> &Handle<Project> {
+ &self.project
+ }
+
+ // pub fn recent_navigation_history(
+ // &self,
+ // limit: Option<usize>,
+ // cx: &AppContext,
+ // ) -> Vec<(ProjectPath, Option<PathBuf>)> {
+ // let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
+ // let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
+ // for pane in &self.panes {
+ // let pane = pane.read(cx);
+ // pane.nav_history()
+ // .for_each_entry(cx, |entry, (project_path, fs_path)| {
+ // if let Some(fs_path) = &fs_path {
+ // abs_paths_opened
+ // .entry(fs_path.clone())
+ // .or_default()
+ // .insert(project_path.clone());
+ // }
+ // let timestamp = entry.timestamp;
+ // match history.entry(project_path) {
+ // hash_map::Entry::Occupied(mut entry) => {
+ // let (_, old_timestamp) = entry.get();
+ // if ×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<Pane>,
+ // mode: NavigationMode,
+ // cx: &mut ViewContext<Workspace>,
+ // ) -> Task<Result<()>> {
+ // let to_load = if let Some(pane) = pane.upgrade(cx) {
+ // cx.focus(&pane);
+
+ // pane.update(cx, |pane, cx| {
+ // loop {
+ // // Retrieve the weak item handle from the history.
+ // let entry = pane.nav_history_mut().pop(mode, cx)?;
+
+ // // If the item is still present in this pane, then activate it.
+ // if let Some(index) = entry
+ // .item
+ // .upgrade(cx)
+ // .and_then(|v| pane.index_for_item(v.as_ref()))
+ // {
+ // let prev_active_item_index = pane.active_item_index();
+ // pane.nav_history_mut().set_mode(mode);
+ // pane.activate_item(index, true, true, cx);
+ // pane.nav_history_mut().set_mode(NavigationMode::Normal);
+
+ // let mut navigated = prev_active_item_index != pane.active_item_index();
+ // if let Some(data) = entry.data {
+ // navigated |= pane.active_item()?.navigate(data, cx);
+ // }
+
+ // if navigated {
+ // break None;
+ // }
+ // }
+ // // If the item is no longer present in this pane, then retrieve its
+ // // project path in order to reopen it.
+ // else {
+ // break pane
+ // .nav_history()
+ // .path_for_item(entry.item.id())
+ // .map(|(project_path, _)| (project_path, entry));
+ // }
+ // }
+ // })
+ // } else {
+ // None
+ // };
+
+ // if let Some((project_path, entry)) = to_load {
+ // // If the item was no longer present, then load it again from its previous path.
+ // let task = self.load_path(project_path, cx);
+ // cx.spawn(|workspace, mut cx| async move {
+ // let task = task.await;
+ // let mut navigated = false;
+ // if let Some((project_entry_id, build_item)) = task.log_err() {
+ // let prev_active_item_id = pane.update(&mut cx, |pane, _| {
+ // pane.nav_history_mut().set_mode(mode);
+ // pane.active_item().map(|p| p.id())
+ // })?;
+
+ // pane.update(&mut cx, |pane, cx| {
+ // let item = pane.open_item(project_entry_id, true, cx, build_item);
+ // navigated |= Some(item.id()) != prev_active_item_id;
+ // pane.nav_history_mut().set_mode(NavigationMode::Normal);
+ // if let Some(data) = entry.data {
+ // navigated |= item.navigate(data, cx);
+ // }
+ // })?;
+ // }
+
+ // if !navigated {
+ // workspace
+ // .update(&mut cx, |workspace, cx| {
+ // Self::navigate_history(workspace, pane, mode, cx)
+ // })?
+ // .await?;
+ // }
+
+ // Ok(())
+ // })
+ // } else {
+ // Task::ready(Ok(()))
+ // }
+ // }
+
+ // pub fn go_back(
+ // &mut self,
+ // pane: WeakViewHandle<Pane>,
+ // cx: &mut ViewContext<Workspace>,
+ // ) -> Task<Result<()>> {
+ // self.navigate_history(pane, NavigationMode::GoingBack, cx)
+ // }
+
+ // pub fn go_forward(
+ // &mut self,
+ // pane: WeakViewHandle<Pane>,
+ // cx: &mut ViewContext<Workspace>,
+ // ) -> Task<Result<()>> {
+ // self.navigate_history(pane, NavigationMode::GoingForward, cx)
+ // }
+
+ // pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
+ // self.navigate_history(
+ // self.active_pane().downgrade(),
+ // NavigationMode::ReopeningClosedItem,
+ // cx,
+ // )
+ // }
+
+ // pub fn client(&self) -> &Client {
+ // &self.app_state.client
+ // }
+
+ // pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
+ // self.titlebar_item = Some(item);
+ // cx.notify();
+ // }
+
+ // pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
+ // self.titlebar_item.clone()
+ // }
+
+ // /// Call the given callback with a workspace whose project is local.
+ // ///
+ // /// If the given workspace has a local project, then it will be passed
+ // /// to the callback. Otherwise, a new empty window will be created.
+ // pub fn with_local_workspace<T, F>(
+ // &mut self,
+ // cx: &mut ViewContext<Self>,
+ // callback: F,
+ // ) -> Task<Result<T>>
+ // where
+ // T: 'static,
+ // F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+ // {
+ // if self.project.read(cx).is_local() {
+ // Task::Ready(Some(Ok(callback(self, cx))))
+ // } else {
+ // let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
+ // cx.spawn(|_vh, mut cx| async move {
+ // let (workspace, _) = task.await;
+ // workspace.update(&mut cx, callback)
+ // })
+ // }
+ // }
+
+ // pub fn worktrees<'a>(
+ // &self,
+ // cx: &'a AppContext,
+ // ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+ // self.project.read(cx).worktrees(cx)
+ // }
+
+ // pub fn visible_worktrees<'a>(
+ // &self,
+ // cx: &'a AppContext,
+ // ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+ // self.project.read(cx).visible_worktrees(cx)
+ // }
+
+ // pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
+ // let futures = self
+ // .worktrees(cx)
+ // .filter_map(|worktree| worktree.read(cx).as_local())
+ // .map(|worktree| worktree.scan_complete())
+ // .collect::<Vec<_>>();
+ // async move {
+ // for future in futures {
+ // future.await;
+ // }
+ // }
+ // }
+
+ // pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
+ // cx.spawn(|mut cx| async move {
+ // let window = cx
+ // .windows()
+ // .into_iter()
+ // .find(|window| window.is_active(&cx).unwrap_or(false));
+ // if let Some(window) = window {
+ // //This can only get called when the window's project connection has been lost
+ // //so we don't need to prompt the user for anything and instead just close the window
+ // window.remove(&mut cx);
+ // }
+ // })
+ // .detach();
+ // }
+
+ // pub fn close(
+ // &mut self,
+ // _: &CloseWindow,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let window = cx.window();
+ // let prepare = self.prepare_to_close(false, cx);
+ // Some(cx.spawn(|_, mut cx| async move {
+ // if prepare.await? {
+ // window.remove(&mut cx);
+ // }
+ // Ok(())
+ // }))
+ // }
+
+ // pub fn prepare_to_close(
+ // &mut self,
+ // quitting: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<bool>> {
+ // let active_call = self.active_call().cloned();
+ // let window = cx.window();
+
+ // cx.spawn(|this, mut cx| async move {
+ // let workspace_count = cx
+ // .windows()
+ // .into_iter()
+ // .filter(|window| window.root_is::<Workspace>())
+ // .count();
+
+ // if let Some(active_call) = active_call {
+ // if !quitting
+ // && workspace_count == 1
+ // && active_call.read_with(&cx, |call, _| call.room().is_some())
+ // {
+ // let answer = window.prompt(
+ // PromptLevel::Warning,
+ // "Do you want to leave the current call?",
+ // &["Close window and hang up", "Cancel"],
+ // &mut cx,
+ // );
+
+ // if let Some(mut answer) = answer {
+ // if answer.next().await == Some(1) {
+ // return anyhow::Ok(false);
+ // } else {
+ // active_call
+ // .update(&mut cx, |call, cx| call.hang_up(cx))
+ // .await
+ // .log_err();
+ // }
+ // }
+ // }
+ // }
+
+ // Ok(this
+ // .update(&mut cx, |this, cx| {
+ // this.save_all_internal(SaveIntent::Close, cx)
+ // })?
+ // .await?)
+ // })
+ // }
+
+ // fn save_all(
+ // &mut self,
+ // action: &SaveAll,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let save_all =
+ // self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
+ // Some(cx.foreground().spawn(async move {
+ // save_all.await?;
+ // Ok(())
+ // }))
+ // }
+
+ // fn save_all_internal(
+ // &mut self,
+ // mut save_intent: SaveIntent,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<bool>> {
+ // if self.project.read(cx).is_read_only() {
+ // return Task::ready(Ok(true));
+ // }
+ // let dirty_items = self
+ // .panes
+ // .iter()
+ // .flat_map(|pane| {
+ // pane.read(cx).items().filter_map(|item| {
+ // if item.is_dirty(cx) {
+ // Some((pane.downgrade(), item.boxed_clone()))
+ // } else {
+ // None
+ // }
+ // })
+ // })
+ // .collect::<Vec<_>>();
+
+ // let project = self.project.clone();
+ // cx.spawn(|workspace, mut cx| async move {
+ // // Override save mode and display "Save all files" prompt
+ // if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+ // let mut answer = workspace.update(&mut cx, |_, cx| {
+ // let prompt = Pane::file_names_for_prompt(
+ // &mut dirty_items.iter().map(|(_, handle)| handle),
+ // dirty_items.len(),
+ // cx,
+ // );
+ // cx.prompt(
+ // PromptLevel::Warning,
+ // &prompt,
+ // &["Save all", "Discard all", "Cancel"],
+ // )
+ // })?;
+ // match answer.next().await {
+ // Some(0) => save_intent = SaveIntent::SaveAll,
+ // Some(1) => save_intent = SaveIntent::Skip,
+ // _ => {}
+ // }
+ // }
+ // for (pane, item) in dirty_items {
+ // let (singleton, project_entry_ids) =
+ // cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
+ // if singleton || !project_entry_ids.is_empty() {
+ // if let Some(ix) =
+ // pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
+ // {
+ // if !Pane::save_item(
+ // project.clone(),
+ // &pane,
+ // ix,
+ // &*item,
+ // save_intent,
+ // &mut cx,
+ // )
+ // .await?
+ // {
+ // return Ok(false);
+ // }
+ // }
+ // }
+ // }
+ // Ok(true)
+ // })
+ // }
+
+ // pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+ // let mut paths = cx.prompt_for_paths(PathPromptOptions {
+ // files: true,
+ // directories: true,
+ // multiple: true,
+ // });
+
+ // Some(cx.spawn(|this, mut cx| async move {
+ // if let Some(paths) = paths.recv().await.flatten() {
+ // if let Some(task) = this
+ // .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
+ // .log_err()
+ // {
+ // task.await?
+ // }
+ // }
+ // Ok(())
+ // }))
+ // }
+
+ // pub fn open_workspace_for_paths(
+ // &mut self,
+ // paths: Vec<PathBuf>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // let window = cx.window().downcast::<Self>();
+ // let is_remote = self.project.read(cx).is_remote();
+ // let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
+ // let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
+ // let close_task = if is_remote || has_worktree || has_dirty_items {
+ // None
+ // } else {
+ // Some(self.prepare_to_close(false, cx))
+ // };
+ // let app_state = self.app_state.clone();
+
+ // cx.spawn(|_, mut cx| async move {
+ // let window_to_replace = if let Some(close_task) = close_task {
+ // if !close_task.await? {
+ // return Ok(());
+ // }
+ // window
+ // } else {
+ // None
+ // };
+ // cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
+ // .await?;
+ // Ok(())
+ // })
+ // }
+
+ #[allow(clippy::type_complexity)]
+ pub fn open_paths(
+ &mut self,
+ mut abs_paths: Vec<PathBuf>,
+ visible: bool,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
+ log::info!("open paths {:?}", abs_paths);
+
+ let fs = self.app_state.fs.clone();
+
+ // Sort the paths to ensure we add worktrees for parents before their children.
+ abs_paths.sort_unstable();
+ cx.spawn(|this, mut cx| async move {
+ let mut tasks = Vec::with_capacity(abs_paths.len());
+ for abs_path in &abs_paths {
+ let project_path = match this
+ .update(&mut cx, |this, cx| {
+ Workspace::project_path_for_path(
+ this.project.clone(),
+ abs_path,
+ visible,
+ cx,
+ )
+ })
+ .log_err()
+ {
+ Some(project_path) => project_path.await.log_err(),
+ None => None,
+ };
+
+ let this = this.clone();
+ let task = cx.spawn(|mut cx| {
+ let fs = fs.clone();
+ let abs_path = abs_path.clone();
+ async move {
+ let (worktree, project_path) = project_path?;
+ if fs.is_file(&abs_path).await {
+ Some(
+ this.update(&mut cx, |this, cx| {
+ this.open_path(project_path, None, true, cx)
+ })
+ .log_err()?
+ .await,
+ )
+ } else {
+ this.update(&mut cx, |workspace, cx| {
+ let worktree = worktree.read(cx);
+ let worktree_abs_path = worktree.abs_path();
+ let entry_id = if abs_path == worktree_abs_path.as_ref() {
+ worktree.root_entry()
+ } else {
+ abs_path
+ .strip_prefix(worktree_abs_path.as_ref())
+ .ok()
+ .and_then(|relative_path| {
+ worktree.entry_for_path(relative_path)
+ })
+ }
+ .map(|entry| entry.id);
+ if let Some(entry_id) = entry_id {
+ workspace.project.update(cx, |_, cx| {
+ cx.emit(project2::Event::ActiveEntryChanged(Some(
+ entry_id,
+ )));
+ })
+ }
+ })
+ .log_err()?;
+ None
+ }
+ }
+ });
+ tasks.push(task);
+ }
+
+ futures::future::join_all(tasks).await
+ })
+ }
+
+ // fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
+ // let mut paths = cx.prompt_for_paths(PathPromptOptions {
+ // files: false,
+ // directories: true,
+ // multiple: true,
+ // });
+ // cx.spawn(|this, mut cx| async move {
+ // if let Some(paths) = paths.recv().await.flatten() {
+ // let results = this
+ // .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
+ // .await;
+ // for result in results.into_iter().flatten() {
+ // result.log_err();
+ // }
+ // }
+ // anyhow::Ok(())
+ // })
+ // .detach_and_log_err(cx);
+ // }
+
+ fn project_path_for_path(
+ project: Handle<Project>,
+ abs_path: &Path,
+ visible: bool,
+ cx: &mut AppContext,
+ ) -> Task<Result<(Handle<Worktree>, ProjectPath)>> {
+ let entry = project.update(cx, |project, cx| {
+ project.find_or_create_local_worktree(abs_path, visible, cx)
+ });
+ cx.spawn(|cx| async move {
+ let (worktree, path) = entry.await?;
+ let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
+ Ok((
+ worktree,
+ ProjectPath {
+ worktree_id,
+ path: path.into(),
+ },
+ ))
+ })
+ }
+
+ // /// Returns the modal that was toggled closed if it was open.
+ // pub fn toggle_modal<V, F>(
+ // &mut self,
+ // cx: &mut ViewContext<Self>,
+ // add_view: F,
+ // ) -> Option<ViewHandle<V>>
+ // where
+ // V: 'static + Modal,
+ // F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
+ // {
+ // cx.notify();
+ // // Whatever modal was visible is getting clobbered. If its the same type as V, then return
+ // // it. Otherwise, create a new modal and set it as active.
+ // if let Some(already_open_modal) = self
+ // .dismiss_modal(cx)
+ // .and_then(|modal| modal.downcast::<V>())
+ // {
+ // cx.focus_self();
+ // Some(already_open_modal)
+ // } else {
+ // let modal = add_view(self, cx);
+ // cx.subscribe(&modal, |this, _, event, cx| {
+ // if V::dismiss_on_event(event) {
+ // this.dismiss_modal(cx);
+ // }
+ // })
+ // .detach();
+ // let previously_focused_view_id = cx.focused_view_id();
+ // cx.focus(&modal);
+ // self.modal = Some(ActiveModal {
+ // view: Box::new(modal),
+ // previously_focused_view_id,
+ // });
+ // None
+ // }
+ // }
+
+ // pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
+ // self.modal
+ // .as_ref()
+ // .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
+ // }
+
+ // pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
+ // if let Some(modal) = self.modal.take() {
+ // if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
+ // if modal.view.has_focus(cx) {
+ // cx.window_context().focus(Some(previously_focused_view_id));
+ // }
+ // }
+ // cx.notify();
+ // Some(modal.view.as_any().clone())
+ // } else {
+ // None
+ // }
+ // }
+
+ // pub fn items<'a>(
+ // &'a self,
+ // cx: &'a AppContext,
+ // ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
+ // self.panes.iter().flat_map(|pane| pane.read(cx).items())
+ // }
+
+ // pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+ // self.items_of_type(cx).max_by_key(|item| item.id())
+ // }
+
+ // pub fn items_of_type<'a, T: Item>(
+ // &'a self,
+ // cx: &'a AppContext,
+ // ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
+ // self.panes
+ // .iter()
+ // .flat_map(|pane| pane.read(cx).items_of_type())
+ // }
+
+ // pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+ // self.active_pane().read(cx).active_item()
+ // }
+
+ // fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
+ // self.active_item(cx).and_then(|item| item.project_path(cx))
+ // }
+
+ // pub fn save_active_item(
+ // &mut self,
+ // save_intent: SaveIntent,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // let project = self.project.clone();
+ // let pane = self.active_pane();
+ // let item_ix = pane.read(cx).active_item_index();
+ // let item = pane.read(cx).active_item();
+ // let pane = pane.downgrade();
+
+ // cx.spawn(|_, mut cx| async move {
+ // if let Some(item) = item {
+ // Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
+ // .await
+ // .map(|_| ())
+ // } else {
+ // Ok(())
+ // }
+ // })
+ // }
+
+ // pub fn close_inactive_items_and_panes(
+ // &mut self,
+ // _: &CloseInactiveTabsAndPanes,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // self.close_all_internal(true, SaveIntent::Close, cx)
+ // }
+
+ // pub fn close_all_items_and_panes(
+ // &mut self,
+ // action: &CloseAllItemsAndPanes,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
+ // }
+
+ // fn close_all_internal(
+ // &mut self,
+ // retain_active_pane: bool,
+ // save_intent: SaveIntent,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let current_pane = self.active_pane();
+
+ // let mut tasks = Vec::new();
+
+ // if retain_active_pane {
+ // if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
+ // pane.close_inactive_items(&CloseInactiveItems, cx)
+ // }) {
+ // tasks.push(current_pane_close);
+ // };
+ // }
+
+ // for pane in self.panes() {
+ // if retain_active_pane && pane.id() == current_pane.id() {
+ // continue;
+ // }
+
+ // if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
+ // pane.close_all_items(
+ // &CloseAllItems {
+ // save_intent: Some(save_intent),
+ // },
+ // cx,
+ // )
+ // }) {
+ // tasks.push(close_pane_items)
+ // }
+ // }
+
+ // if tasks.is_empty() {
+ // None
+ // } else {
+ // Some(cx.spawn(|_, _| async move {
+ // for task in tasks {
+ // task.await?
+ // }
+ // Ok(())
+ // }))
+ // }
+ // }
+
+ // pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
+ // let dock = match dock_side {
+ // DockPosition::Left => &self.left_dock,
+ // DockPosition::Bottom => &self.bottom_dock,
+ // DockPosition::Right => &self.right_dock,
+ // };
+ // let mut focus_center = false;
+ // let mut reveal_dock = false;
+ // dock.update(cx, |dock, cx| {
+ // let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
+ // let was_visible = dock.is_open() && !other_is_zoomed;
+ // dock.set_open(!was_visible, cx);
+
+ // if let Some(active_panel) = dock.active_panel() {
+ // if was_visible {
+ // if active_panel.has_focus(cx) {
+ // focus_center = true;
+ // }
+ // } else {
+ // cx.focus(active_panel.as_any());
+ // reveal_dock = true;
+ // }
+ // }
+ // });
+
+ // if reveal_dock {
+ // self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
+ // }
+
+ // if focus_center {
+ // cx.focus_self();
+ // }
+
+ // cx.notify();
+ // self.serialize_workspace(cx);
+ // }
+
+ // pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
+ // let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
+
+ // for dock in docks {
+ // dock.update(cx, |dock, cx| {
+ // dock.set_open(false, cx);
+ // });
+ // }
+
+ // cx.focus_self();
+ // cx.notify();
+ // self.serialize_workspace(cx);
+ // }
+
+ // /// Transfer focus to the panel of the given type.
+ // pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
+ // self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
+ // .as_any()
+ // .clone()
+ // .downcast()
+ // }
+
+ // /// Focus the panel of the given type if it isn't already focused. If it is
+ // /// already focused, then transfer focus back to the workspace center.
+ // pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
+ // self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
+ // }
+
+ // /// Focus or unfocus the given panel type, depending on the given callback.
+ // fn focus_or_unfocus_panel<T: Panel>(
+ // &mut self,
+ // cx: &mut ViewContext<Self>,
+ // should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
+ // ) -> Option<Rc<dyn PanelHandle>> {
+ // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+ // if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
+ // let mut focus_center = false;
+ // let mut reveal_dock = false;
+ // let panel = dock.update(cx, |dock, cx| {
+ // dock.activate_panel(panel_index, cx);
+
+ // let panel = dock.active_panel().cloned();
+ // if let Some(panel) = panel.as_ref() {
+ // if should_focus(&**panel, cx) {
+ // dock.set_open(true, cx);
+ // cx.focus(panel.as_any());
+ // reveal_dock = true;
+ // } else {
+ // // if panel.is_zoomed(cx) {
+ // // dock.set_open(false, cx);
+ // // }
+ // focus_center = true;
+ // }
+ // }
+ // panel
+ // });
+
+ // if focus_center {
+ // cx.focus_self();
+ // }
+
+ // self.serialize_workspace(cx);
+ // cx.notify();
+ // return panel;
+ // }
+ // }
+ // None
+ // }
+
+ // pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
+ // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+ // let dock = dock.read(cx);
+ // if let Some(panel) = dock.panel::<T>() {
+ // return Some(panel);
+ // }
+ // }
+ // None
+ // }
+
+ // fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
+ // for pane in &self.panes {
+ // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+ // }
+
+ // self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+ // self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+ // self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+ // self.zoomed = None;
+ // self.zoomed_position = None;
+
+ // cx.notify();
+ // }
+
+ // #[cfg(any(test, feature = "test-support"))]
+ // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
+ // self.zoomed.and_then(|view| view.upgrade(cx))
+ // }
+
+ // fn dismiss_zoomed_items_to_reveal(
+ // &mut self,
+ // dock_to_reveal: Option<DockPosition>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // // If a center pane is zoomed, unzoom it.
+ // for pane in &self.panes {
+ // if pane != &self.active_pane || dock_to_reveal.is_some() {
+ // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+ // }
+ // }
+
+ // // If another dock is zoomed, hide it.
+ // let mut focus_center = false;
+ // for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
+ // dock.update(cx, |dock, cx| {
+ // if Some(dock.position()) != dock_to_reveal {
+ // if let Some(panel) = dock.active_panel() {
+ // if panel.is_zoomed(cx) {
+ // focus_center |= panel.has_focus(cx);
+ // dock.set_open(false, cx);
+ // }
+ // }
+ // }
+ // });
+ // }
+
+ // if focus_center {
+ // cx.focus_self();
+ // }
+
+ // if self.zoomed_position != dock_to_reveal {
+ // self.zoomed = None;
+ // self.zoomed_position = None;
+ // }
+
+ // cx.notify();
+ // }
+
+ // fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
+ // let pane = cx.add_view(|cx| {
+ // Pane::new(
+ // self.weak_handle(),
+ // self.project.clone(),
+ // self.pane_history_timestamp.clone(),
+ // cx,
+ // )
+ // });
+ // cx.subscribe(&pane, Self::handle_pane_event).detach();
+ // self.panes.push(pane.clone());
+ // cx.focus(&pane);
+ // cx.emit(Event::PaneAdded(pane.clone()));
+ // pane
+ // }
+
+ // pub fn add_item_to_center(
+ // &mut self,
+ // item: Box<dyn ItemHandle>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> bool {
+ // if let Some(center_pane) = self.last_active_center_pane.clone() {
+ // if let Some(center_pane) = center_pane.upgrade(cx) {
+ // center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
+ // true
+ // } else {
+ // false
+ // }
+ // } else {
+ // false
+ // }
+ // }
+
+ // pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+ // self.active_pane
+ // .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
+ // }
+
+ // pub fn split_item(
+ // &mut self,
+ // split_direction: SplitDirection,
+ // item: Box<dyn ItemHandle>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
+ // new_pane.update(cx, move |new_pane, cx| {
+ // new_pane.add_item(item, true, true, None, cx)
+ // })
+ // }
+
+ // pub fn open_abs_path(
+ // &mut self,
+ // abs_path: PathBuf,
+ // visible: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+ // cx.spawn(|workspace, mut cx| async move {
+ // let open_paths_task_result = workspace
+ // .update(&mut cx, |workspace, cx| {
+ // workspace.open_paths(vec![abs_path.clone()], visible, cx)
+ // })
+ // .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
+ // .await;
+ // anyhow::ensure!(
+ // open_paths_task_result.len() == 1,
+ // "open abs path {abs_path:?} task returned incorrect number of results"
+ // );
+ // match open_paths_task_result
+ // .into_iter()
+ // .next()
+ // .expect("ensured single task result")
+ // {
+ // Some(open_result) => {
+ // open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
+ // }
+ // None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
+ // }
+ // })
+ // }
+
+ // pub fn split_abs_path(
+ // &mut self,
+ // abs_path: PathBuf,
+ // visible: bool,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
+ // let project_path_task =
+ // Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
+ // cx.spawn(|this, mut cx| async move {
+ // let (_, path) = project_path_task.await?;
+ // this.update(&mut cx, |this, cx| this.split_path(path, cx))?
+ // .await
+ // })
+ // }
+
+ pub fn open_path(
+ &mut self,
+ path: impl Into<ProjectPath>,
+ pane: Option<WeakView<Pane>>,
+ focus_item: bool,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
+ let pane = pane.unwrap_or_else(|| {
+ self.last_active_center_pane.clone().unwrap_or_else(|| {
+ self.panes
+ .first()
+ .expect("There must be an active pane")
+ .downgrade()
+ })
+ });
+
+ let task = self.load_path(path.into(), cx);
+ cx.spawn(|_, mut cx| async move {
+ let (project_entry_id, build_item) = task.await?;
+ pane.update(&mut cx, |pane, cx| {
+ pane.open_item(project_entry_id, focus_item, cx, build_item)
+ })
+ })
+ }
+
+ // pub fn split_path(
+ // &mut self,
+ // path: impl Into<ProjectPath>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
+ // let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
+ // self.panes
+ // .first()
+ // .expect("There must be an active pane")
+ // .downgrade()
+ // });
+
+ // if let Member::Pane(center_pane) = &self.center.root {
+ // if center_pane.read(cx).items_len() == 0 {
+ // return self.open_path(path, Some(pane), true, cx);
+ // }
+ // }
+
+ // let task = self.load_path(path.into(), cx);
+ // cx.spawn(|this, mut cx| async move {
+ // let (project_entry_id, build_item) = task.await?;
+ // this.update(&mut cx, move |this, cx| -> Option<_> {
+ // let pane = pane.upgrade(cx)?;
+ // let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
+ // new_pane.update(cx, |new_pane, cx| {
+ // Some(new_pane.open_item(project_entry_id, true, cx, build_item))
+ // })
+ // })
+ // .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
+ // })
+ // }
+
+ pub(crate) fn load_path(
+ &mut self,
+ path: ProjectPath,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<
+ Result<(
+ ProjectEntryId,
+ impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
+ )>,
+ > {
+ let project = self.project().clone();
+ let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
+ cx.spawn(|_, mut cx| async move {
+ let (project_entry_id, project_item) = project_item.await?;
+ let build_item = cx.update(|cx| {
+ cx.default_global::<ProjectItemBuilders>()
+ .get(&project_item.model_type())
+ .ok_or_else(|| anyhow!("no item builder for project item"))
+ .cloned()
+ })?;
+ let build_item =
+ move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
+ Ok((project_entry_id, build_item))
+ })
+ }
+
+ // pub fn open_project_item<T>(
+ // &mut self,
+ // project_item: ModelHandle<T::Item>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> ViewHandle<T>
+ // where
+ // T: ProjectItem,
+ // {
+ // use project::Item as _;
+
+ // let entry_id = project_item.read(cx).entry_id(cx);
+ // if let Some(item) = entry_id
+ // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
+ // .and_then(|item| item.downcast())
+ // {
+ // self.activate_item(&item, cx);
+ // return item;
+ // }
+
+ // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+ // self.add_item(Box::new(item.clone()), cx);
+ // item
+ // }
+
+ // pub fn split_project_item<T>(
+ // &mut self,
+ // project_item: ModelHandle<T::Item>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> ViewHandle<T>
+ // where
+ // T: ProjectItem,
+ // {
+ // use project::Item as _;
+
+ // let entry_id = project_item.read(cx).entry_id(cx);
+ // if let Some(item) = entry_id
+ // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
+ // .and_then(|item| item.downcast())
+ // {
+ // self.activate_item(&item, cx);
+ // return item;
+ // }
+
+ // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
+ // self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
+ // item
+ // }
+
+ // pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+ // if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
+ // self.active_pane.update(cx, |pane, cx| {
+ // pane.add_item(Box::new(shared_screen), false, true, None, cx)
+ // });
+ // }
+ // }
+
+ // pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
+ // let result = self.panes.iter().find_map(|pane| {
+ // pane.read(cx)
+ // .index_for_item(item)
+ // .map(|ix| (pane.clone(), ix))
+ // });
+ // if let Some((pane, ix)) = result {
+ // pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
+ // true
+ // } else {
+ // false
+ // }
+ // }
+
+ // fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
+ // let panes = self.center.panes();
+ // if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
+ // cx.focus(&pane);
+ // } else {
+ // self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
+ // }
+ // }
+
+ // pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
+ // let panes = self.center.panes();
+ // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
+ // let next_ix = (ix + 1) % panes.len();
+ // let next_pane = panes[next_ix].clone();
+ // cx.focus(&next_pane);
+ // }
+ // }
+
+ // pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
+ // let panes = self.center.panes();
+ // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
+ // let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
+ // let prev_pane = panes[prev_ix].clone();
+ // cx.focus(&prev_pane);
+ // }
+ // }
+
+ // pub fn activate_pane_in_direction(
+ // &mut self,
+ // direction: SplitDirection,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // if let Some(pane) = self.find_pane_in_direction(direction, cx) {
+ // cx.focus(pane);
+ // }
+ // }
+
+ // pub fn swap_pane_in_direction(
+ // &mut self,
+ // direction: SplitDirection,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // if let Some(to) = self
+ // .find_pane_in_direction(direction, cx)
+ // .map(|pane| pane.clone())
+ // {
+ // self.center.swap(&self.active_pane.clone(), &to);
+ // cx.notify();
+ // }
+ // }
+
+ // fn find_pane_in_direction(
+ // &mut self,
+ // direction: SplitDirection,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<&ViewHandle<Pane>> {
+ // let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
+ // return None;
+ // };
+ // let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
+ // let center = match cursor {
+ // Some(cursor) if bounding_box.contains_point(cursor) => cursor,
+ // _ => bounding_box.center(),
+ // };
+
+ // let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
+
+ // let target = match direction {
+ // SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
+ // SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
+ // SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
+ // SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
+ // };
+ // self.center.pane_at_pixel_position(target)
+ // }
+
+ // fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+ // if self.active_pane != pane {
+ // self.active_pane = pane.clone();
+ // self.status_bar.update(cx, |status_bar, cx| {
+ // status_bar.set_active_pane(&self.active_pane, cx);
+ // });
+ // self.active_item_path_changed(cx);
+ // self.last_active_center_pane = Some(pane.downgrade());
+ // }
+
+ // self.dismiss_zoomed_items_to_reveal(None, cx);
+ // if pane.read(cx).is_zoomed() {
+ // self.zoomed = Some(pane.downgrade().into_any());
+ // } else {
+ // self.zoomed = None;
+ // }
+ // self.zoomed_position = None;
+ // self.update_active_view_for_followers(cx);
+
+ // cx.notify();
+ // }
+
+ // fn handle_pane_event(
+ // &mut self,
+ // pane: ViewHandle<Pane>,
+ // event: &pane::Event,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // match event {
+ // pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
+ // pane::Event::Split(direction) => {
+ // self.split_and_clone(pane, *direction, cx);
+ // }
+ // pane::Event::Remove => self.remove_pane(pane, cx),
+ // pane::Event::ActivateItem { local } => {
+ // if *local {
+ // self.unfollow(&pane, cx);
+ // }
+ // if &pane == self.active_pane() {
+ // self.active_item_path_changed(cx);
+ // }
+ // }
+ // pane::Event::ChangeItemTitle => {
+ // if pane == self.active_pane {
+ // self.active_item_path_changed(cx);
+ // }
+ // self.update_window_edited(cx);
+ // }
+ // pane::Event::RemoveItem { item_id } => {
+ // self.update_window_edited(cx);
+ // if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
+ // if entry.get().id() == pane.id() {
+ // entry.remove();
+ // }
+ // }
+ // }
+ // pane::Event::Focus => {
+ // self.handle_pane_focused(pane.clone(), cx);
+ // }
+ // pane::Event::ZoomIn => {
+ // if pane == self.active_pane {
+ // pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
+ // if pane.read(cx).has_focus() {
+ // self.zoomed = Some(pane.downgrade().into_any());
+ // self.zoomed_position = None;
+ // }
+ // cx.notify();
+ // }
+ // }
+ // pane::Event::ZoomOut => {
+ // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
+ // if self.zoomed_position.is_none() {
+ // self.zoomed = None;
+ // }
+ // cx.notify();
+ // }
+ // }
+
+ // self.serialize_workspace(cx);
+ // }
+
+ // pub fn split_pane(
+ // &mut self,
+ // pane_to_split: ViewHandle<Pane>,
+ // split_direction: SplitDirection,
+ // cx: &mut ViewContext<Self>,
+ // ) -> ViewHandle<Pane> {
+ // let new_pane = self.add_pane(cx);
+ // self.center
+ // .split(&pane_to_split, &new_pane, split_direction)
+ // .unwrap();
+ // cx.notify();
+ // new_pane
+ // }
+
+ // pub fn split_and_clone(
+ // &mut self,
+ // pane: ViewHandle<Pane>,
+ // direction: SplitDirection,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<ViewHandle<Pane>> {
+ // let item = pane.read(cx).active_item()?;
+ // let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
+ // let new_pane = self.add_pane(cx);
+ // new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
+ // self.center.split(&pane, &new_pane, direction).unwrap();
+ // Some(new_pane)
+ // } else {
+ // None
+ // };
+ // cx.notify();
+ // maybe_pane_handle
+ // }
+
+ // pub fn split_pane_with_item(
+ // &mut self,
+ // pane_to_split: WeakViewHandle<Pane>,
+ // split_direction: SplitDirection,
+ // from: WeakViewHandle<Pane>,
+ // item_id_to_move: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
+ // return;
+ // };
+ // let Some(from) = from.upgrade(cx) else {
+ // return;
+ // };
+
+ // let new_pane = self.add_pane(cx);
+ // self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
+ // self.center
+ // .split(&pane_to_split, &new_pane, split_direction)
+ // .unwrap();
+ // cx.notify();
+ // }
+
+ // pub fn split_pane_with_project_entry(
+ // &mut self,
+ // pane_to_split: WeakViewHandle<Pane>,
+ // split_direction: SplitDirection,
+ // project_entry: ProjectEntryId,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let pane_to_split = pane_to_split.upgrade(cx)?;
+ // let new_pane = self.add_pane(cx);
+ // self.center
+ // .split(&pane_to_split, &new_pane, split_direction)
+ // .unwrap();
+
+ // let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
+ // let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
+ // Some(cx.foreground().spawn(async move {
+ // task.await?;
+ // Ok(())
+ // }))
+ // }
+
+ // pub fn move_item(
+ // &mut self,
+ // source: ViewHandle<Pane>,
+ // destination: ViewHandle<Pane>,
+ // item_id_to_move: usize,
+ // destination_index: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // let item_to_move = source
+ // .read(cx)
+ // .items()
+ // .enumerate()
+ // .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
+
+ // if item_to_move.is_none() {
+ // log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
+ // return;
+ // }
+ // let (item_ix, item_handle) = item_to_move.unwrap();
+ // let item_handle = item_handle.clone();
+
+ // if source != destination {
+ // // Close item from previous pane
+ // source.update(cx, |source, cx| {
+ // source.remove_item(item_ix, false, cx);
+ // });
+ // }
+
+ // // This automatically removes duplicate items in the pane
+ // destination.update(cx, |destination, cx| {
+ // destination.add_item(item_handle, true, true, Some(destination_index), cx);
+ // cx.focus_self();
+ // });
+ // }
+
+ // fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+ // if self.center.remove(&pane).unwrap() {
+ // self.force_remove_pane(&pane, cx);
+ // self.unfollow(&pane, cx);
+ // self.last_leaders_by_pane.remove(&pane.downgrade());
+ // for removed_item in pane.read(cx).items() {
+ // self.panes_by_item.remove(&removed_item.id());
+ // }
+
+ // cx.notify();
+ // } else {
+ // self.active_item_path_changed(cx);
+ // }
+ // }
+
+ // pub fn panes(&self) -> &[ViewHandle<Pane>] {
+ // &self.panes
+ // }
+
+ // pub fn active_pane(&self) -> &ViewHandle<Pane> {
+ // &self.active_pane
+ // }
+
+ // fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+ // self.follower_states.retain(|_, state| {
+ // if state.leader_id == peer_id {
+ // for item in state.items_by_leader_view_id.values() {
+ // item.set_leader_peer_id(None, cx);
+ // }
+ // false
+ // } else {
+ // true
+ // }
+ // });
+ // cx.notify();
+ // }
+
+ // fn start_following(
+ // &mut self,
+ // leader_id: PeerId,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let pane = self.active_pane().clone();
+
+ // self.last_leaders_by_pane
+ // .insert(pane.downgrade(), leader_id);
+ // self.unfollow(&pane, cx);
+ // self.follower_states.insert(
+ // pane.clone(),
+ // FollowerState {
+ // leader_id,
+ // active_view_id: None,
+ // items_by_leader_view_id: Default::default(),
+ // },
+ // );
+ // cx.notify();
+
+ // let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+ // let project_id = self.project.read(cx).remote_id();
+ // let request = self.app_state.client.request(proto::Follow {
+ // room_id,
+ // project_id,
+ // leader_id: Some(leader_id),
+ // });
+
+ // Some(cx.spawn(|this, mut cx| async move {
+ // let response = request.await?;
+ // this.update(&mut cx, |this, _| {
+ // let state = this
+ // .follower_states
+ // .get_mut(&pane)
+ // .ok_or_else(|| anyhow!("following interrupted"))?;
+ // state.active_view_id = if let Some(active_view_id) = response.active_view_id {
+ // Some(ViewId::from_proto(active_view_id)?)
+ // } else {
+ // None
+ // };
+ // Ok::<_, anyhow::Error>(())
+ // })??;
+ // Self::add_views_from_leader(
+ // this.clone(),
+ // leader_id,
+ // vec![pane],
+ // response.views,
+ // &mut cx,
+ // )
+ // .await?;
+ // this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
+ // Ok(())
+ // }))
+ // }
+
+ // pub fn follow_next_collaborator(
+ // &mut self,
+ // _: &FollowNextCollaborator,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let collaborators = self.project.read(cx).collaborators();
+ // let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
+ // let mut collaborators = collaborators.keys().copied();
+ // for peer_id in collaborators.by_ref() {
+ // if peer_id == leader_id {
+ // break;
+ // }
+ // }
+ // collaborators.next()
+ // } else if let Some(last_leader_id) =
+ // self.last_leaders_by_pane.get(&self.active_pane.downgrade())
+ // {
+ // if collaborators.contains_key(last_leader_id) {
+ // Some(*last_leader_id)
+ // } else {
+ // None
+ // }
+ // } else {
+ // None
+ // };
+
+ // let pane = self.active_pane.clone();
+ // let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
+ // else {
+ // return None;
+ // };
+ // if Some(leader_id) == self.unfollow(&pane, cx) {
+ // return None;
+ // }
+ // self.follow(leader_id, cx)
+ // }
+
+ // pub fn follow(
+ // &mut self,
+ // leader_id: PeerId,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
+ // let project = self.project.read(cx);
+
+ // let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
+ // return None;
+ // };
+
+ // let other_project_id = match remote_participant.location {
+ // call::ParticipantLocation::External => None,
+ // call::ParticipantLocation::UnsharedProject => None,
+ // call::ParticipantLocation::SharedProject { project_id } => {
+ // if Some(project_id) == project.remote_id() {
+ // None
+ // } else {
+ // Some(project_id)
+ // }
+ // }
+ // };
+
+ // // if they are active in another project, follow there.
+ // if let Some(project_id) = other_project_id {
+ // let app_state = self.app_state.clone();
+ // return Some(crate::join_remote_project(
+ // project_id,
+ // remote_participant.user.id,
+ // app_state,
+ // cx,
+ // ));
+ // }
+
+ // // if you're already following, find the right pane and focus it.
+ // for (pane, state) in &self.follower_states {
+ // if leader_id == state.leader_id {
+ // cx.focus(pane);
+ // return None;
+ // }
+ // }
+
+ // // Otherwise, follow.
+ // self.start_following(leader_id, cx)
+ // }
+
+ // pub fn unfollow(
+ // &mut self,
+ // pane: &ViewHandle<Pane>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<PeerId> {
+ // let state = self.follower_states.remove(pane)?;
+ // let leader_id = state.leader_id;
+ // for (_, item) in state.items_by_leader_view_id {
+ // item.set_leader_peer_id(None, cx);
+ // }
+
+ // if self
+ // .follower_states
+ // .values()
+ // .all(|state| state.leader_id != state.leader_id)
+ // {
+ // let project_id = self.project.read(cx).remote_id();
+ // let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+ // self.app_state
+ // .client
+ // .send(proto::Unfollow {
+ // room_id,
+ // project_id,
+ // leader_id: Some(leader_id),
+ // })
+ // .log_err();
+ // }
+
+ // cx.notify();
+ // Some(leader_id)
+ // }
+
+ // pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
+ // self.follower_states
+ // .values()
+ // .any(|state| state.leader_id == peer_id)
+ // }
+
+ // fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ // // TODO: There should be a better system in place for this
+ // // (https://github.com/zed-industries/zed/issues/1290)
+ // let is_fullscreen = cx.window_is_fullscreen();
+ // let container_theme = if is_fullscreen {
+ // let mut container_theme = theme.titlebar.container;
+ // container_theme.padding.left = container_theme.padding.right;
+ // container_theme
+ // } else {
+ // theme.titlebar.container
+ // };
+
+ // enum TitleBar {}
+ // MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
+ // Stack::new()
+ // .with_children(
+ // self.titlebar_item
+ // .as_ref()
+ // .map(|item| ChildView::new(item, cx)),
+ // )
+ // .contained()
+ // .with_style(container_theme)
+ // })
+ // .on_click(MouseButton::Left, |event, _, cx| {
+ // if event.click_count == 2 {
+ // cx.zoom_window();
+ // }
+ // })
+ // .constrained()
+ // .with_height(theme.titlebar.height)
+ // .into_any_named("titlebar")
+ // }
+
+ // fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
+ // let active_entry = self.active_project_path(cx);
+ // self.project
+ // .update(cx, |project, cx| project.set_active_path(active_entry, cx));
+ // self.update_window_title(cx);
+ // }
+
+ // fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
+ // let project = self.project().read(cx);
+ // let mut title = String::new();
+
+ // if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
+ // let filename = path
+ // .path
+ // .file_name()
+ // .map(|s| s.to_string_lossy())
+ // .or_else(|| {
+ // Some(Cow::Borrowed(
+ // project
+ // .worktree_for_id(path.worktree_id, cx)?
+ // .read(cx)
+ // .root_name(),
+ // ))
+ // });
+
+ // if let Some(filename) = filename {
+ // title.push_str(filename.as_ref());
+ // title.push_str(" — ");
+ // }
+ // }
+
+ // for (i, name) in project.worktree_root_names(cx).enumerate() {
+ // if i > 0 {
+ // title.push_str(", ");
+ // }
+ // title.push_str(name);
+ // }
+
+ // if title.is_empty() {
+ // title = "empty project".to_string();
+ // }
+
+ // if project.is_remote() {
+ // title.push_str(" ↙");
+ // } else if project.is_shared() {
+ // title.push_str(" ↗");
+ // }
+
+ // cx.set_window_title(&title);
+ // }
+
+ // fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
+ // let is_edited = !self.project.read(cx).is_read_only()
+ // && self
+ // .items(cx)
+ // .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
+ // if is_edited != self.window_edited {
+ // self.window_edited = is_edited;
+ // cx.set_window_edited(self.window_edited)
+ // }
+ // }
+
+ // fn render_disconnected_overlay(
+ // &self,
+ // cx: &mut ViewContext<Workspace>,
+ // ) -> Option<AnyElement<Workspace>> {
+ // if self.project.read(cx).is_read_only() {
+ // enum DisconnectedOverlay {}
+ // Some(
+ // MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
+ // let theme = &theme::current(cx);
+ // Label::new(
+ // "Your connection to the remote project has been lost.",
+ // theme.workspace.disconnected_overlay.text.clone(),
+ // )
+ // .aligned()
+ // .contained()
+ // .with_style(theme.workspace.disconnected_overlay.container)
+ // })
+ // .with_cursor_style(CursorStyle::Arrow)
+ // .capture_all()
+ // .into_any_named("disconnected overlay"),
+ // )
+ // } else {
+ // None
+ // }
+ // }
+
+ // fn render_notifications(
+ // &self,
+ // theme: &theme::Workspace,
+ // cx: &AppContext,
+ // ) -> Option<AnyElement<Workspace>> {
+ // if self.notifications.is_empty() {
+ // None
+ // } else {
+ // Some(
+ // Flex::column()
+ // .with_children(self.notifications.iter().map(|(_, _, notification)| {
+ // ChildView::new(notification.as_any(), cx)
+ // .contained()
+ // .with_style(theme.notification)
+ // }))
+ // .constrained()
+ // .with_width(theme.notifications.width)
+ // .contained()
+ // .with_style(theme.notifications.container)
+ // .aligned()
+ // .bottom()
+ // .right()
+ // .into_any(),
+ // )
+ // }
+ // }
+
+ // // RPC handlers
+
+ // fn handle_follow(
+ // &mut self,
+ // follower_project_id: Option<u64>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> proto::FollowResponse {
+ // let client = &self.app_state.client;
+ // let project_id = self.project.read(cx).remote_id();
+
+ // let active_view_id = self.active_item(cx).and_then(|i| {
+ // Some(
+ // i.to_followable_item_handle(cx)?
+ // .remote_id(client, cx)?
+ // .to_proto(),
+ // )
+ // });
+
+ // cx.notify();
+
+ // self.last_active_view_id = active_view_id.clone();
+ // proto::FollowResponse {
+ // active_view_id,
+ // views: self
+ // .panes()
+ // .iter()
+ // .flat_map(|pane| {
+ // let leader_id = self.leader_for_pane(pane);
+ // pane.read(cx).items().filter_map({
+ // let cx = &cx;
+ // move |item| {
+ // let item = item.to_followable_item_handle(cx)?;
+ // if (project_id.is_none() || project_id != follower_project_id)
+ // && item.is_project_item(cx)
+ // {
+ // return None;
+ // }
+ // let id = item.remote_id(client, cx)?.to_proto();
+ // let variant = item.to_state_proto(cx)?;
+ // Some(proto::View {
+ // id: Some(id),
+ // leader_id,
+ // variant: Some(variant),
+ // })
+ // }
+ // })
+ // })
+ // .collect(),
+ // }
+ // }
+
+ // fn handle_update_followers(
+ // &mut self,
+ // leader_id: PeerId,
+ // message: proto::UpdateFollowers,
+ // _cx: &mut ViewContext<Self>,
+ // ) {
+ // self.leader_updates_tx
+ // .unbounded_send((leader_id, message))
+ // .ok();
+ // }
+
+ // async fn process_leader_update(
+ // this: &WeakViewHandle<Self>,
+ // leader_id: PeerId,
+ // update: proto::UpdateFollowers,
+ // cx: &mut AsyncAppContext,
+ // ) -> Result<()> {
+ // match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
+ // proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
+ // this.update(cx, |this, _| {
+ // for (_, state) in &mut this.follower_states {
+ // if state.leader_id == leader_id {
+ // state.active_view_id =
+ // if let Some(active_view_id) = update_active_view.id.clone() {
+ // Some(ViewId::from_proto(active_view_id)?)
+ // } else {
+ // None
+ // };
+ // }
+ // }
+ // anyhow::Ok(())
+ // })??;
+ // }
+ // proto::update_followers::Variant::UpdateView(update_view) => {
+ // let variant = update_view
+ // .variant
+ // .ok_or_else(|| anyhow!("missing update view variant"))?;
+ // let id = update_view
+ // .id
+ // .ok_or_else(|| anyhow!("missing update view id"))?;
+ // let mut tasks = Vec::new();
+ // this.update(cx, |this, cx| {
+ // let project = this.project.clone();
+ // for (_, state) in &mut this.follower_states {
+ // if state.leader_id == leader_id {
+ // let view_id = ViewId::from_proto(id.clone())?;
+ // if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
+ // tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
+ // }
+ // }
+ // }
+ // anyhow::Ok(())
+ // })??;
+ // try_join_all(tasks).await.log_err();
+ // }
+ // proto::update_followers::Variant::CreateView(view) => {
+ // let panes = this.read_with(cx, |this, _| {
+ // this.follower_states
+ // .iter()
+ // .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
+ // .cloned()
+ // .collect()
+ // })?;
+ // Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
+ // }
+ // }
+ // this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
+ // Ok(())
+ // }
+
+ // async fn add_views_from_leader(
+ // this: WeakViewHandle<Self>,
+ // leader_id: PeerId,
+ // panes: Vec<ViewHandle<Pane>>,
+ // views: Vec<proto::View>,
+ // cx: &mut AsyncAppContext,
+ // ) -> Result<()> {
+ // let this = this
+ // .upgrade(cx)
+ // .ok_or_else(|| anyhow!("workspace dropped"))?;
+
+ // let item_builders = cx.update(|cx| {
+ // cx.default_global::<FollowableItemBuilders>()
+ // .values()
+ // .map(|b| b.0)
+ // .collect::<Vec<_>>()
+ // });
+
+ // let mut item_tasks_by_pane = HashMap::default();
+ // for pane in panes {
+ // let mut item_tasks = Vec::new();
+ // let mut leader_view_ids = Vec::new();
+ // for view in &views {
+ // let Some(id) = &view.id else { continue };
+ // let id = ViewId::from_proto(id.clone())?;
+ // let mut variant = view.variant.clone();
+ // if variant.is_none() {
+ // Err(anyhow!("missing view variant"))?;
+ // }
+ // for build_item in &item_builders {
+ // let task = cx
+ // .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
+ // if let Some(task) = task {
+ // item_tasks.push(task);
+ // leader_view_ids.push(id);
+ // break;
+ // } else {
+ // assert!(variant.is_some());
+ // }
+ // }
+ // }
+
+ // item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
+ // }
+
+ // for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
+ // let items = futures::future::try_join_all(item_tasks).await?;
+ // this.update(cx, |this, cx| {
+ // let state = this.follower_states.get_mut(&pane)?;
+ // for (id, item) in leader_view_ids.into_iter().zip(items) {
+ // item.set_leader_peer_id(Some(leader_id), cx);
+ // state.items_by_leader_view_id.insert(id, item);
+ // }
+
+ // Some(())
+ // });
+ // }
+ // Ok(())
+ // }
+
+ // fn update_active_view_for_followers(&mut self, cx: &AppContext) {
+ // let mut is_project_item = true;
+ // let mut update = proto::UpdateActiveView::default();
+ // if self.active_pane.read(cx).has_focus() {
+ // let item = self
+ // .active_item(cx)
+ // .and_then(|item| item.to_followable_item_handle(cx));
+ // if let Some(item) = item {
+ // is_project_item = item.is_project_item(cx);
+ // update = proto::UpdateActiveView {
+ // id: item
+ // .remote_id(&self.app_state.client, cx)
+ // .map(|id| id.to_proto()),
+ // leader_id: self.leader_for_pane(&self.active_pane),
+ // };
+ // }
+ // }
+
+ // if update.id != self.last_active_view_id {
+ // self.last_active_view_id = update.id.clone();
+ // self.update_followers(
+ // is_project_item,
+ // proto::update_followers::Variant::UpdateActiveView(update),
+ // cx,
+ // );
+ // }
+ // }
+
+ // fn update_followers(
+ // &self,
+ // project_only: bool,
+ // update: proto::update_followers::Variant,
+ // cx: &AppContext,
+ // ) -> Option<()> {
+ // let project_id = if project_only {
+ // self.project.read(cx).remote_id()
+ // } else {
+ // None
+ // };
+ // self.app_state().workspace_store.read_with(cx, |store, cx| {
+ // store.update_followers(project_id, update, cx)
+ // })
+ // }
+
+ // pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
+ // self.follower_states.get(pane).map(|state| state.leader_id)
+ // }
+
+ // fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
+ // cx.notify();
+
+ // let call = self.active_call()?;
+ // let room = call.read(cx).room()?.read(cx);
+ // let participant = room.remote_participant_for_peer_id(leader_id)?;
+ // let mut items_to_activate = Vec::new();
+
+ // let leader_in_this_app;
+ // let leader_in_this_project;
+ // match participant.location {
+ // call::ParticipantLocation::SharedProject { project_id } => {
+ // leader_in_this_app = true;
+ // leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
+ // }
+ // call::ParticipantLocation::UnsharedProject => {
+ // leader_in_this_app = true;
+ // leader_in_this_project = false;
+ // }
+ // call::ParticipantLocation::External => {
+ // leader_in_this_app = false;
+ // leader_in_this_project = false;
+ // }
+ // };
+
+ // for (pane, state) in &self.follower_states {
+ // if state.leader_id != leader_id {
+ // continue;
+ // }
+ // if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
+ // if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
+ // if leader_in_this_project || !item.is_project_item(cx) {
+ // items_to_activate.push((pane.clone(), item.boxed_clone()));
+ // }
+ // } else {
+ // log::warn!(
+ // "unknown view id {:?} for leader {:?}",
+ // active_view_id,
+ // leader_id
+ // );
+ // }
+ // continue;
+ // }
+ // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
+ // items_to_activate.push((pane.clone(), Box::new(shared_screen)));
+ // }
+ // }
+
+ // for (pane, item) in items_to_activate {
+ // let pane_was_focused = pane.read(cx).has_focus();
+ // if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
+ // pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
+ // } else {
+ // pane.update(cx, |pane, cx| {
+ // pane.add_item(item.boxed_clone(), false, false, None, cx)
+ // });
+ // }
+
+ // if pane_was_focused {
+ // pane.update(cx, |pane, cx| pane.focus_active_item(cx));
+ // }
+ // }
+
+ // None
+ // }
+
+ // fn shared_screen_for_peer(
+ // &self,
+ // peer_id: PeerId,
+ // pane: &ViewHandle<Pane>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<ViewHandle<SharedScreen>> {
+ // let call = self.active_call()?;
+ // let room = call.read(cx).room()?.read(cx);
+ // let participant = room.remote_participant_for_peer_id(peer_id)?;
+ // let track = participant.video_tracks.values().next()?.clone();
+ // let user = participant.user.clone();
+
+ // for item in pane.read(cx).items_of_type::<SharedScreen>() {
+ // if item.read(cx).peer_id == peer_id {
+ // return Some(item);
+ // }
+ // }
+
+ // Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
+ // }
+
+ // pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
+ // if active {
+ // self.update_active_view_for_followers(cx);
+ // cx.background()
+ // .spawn(persistence::DB.update_timestamp(self.database_id()))
+ // .detach();
+ // } else {
+ // for pane in &self.panes {
+ // pane.update(cx, |pane, cx| {
+ // if let Some(item) = pane.active_item() {
+ // item.workspace_deactivated(cx);
+ // }
+ // if matches!(
+ // settings::get::<WorkspaceSettings>(cx).autosave,
+ // AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
+ // ) {
+ // for item in pane.items() {
+ // Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
+ // .detach_and_log_err(cx);
+ // }
+ // }
+ // });
+ // }
+ // }
+ // }
+
+ // fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
+ // self.active_call.as_ref().map(|(call, _)| call)
+ // }
+
+ // fn on_active_call_event(
+ // &mut self,
+ // _: ModelHandle<ActiveCall>,
+ // event: &call::room::Event,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // match event {
+ // call::room::Event::ParticipantLocationChanged { participant_id }
+ // | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
+ // self.leader_updated(*participant_id, cx);
+ // }
+ // _ => {}
+ // }
+ // }
+
+ // pub fn database_id(&self) -> WorkspaceId {
+ // self.database_id
+ // }
+
+ // fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
+ // let project = self.project().read(cx);
+
+ // if project.is_local() {
+ // Some(
+ // project
+ // .visible_worktrees(cx)
+ // .map(|worktree| worktree.read(cx).abs_path())
+ // .collect::<Vec<_>>()
+ // .into(),
+ // )
+ // } else {
+ // None
+ // }
+ // }
+
+ // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
+ // match member {
+ // Member::Axis(PaneAxis { members, .. }) => {
+ // for child in members.iter() {
+ // self.remove_panes(child.clone(), cx)
+ // }
+ // }
+ // Member::Pane(pane) => {
+ // self.force_remove_pane(&pane, cx);
+ // }
+ // }
+ // }
+
+ // fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
+ // self.panes.retain(|p| p != pane);
+ // cx.focus(self.panes.last().unwrap());
+ // if self.last_active_center_pane == Some(pane.downgrade()) {
+ // self.last_active_center_pane = None;
+ // }
+ // cx.notify();
+ // }
+
+ // fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
+ // self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
+ // cx.background().timer(Duration::from_millis(100)).await;
+ // this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
+ // .ok();
+ // }));
+ // }
+
+ // fn serialize_workspace(&self, cx: &ViewContext<Self>) {
+ // fn serialize_pane_handle(
+ // pane_handle: &ViewHandle<Pane>,
+ // cx: &AppContext,
+ // ) -> SerializedPane {
+ // let (items, active) = {
+ // let pane = pane_handle.read(cx);
+ // let active_item_id = pane.active_item().map(|item| item.id());
+ // (
+ // pane.items()
+ // .filter_map(|item_handle| {
+ // Some(SerializedItem {
+ // kind: Arc::from(item_handle.serialized_item_kind()?),
+ // item_id: item_handle.id(),
+ // active: Some(item_handle.id()) == active_item_id,
+ // })
+ // })
+ // .collect::<Vec<_>>(),
+ // pane.has_focus(),
+ // )
+ // };
+
+ // SerializedPane::new(items, active)
+ // }
+
+ // fn build_serialized_pane_group(
+ // pane_group: &Member,
+ // cx: &AppContext,
+ // ) -> SerializedPaneGroup {
+ // match pane_group {
+ // Member::Axis(PaneAxis {
+ // axis,
+ // members,
+ // flexes,
+ // bounding_boxes: _,
+ // }) => SerializedPaneGroup::Group {
+ // axis: *axis,
+ // children: members
+ // .iter()
+ // .map(|member| build_serialized_pane_group(member, cx))
+ // .collect::<Vec<_>>(),
+ // flexes: Some(flexes.borrow().clone()),
+ // },
+ // Member::Pane(pane_handle) => {
+ // SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
+ // }
+ // }
+ // }
+
+ // fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
+ // let left_dock = this.left_dock.read(cx);
+ // let left_visible = left_dock.is_open();
+ // let left_active_panel = left_dock.visible_panel().and_then(|panel| {
+ // Some(
+ // cx.view_ui_name(panel.as_any().window(), panel.id())?
+ // .to_string(),
+ // )
+ // });
+ // let left_dock_zoom = left_dock
+ // .visible_panel()
+ // .map(|panel| panel.is_zoomed(cx))
+ // .unwrap_or(false);
+
+ // let right_dock = this.right_dock.read(cx);
+ // let right_visible = right_dock.is_open();
+ // let right_active_panel = right_dock.visible_panel().and_then(|panel| {
+ // Some(
+ // cx.view_ui_name(panel.as_any().window(), panel.id())?
+ // .to_string(),
+ // )
+ // });
+ // let right_dock_zoom = right_dock
+ // .visible_panel()
+ // .map(|panel| panel.is_zoomed(cx))
+ // .unwrap_or(false);
+
+ // let bottom_dock = this.bottom_dock.read(cx);
+ // let bottom_visible = bottom_dock.is_open();
+ // let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
+ // Some(
+ // cx.view_ui_name(panel.as_any().window(), panel.id())?
+ // .to_string(),
+ // )
+ // });
+ // let bottom_dock_zoom = bottom_dock
+ // .visible_panel()
+ // .map(|panel| panel.is_zoomed(cx))
+ // .unwrap_or(false);
+
+ // DockStructure {
+ // left: DockData {
+ // visible: left_visible,
+ // active_panel: left_active_panel,
+ // zoom: left_dock_zoom,
+ // },
+ // right: DockData {
+ // visible: right_visible,
+ // active_panel: right_active_panel,
+ // zoom: right_dock_zoom,
+ // },
+ // bottom: DockData {
+ // visible: bottom_visible,
+ // active_panel: bottom_active_panel,
+ // zoom: bottom_dock_zoom,
+ // },
+ // }
+ // }
+
+ // if let Some(location) = self.location(cx) {
+ // // Load bearing special case:
+ // // - with_local_workspace() relies on this to not have other stuff open
+ // // when you open your log
+ // if !location.paths().is_empty() {
+ // let center_group = build_serialized_pane_group(&self.center.root, cx);
+ // let docks = build_serialized_docks(self, cx);
+
+ // let serialized_workspace = SerializedWorkspace {
+ // id: self.database_id,
+ // location,
+ // center_group,
+ // bounds: Default::default(),
+ // display: Default::default(),
+ // docks,
+ // };
+
+ // cx.background()
+ // .spawn(persistence::DB.save_workspace(serialized_workspace))
+ // .detach();
+ // }
+ // }
+ // }
+
+ // pub(crate) fn load_workspace(
+ // workspace: WeakViewHandle<Workspace>,
+ // serialized_workspace: SerializedWorkspace,
+ // paths_to_open: Vec<Option<ProjectPath>>,
+ // cx: &mut AppContext,
+ // ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
+ // cx.spawn(|mut cx| async move {
+ // let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
+ // (
+ // workspace.project().clone(),
+ // workspace.last_active_center_pane.clone(),
+ // )
+ // })?;
+
+ // let mut center_group = None;
+ // let mut center_items = None;
+ // // Traverse the splits tree and add to things
+ // if let Some((group, active_pane, items)) = serialized_workspace
+ // .center_group
+ // .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
+ // .await
+ // {
+ // center_items = Some(items);
+ // center_group = Some((group, active_pane))
+ // }
+
+ // let mut items_by_project_path = cx.read(|cx| {
+ // center_items
+ // .unwrap_or_default()
+ // .into_iter()
+ // .filter_map(|item| {
+ // let item = item?;
+ // let project_path = item.project_path(cx)?;
+ // Some((project_path, item))
+ // })
+ // .collect::<HashMap<_, _>>()
+ // });
+
+ // let opened_items = paths_to_open
+ // .into_iter()
+ // .map(|path_to_open| {
+ // path_to_open
+ // .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
+ // })
+ // .collect::<Vec<_>>();
+
+ // // Remove old panes from workspace panes list
+ // workspace.update(&mut cx, |workspace, cx| {
+ // if let Some((center_group, active_pane)) = center_group {
+ // workspace.remove_panes(workspace.center.root.clone(), cx);
+
+ // // Swap workspace center group
+ // workspace.center = PaneGroup::with_root(center_group);
+
+ // // Change the focus to the workspace first so that we retrigger focus in on the pane.
+ // cx.focus_self();
+
+ // if let Some(active_pane) = active_pane {
+ // cx.focus(&active_pane);
+ // } else {
+ // cx.focus(workspace.panes.last().unwrap());
+ // }
+ // } else {
+ // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
+ // if let Some(old_center_handle) = old_center_handle {
+ // cx.focus(&old_center_handle)
+ // } else {
+ // cx.focus_self()
+ // }
+ // }
+
+ // let docks = serialized_workspace.docks;
+ // workspace.left_dock.update(cx, |dock, cx| {
+ // dock.set_open(docks.left.visible, cx);
+ // if let Some(active_panel) = docks.left.active_panel {
+ // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+ // dock.activate_panel(ix, cx);
+ // }
+ // }
+ // dock.active_panel()
+ // .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
+ // if docks.left.visible && docks.left.zoom {
+ // cx.focus_self()
+ // }
+ // });
+ // // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
+ // workspace.right_dock.update(cx, |dock, cx| {
+ // dock.set_open(docks.right.visible, cx);
+ // if let Some(active_panel) = docks.right.active_panel {
+ // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+ // dock.activate_panel(ix, cx);
+ // }
+ // }
+ // dock.active_panel()
+ // .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
+
+ // if docks.right.visible && docks.right.zoom {
+ // cx.focus_self()
+ // }
+ // });
+ // workspace.bottom_dock.update(cx, |dock, cx| {
+ // dock.set_open(docks.bottom.visible, cx);
+ // if let Some(active_panel) = docks.bottom.active_panel {
+ // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
+ // dock.activate_panel(ix, cx);
+ // }
+ // }
+
+ // dock.active_panel()
+ // .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
+
+ // if docks.bottom.visible && docks.bottom.zoom {
+ // cx.focus_self()
+ // }
+ // });
+
+ // cx.notify();
+ // })?;
+
+ // // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
+ // workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
+
+ // Ok(opened_items)
+ // })
+ // }
+
+ // #[cfg(any(test, feature = "test-support"))]
+ // pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+ // use node_runtime::FakeNodeRuntime;
+
+ // let client = project.read(cx).client();
+ // let user_store = project.read(cx).user_store();
+
+ // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+ // let app_state = Arc::new(AppState {
+ // languages: project.read(cx).languages().clone(),
+ // workspace_store,
+ // client,
+ // user_store,
+ // fs: project.read(cx).fs().clone(),
+ // build_window_options: |_, _, _| Default::default(),
+ // initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+ // node_runtime: FakeNodeRuntime::new(),
+ // });
+ // Self::new(0, project, app_state, cx)
+ // }
+
+ // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
+ // let dock = match position {
+ // DockPosition::Left => &self.left_dock,
+ // DockPosition::Right => &self.right_dock,
+ // DockPosition::Bottom => &self.bottom_dock,
+ // };
+ // let active_panel = dock.read(cx).visible_panel()?;
+ // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
+ // dock.read(cx).render_placeholder(cx)
+ // } else {
+ // ChildView::new(dock, cx).into_any()
+ // };
+
+ // Some(
+ // element
+ // .constrained()
+ // .dynamically(move |constraint, _, cx| match position {
+ // DockPosition::Left | DockPosition::Right => SizeConstraint::new(
+ // Vector2F::new(20., constraint.min.y()),
+ // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
+ // ),
+ // DockPosition::Bottom => SizeConstraint::new(
+ // Vector2F::new(constraint.min.x(), 20.),
+ // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
+ // ),
+ // })
+ // .into_any(),
+ // )
+ // }
+ // }
+
+ // fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
+ // ZED_WINDOW_POSITION
+ // .zip(*ZED_WINDOW_SIZE)
+ // .map(|(position, size)| {
+ // WindowBounds::Fixed(RectF::new(
+ // cx.platform().screens()[0].bounds().origin() + position,
+ // size,
+ // ))
+ // })
+ // }
+
+ // async fn open_items(
+ // serialized_workspace: Option<SerializedWorkspace>,
+ // workspace: &WeakViewHandle<Workspace>,
+ // mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
+ // app_state: Arc<AppState>,
+ // mut cx: AsyncAppContext,
+ // ) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
+ // let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
+
+ // if let Some(serialized_workspace) = serialized_workspace {
+ // let workspace = workspace.clone();
+ // let restored_items = cx
+ // .update(|cx| {
+ // Workspace::load_workspace(
+ // workspace,
+ // serialized_workspace,
+ // project_paths_to_open
+ // .iter()
+ // .map(|(_, project_path)| project_path)
+ // .cloned()
+ // .collect(),
+ // cx,
+ // )
+ // })
+ // .await?;
+
+ // let restored_project_paths = cx.read(|cx| {
+ // restored_items
+ // .iter()
+ // .filter_map(|item| item.as_ref()?.project_path(cx))
+ // .collect::<HashSet<_>>()
+ // });
+
+ // for restored_item in restored_items {
+ // opened_items.push(restored_item.map(Ok));
+ // }
+
+ // project_paths_to_open
+ // .iter_mut()
+ // .for_each(|(_, project_path)| {
+ // if let Some(project_path_to_open) = project_path {
+ // if restored_project_paths.contains(project_path_to_open) {
+ // *project_path = None;
+ // }
+ // }
+ // });
+ // } else {
+ // for _ in 0..project_paths_to_open.len() {
+ // opened_items.push(None);
+ // }
+ // }
+ // assert!(opened_items.len() == project_paths_to_open.len());
+
+ // let tasks =
+ // project_paths_to_open
+ // .into_iter()
+ // .enumerate()
+ // .map(|(i, (abs_path, project_path))| {
+ // let workspace = workspace.clone();
+ // cx.spawn(|mut cx| {
+ // let fs = app_state.fs.clone();
+ // async move {
+ // let file_project_path = project_path?;
+ // if fs.is_file(&abs_path).await {
+ // Some((
+ // i,
+ // workspace
+ // .update(&mut cx, |workspace, cx| {
+ // workspace.open_path(file_project_path, None, true, cx)
+ // })
+ // .log_err()?
+ // .await,
+ // ))
+ // } else {
+ // None
+ // }
+ // }
+ // })
+ // });
+
+ // for maybe_opened_path in futures::future::join_all(tasks.into_iter())
+ // .await
+ // .into_iter()
+ // {
+ // if let Some((i, path_open_result)) = maybe_opened_path {
+ // opened_items[i] = Some(path_open_result);
+ // }
+ // }
+
+ // Ok(opened_items)
+ // }
+
+ // fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
+ // const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
+ // const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
+ // const MESSAGE_ID: usize = 2;
+
+ // if workspace
+ // .read_with(cx, |workspace, cx| {
+ // workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
+ // })
+ // .unwrap_or(false)
+ // {
+ // return;
+ // }
+
+ // if db::kvp::KEY_VALUE_STORE
+ // .read_kvp(NEW_DOCK_HINT_KEY)
+ // .ok()
+ // .flatten()
+ // .is_some()
+ // {
+ // if !workspace
+ // .read_with(cx, |workspace, cx| {
+ // workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
+ // })
+ // .unwrap_or(false)
+ // {
+ // cx.update(|cx| {
+ // cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
+ // let entry = tracker
+ // .entry(TypeId::of::<MessageNotification>())
+ // .or_default();
+ // if !entry.contains(&MESSAGE_ID) {
+ // entry.push(MESSAGE_ID);
+ // }
+ // });
+ // });
+ // }
+
+ // return;
+ // }
+
+ // cx.spawn(|_| async move {
+ // db::kvp::KEY_VALUE_STORE
+ // .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
+ // .await
+ // .ok();
+ // })
+ // .detach();
+
+ // workspace
+ // .update(cx, |workspace, cx| {
+ // workspace.show_notification_once(2, cx, |cx| {
+ // cx.add_view(|_| {
+ // MessageNotification::new_element(|text, _| {
+ // Text::new(
+ // "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
+ // text,
+ // )
+ // .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
+ // let code_span_background_color = settings::get::<ThemeSettings>(cx)
+ // .theme
+ // .editor
+ // .document_highlight_read_background;
+
+ // cx.scene().push_quad(gpui::Quad {
+ // bounds,
+ // background: Some(code_span_background_color),
+ // border: Default::default(),
+ // corner_radii: (2.0).into(),
+ // })
+ // })
+ // .into_any()
+ // })
+ // .with_click_message("Read more about the new panel system")
+ // .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
+ // })
+ // })
+ // })
+ // .ok();
+}
+
// fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
// const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";