diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 260ec0b6b365291975de4b4ab22aa3c3aeff7814..72b8be2f741ec733481b6b73af61c04fc389b0d9 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -13,11 +13,12 @@ use smallvec::SmallVec; pub use test_context::*; use crate::{ - current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, - ClipboardItem, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, - KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, - TextSystem, View, Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, + AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Executor, + FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, + Pixels, Platform, Point, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, + TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, + WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -249,14 +250,21 @@ impl AppContext { result } + pub fn windows(&self) -> Vec { + self.windows + .values() + .filter_map(|window| Some(window.as_ref()?.handle.clone())) + .collect() + } + pub(crate) fn read_window( &mut self, - id: WindowId, + handle: AnyWindowHandle, read: impl FnOnce(&WindowContext) -> R, ) -> Result { let window = self .windows - .get(id) + .get(handle.id) .ok_or_else(|| anyhow!("window not found"))? .as_ref() .unwrap(); @@ -265,13 +273,13 @@ impl AppContext { pub(crate) fn update_window( &mut self, - id: WindowId, + handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> Result { self.update(|cx| { let mut window = cx .windows - .get_mut(id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .take() .unwrap(); @@ -279,7 +287,7 @@ impl AppContext { let result = update(&mut WindowContext::mutable(cx, &mut window)); cx.windows - .get_mut(id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .replace(window); @@ -315,8 +323,11 @@ impl AppContext { self.apply_notify_effect(emitter); } Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event), - Effect::FocusChanged { window_id, focused } => { - self.apply_focus_changed_effect(window_id, focused); + Effect::FocusChanged { + window_handle, + focused, + } => { + self.apply_focus_changed_effect(window_handle, focused); } Effect::Refresh => { self.apply_refresh_effect(); @@ -336,18 +347,19 @@ impl AppContext { let dirty_window_ids = self .windows .iter() - .filter_map(|(window_id, window)| { + .filter_map(|(_, window)| { let window = window.as_ref().unwrap(); if window.dirty { - Some(window_id) + Some(window.handle.clone()) } else { None } }) .collect::>(); - for dirty_window_id in dirty_window_ids { - self.update_window(dirty_window_id, |cx| cx.draw()).unwrap(); + for dirty_window_handle in dirty_window_ids { + self.update_window(dirty_window_handle, |cx| cx.draw()) + .unwrap(); } } @@ -369,9 +381,8 @@ impl AppContext { } fn release_dropped_focus_handles(&mut self) { - let window_ids = self.windows.keys().collect::>(); - for window_id in window_ids { - self.update_window(window_id, |cx| { + for window_handle in self.windows() { + self.update_window(window_handle, |cx| { let mut blur_window = false; let focus = cx.window.focus; cx.window.focus_handles.write().retain(|handle_id, count| { @@ -406,8 +417,12 @@ impl AppContext { .retain(&emitter, |handler| handler(&event, self)); } - fn apply_focus_changed_effect(&mut self, window_id: WindowId, focused: Option) { - self.update_window(window_id, |cx| { + fn apply_focus_changed_effect( + &mut self, + window_handle: AnyWindowHandle, + focused: Option, + ) { + self.update_window(window_handle, |cx| { if cx.window.focus == focused { let mut listeners = mem::take(&mut cx.window.focus_listeners); let focused = @@ -752,12 +767,12 @@ impl MainThread { }) } - pub(crate) fn update_window( + pub fn update_window( &mut self, - id: WindowId, + handle: AnyWindowHandle, update: impl FnOnce(&mut MainThread) -> R, ) -> Result { - self.0.update_window(id, |cx| { + self.0.update_window(handle, |cx| { update(unsafe { std::mem::transmute::<&mut WindowContext, &mut MainThread>(cx) }) @@ -800,7 +815,7 @@ pub(crate) enum Effect { event: Box, }, FocusChanged { - window_id: WindowId, + window_handle: AnyWindowHandle, focused: Option, }, Refresh, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 42c9e96dd0a1ee6e97c32ae9c3b1dc35411ddc66..308e51908933e69707633feea685e7e3c75f5a68 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -2,7 +2,7 @@ use crate::{ AnyWindowHandle, AppContext, Context, Executor, Handle, MainThread, ModelContext, Result, Task, ViewContext, WindowContext, }; -use anyhow::anyhow; +use anyhow::Context as _; use derive_more::{Deref, DerefMut}; use parking_lot::Mutex; use std::{any::Any, future::Future, sync::Weak}; @@ -24,10 +24,7 @@ impl Context for AsyncAppContext { where T: Any + Send + Sync, { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut lock = app.lock(); // Need this to compile Ok(lock.entity(build_entity)) } @@ -37,10 +34,7 @@ impl Context for AsyncAppContext { handle: &Handle, update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, ) -> Self::Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut lock = app.lock(); // Need this to compile Ok(lock.update_entity(handle, update)) } @@ -48,10 +42,7 @@ impl Context for AsyncAppContext { impl AsyncAppContext { pub fn refresh(&mut self) -> Result<()> { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut lock = app.lock(); // Need this to compile lock.refresh(); Ok(()) @@ -62,10 +53,7 @@ impl AsyncAppContext { } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut lock = app.lock(); Ok(f(&mut *lock)) } @@ -75,12 +63,9 @@ impl AsyncAppContext { handle: AnyWindowHandle, update: impl FnOnce(&WindowContext) -> R, ) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut app_context = app.lock(); - app_context.read_window(handle.id, update) + app_context.read_window(handle, update) } pub fn update_window( @@ -88,12 +73,9 @@ impl AsyncAppContext { handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut app_context = app.lock(); - app_context.update_window(handle.id, update) + app_context.update_window(handle, update) } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task @@ -124,28 +106,19 @@ impl AsyncAppContext { where R: Send + 'static, { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut app_context = app.lock(); Ok(app_context.run_on_main(f)) } pub fn has_global(&self) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let lock = app.lock(); // Need this to compile Ok(lock.has_global::()) } pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let lock = app.lock(); // Need this to compile Ok(read(lock.global(), &lock)) } @@ -163,10 +136,7 @@ impl AsyncAppContext { &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, ) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; + let app = self.app.upgrade().context("app was released")?; let mut lock = app.lock(); // Need this to compile Ok(lock.update_global(update)) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9133c31f52e6e557da3a6caa37e36492852fb75a..8b287d9b88048d9ee8aea3b709e91da1e22af7ef 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -73,7 +73,7 @@ impl TestAppContext { read: impl FnOnce(&WindowContext) -> R, ) -> R { let mut app_context = self.app.lock(); - app_context.read_window(handle.id, read).unwrap() + app_context.read_window(handle, read).unwrap() } pub fn update_window( @@ -82,7 +82,7 @@ impl TestAppContext { update: impl FnOnce(&mut WindowContext) -> R, ) -> R { let mut app = self.app.lock(); - app.update_window(handle.id, update).unwrap() + app.update_window(handle, update).unwrap() } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index ed633e8966bfa8a54ae2ac97d1be539e4c07debe..66f1a14869462aac4d70762963983961403ef638 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,8 +1,8 @@ use parking_lot::Mutex; use crate::{ - AnyBox, AnyElement, BorrowWindow, Bounds, Element, ElementId, EntityId, Handle, IntoAnyElement, - LayoutId, Pixels, ViewContext, WindowContext, + AnyBox, AnyElement, AnyHandle, BorrowWindow, Bounds, Element, ElementId, Handle, + IntoAnyElement, LayoutId, Pixels, ViewContext, WindowContext, }; use std::{marker::PhantomData, sync::Arc}; @@ -54,7 +54,7 @@ impl Element for View { type ViewState = (); type ElementState = AnyElement; - fn id(&self) -> Option { + fn id(&self) -> Option { Some(ElementId::View(self.state.entity_id)) } @@ -109,7 +109,7 @@ impl Element for EraseViewState { type ViewState = ParentV; type ElementState = AnyBox; - fn id(&self) -> Option { + fn id(&self) -> Option { Element::id(&self.view) } @@ -143,19 +143,19 @@ impl Element for EraseViewState { } trait ViewObject: Send + Sync { - fn entity_id(&self) -> EntityId; + fn entity_handle(&self) -> &AnyHandle; fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox; fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId; fn paint(&mut self, bounds: Bounds, element: &mut AnyBox, cx: &mut WindowContext); } impl ViewObject for View { - fn entity_id(&self) -> EntityId { - self.state.entity_id + fn entity_handle(&self) -> &AnyHandle { + &self.state } fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox { - cx.with_element_id(self.entity_id(), |_global_id, cx| { + cx.with_element_id(self.state.entity_id, |_global_id, cx| { self.state.update(cx, |state, cx| { let mut any_element = Box::new((self.render)(state, cx)); any_element.initialize(state, cx); @@ -165,7 +165,7 @@ impl ViewObject for View { } fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId { - cx.with_element_id(self.entity_id(), |_global_id, cx| { + cx.with_element_id(self.state.entity_id, |_global_id, cx| { self.state.update(cx, |state, cx| { let element = element.downcast_mut::>().unwrap(); element.layout(state, cx) @@ -174,7 +174,7 @@ impl ViewObject for View { } fn paint(&mut self, _: Bounds, element: &mut AnyBox, cx: &mut WindowContext) { - cx.with_element_id(self.entity_id(), |_global_id, cx| { + cx.with_element_id(self.state.entity_id, |_global_id, cx| { self.state.update(cx, |state, cx| { let element = element.downcast_mut::>().unwrap(); element.paint(state, cx); @@ -187,6 +187,12 @@ pub struct AnyView { view: Arc>, } +impl AnyView { + pub fn entity_handle(&self) -> AnyHandle { + self.view.lock().entity_handle().clone() + } +} + impl IntoAnyElement for AnyView { fn into_any(self) -> AnyElement { AnyElement::new(EraseAnyViewState { @@ -200,8 +206,8 @@ impl Element for AnyView { type ViewState = (); type ElementState = AnyBox; - fn id(&self) -> Option { - Some(ElementId::View(self.view.lock().entity_id())) + fn id(&self) -> Option { + Some(ElementId::View(self.view.lock().entity_handle().entity_id)) } fn initialize( @@ -251,7 +257,7 @@ impl Element for EraseAnyViewState { type ViewState = ParentV; type ElementState = AnyBox; - fn id(&self) -> Option { + fn id(&self) -> Option { Element::id(&self.view) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 55cf04c51d9719ddbc033da7f6885ae08246e467..cdd83265b21db0a52898bc4022cca7257a2ed0ff 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,14 +1,14 @@ use crate::{ - px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, - Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, - Element, EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, FontId, - GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, - KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, Modifiers, MonochromeSprite, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, - PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions, - SUBPIXEL_VARIANTS, + px, size, Action, AnyBox, AnyDrag, AnyHandle, AnyView, AppContext, AsyncWindowContext, + AvailableSpace, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, + Edges, Effect, Element, EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, + FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, + KeyMatch, KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, Modifiers, + MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, + PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, + RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, + Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, + WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -145,7 +145,7 @@ impl Drop for FocusHandle { } pub struct Window { - handle: AnyWindowHandle, + pub(crate) handle: AnyWindowHandle, platform_window: MainThreadOnly>, display_id: DisplayId, sprite_atlas: Arc, @@ -305,6 +305,10 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.handle } + pub fn root_view(&self) -> Option { + Some(self.window.root_view.as_ref()?.entity_handle()) + } + pub fn notify(&mut self) { self.window.dirty = true; } @@ -324,10 +328,9 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.last_blur = Some(self.window.focus); } - let window_id = self.window.handle.id; self.window.focus = Some(handle.id); self.app.push_effect(Effect::FocusChanged { - window_id, + window_handle: self.window.handle, focused: Some(handle.id), }); self.notify(); @@ -338,10 +341,9 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.last_blur = Some(self.window.focus); } - let window_id = self.window.handle.id; self.window.focus = None; self.app.push_effect(Effect::FocusChanged { - window_id, + window_handle: self.window.handle, focused: None, }); self.notify(); @@ -359,8 +361,8 @@ impl<'a, 'w> WindowContext<'a, 'w> { mem::transmute::<&mut Self, &mut MainThread>(self) }))) } else { - let id = self.window.handle.id; - self.app.run_on_main(move |cx| cx.update_window(id, f)) + let handle = self.window.handle; + self.app.run_on_main(move |cx| cx.update_window(handle, f)) } } @@ -1076,10 +1078,10 @@ impl<'a, 'w> WindowContext<'a, 'w> { &mut self, f: impl Fn(&mut WindowContext<'_, '_>) + Send + Sync + 'static, ) -> Subscription { - let window_id = self.window.handle.id; + let window_handle = self.window.handle; self.global_observers.insert( TypeId::of::(), - Box::new(move |cx| cx.update_window(window_id, |cx| f(cx)).is_ok()), + Box::new(move |cx| cx.update_window(window_handle, |cx| f(cx)).is_ok()), ) } @@ -1162,6 +1164,16 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } +impl<'a, 'w> MainThread> { + fn platform_window(&self) -> &dyn PlatformWindow { + self.window.platform_window.borrow_on_main_thread().as_ref() + } + + pub fn activate_window(&self) { + self.platform_window().activate(); + } +} + impl Context for WindowContext<'_, '_> { type EntityContext<'a, 'w, T> = ViewContext<'a, 'w, T>; type Result = T; @@ -1457,7 +1469,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { self.app.observers.insert( handle.entity_id, Box::new(move |cx| { - cx.update_window(window_handle.id, |cx| { + cx.update_window(window_handle, |cx| { if let Some(handle) = handle.upgrade() { this.update(cx, |this, cx| on_notify(this, handle, cx)) .is_ok() @@ -1484,7 +1496,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { self.app.event_listeners.insert( handle.entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle.id, |cx| { + cx.update_window(window_handle, |cx| { if let Some(handle) = handle.upgrade() { let event = event.downcast_ref().expect("invalid event type"); this.update(cx, |this, cx| on_event(this, handle, event, cx)) @@ -1508,7 +1520,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { Box::new(move |this, cx| { let this = this.downcast_mut().expect("invalid entity type"); // todo!("are we okay with silently swallowing the error?") - let _ = cx.update_window(window_handle.id, |cx| on_release(this, cx)); + let _ = cx.update_window(window_handle, |cx| on_release(this, cx)); }), ) } @@ -1521,7 +1533,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { let this = self.handle(); let window_handle = self.window.handle; self.app.observe_release(handle, move |entity, cx| { - let _ = cx.update_window(window_handle.id, |cx| { + let _ = cx.update_window(window_handle, |cx| { this.update(cx, |this, cx| on_release(this, entity, cx)) }); }) @@ -1678,12 +1690,12 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { &mut self, f: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) + Send + Sync + 'static, ) -> Subscription { - let window_id = self.window.handle.id; + let window_handle = self.window.handle; let handle = self.handle(); self.global_observers.insert( TypeId::of::(), Box::new(move |cx| { - cx.update_window(window_id, |cx| { + cx.update_window(window_handle, |cx| { handle.update(cx, |view, cx| f(view, cx)).is_ok() }) .unwrap_or(false) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 39a1f0d51abce5541819ebf297a645db4a5b21d0..e5d778778233b6b0e473ceeaf1c4da369cc69932 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -1,1081 +1,1083 @@ -use crate::{ - pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders, - ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, -}; -use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; -use anyhow::Result; -use client2::{ - proto::{self, PeerId}, - Client, -}; -use gpui2::geometry::vector::Vector2F; -use gpui2::AnyWindowHandle; -use gpui2::{ - fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View, - ViewContext, ViewHandle, WeakViewHandle, WindowContext, -}; -use project2::{Project, ProjectEntryId, ProjectPath}; -use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; -use settings2::Setting; -use smallvec::SmallVec; -use std::{ - any::{Any, TypeId}, - borrow::Cow, - cell::RefCell, - fmt, - ops::Range, - path::PathBuf, - rc::Rc, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Duration, -}; -use theme2::Theme; - -#[derive(Deserialize)] -pub struct ItemSettings { - pub git_status: bool, - pub close_position: ClosePosition, +// use crate::{ +// pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders, +// ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, +// }; +// use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; +// use anyhow::Result; +// use client2::{ +// proto::{self, PeerId}, +// Client, +// }; +// use gpui2::geometry::vector::Vector2F; +// use gpui2::AnyWindowHandle; +// use gpui2::{ +// fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View, +// ViewContext, ViewHandle, WeakViewHandle, WindowContext, +// }; +// use project2::{Project, ProjectEntryId, ProjectPath}; +// use schemars::JsonSchema; +// use serde_derive::{Deserialize, Serialize}; +// use settings2::Setting; +// use smallvec::SmallVec; +// use std::{ +// any::{Any, TypeId}, +// borrow::Cow, +// cell::RefCell, +// fmt, +// ops::Range, +// path::PathBuf, +// rc::Rc, +// sync::{ +// atomic::{AtomicBool, Ordering}, +// Arc, +// }, +// time::Duration, +// }; +// use theme2::Theme; + +// #[derive(Deserialize)] +// pub struct ItemSettings { +// pub git_status: bool, +// pub close_position: ClosePosition, +// } + +// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +// #[serde(rename_all = "lowercase")] +// pub enum ClosePosition { +// Left, +// #[default] +// Right, +// } + +// impl ClosePosition { +// pub fn right(&self) -> bool { +// match self { +// ClosePosition::Left => false, +// ClosePosition::Right => true, +// } +// } +// } + +// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +// pub struct ItemSettingsContent { +// git_status: Option, +// close_position: Option, +// } + +// impl Setting for ItemSettings { +// const KEY: Option<&'static str> = Some("tabs"); + +// type FileContent = ItemSettingsContent; + +// fn load( +// default_value: &Self::FileContent, +// user_values: &[&Self::FileContent], +// _: &gpui2::AppContext, +// ) -> anyhow::Result { +// Self::load_via_json_merge(default_value, user_values) +// } +// } + +// #[derive(Eq, PartialEq, Hash, Debug)] +// pub enum ItemEvent { +// CloseItem, +// UpdateTab, +// UpdateBreadcrumbs, +// Edit, +// } + +// // TODO: Combine this with existing HighlightedText struct? +// pub struct BreadcrumbText { +// pub text: String, +// pub highlights: Option, HighlightStyle)>>, +// } + +// pub trait Item: View { +// fn deactivated(&mut self, _: &mut ViewContext) {} +// fn workspace_deactivated(&mut self, _: &mut ViewContext) {} +// fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { +// false +// } +// fn tab_tooltip_text(&self, _: &AppContext) -> Option> { +// None +// } +// fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { +// None +// } +// fn tab_content( +// &self, +// detail: Option, +// style: &theme2::Tab, +// cx: &AppContext, +// ) -> AnyElement; +// fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) { +// } // (model id, Item) +// fn is_singleton(&self, _cx: &AppContext) -> bool { +// false +// } +// fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} +// fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option +// where +// Self: Sized, +// { +// None +// } +// fn is_dirty(&self, _: &AppContext) -> bool { +// false +// } +// fn has_conflict(&self, _: &AppContext) -> bool { +// false +// } +// fn can_save(&self, _cx: &AppContext) -> bool { +// false +// } +// fn save( +// &mut self, +// _project: ModelHandle, +// _cx: &mut ViewContext, +// ) -> Task> { +// unimplemented!("save() must be implemented if can_save() returns true") +// } +// fn save_as( +// &mut self, +// _project: ModelHandle, +// _abs_path: PathBuf, +// _cx: &mut ViewContext, +// ) -> Task> { +// unimplemented!("save_as() must be implemented if can_save() returns true") +// } +// fn reload( +// &mut self, +// _project: ModelHandle, +// _cx: &mut ViewContext, +// ) -> Task> { +// unimplemented!("reload() must be implemented if can_save() returns true") +// } +// fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { +// SmallVec::new() +// } +// fn should_close_item_on_event(_: &Self::Event) -> bool { +// false +// } +// fn should_update_tab_on_event(_: &Self::Event) -> bool { +// false +// } + +// fn act_as_type<'a>( +// &'a self, +// type_id: TypeId, +// self_handle: &'a ViewHandle, +// _: &'a AppContext, +// ) -> Option<&AnyViewHandle> { +// if TypeId::of::() == type_id { +// Some(self_handle) +// } else { +// None +// } +// } + +// fn as_searchable(&self, _: &ViewHandle) -> Option> { +// None +// } + +// fn breadcrumb_location(&self) -> ToolbarItemLocation { +// ToolbarItemLocation::Hidden +// } + +// fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { +// None +// } + +// fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} + +// fn serialized_item_kind() -> Option<&'static str> { +// None +// } + +// fn deserialize( +// _project: ModelHandle, +// _workspace: WeakViewHandle, +// _workspace_id: WorkspaceId, +// _item_id: ItemId, +// _cx: &mut ViewContext, +// ) -> Task>> { +// unimplemented!( +// "deserialize() must be implemented if serialized_item_kind() returns Some(_)" +// ) +// } +// fn show_toolbar(&self) -> bool { +// true +// } +// fn pixel_position_of_cursor(&self, _: &AppContext) -> Option { +// None +// } +// } + +use core::fmt; + +pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { + // fn subscribe_to_item_events( + // &self, + // cx: &mut WindowContext, + // handler: Box, + // ) -> gpui2::Subscription; + // fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option>; + // fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option>; + // fn tab_content( + // &self, + // detail: Option, + // style: &theme2::Tab, + // cx: &AppContext, + // ) -> AnyElement; + // fn dragged_tab_content( + // &self, + // detail: Option, + // style: &theme2::Tab, + // cx: &AppContext, + // ) -> AnyElement; + // fn project_path(&self, cx: &AppContext) -> Option; + // fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + // fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; + // fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)); + // fn is_singleton(&self, cx: &AppContext) -> bool; + // fn boxed_clone(&self) -> Box; + // fn clone_on_split( + // &self, + // workspace_id: WorkspaceId, + // cx: &mut WindowContext, + // ) -> Option>; + // fn added_to_pane( + // &self, + // workspace: &mut Workspace, + // pane: ViewHandle, + // cx: &mut ViewContext, + // ); + // fn deactivated(&self, cx: &mut WindowContext); + // fn workspace_deactivated(&self, cx: &mut WindowContext); + // fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool; + // fn id(&self) -> usize; + // fn window(&self) -> AnyWindowHandle; + // fn as_any(&self) -> &AnyViewHandle; + // fn is_dirty(&self, cx: &AppContext) -> bool; + // fn has_conflict(&self, cx: &AppContext) -> bool; + // fn can_save(&self, cx: &AppContext) -> bool; + // fn save(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; + // fn save_as( + // &self, + // project: ModelHandle, + // abs_path: PathBuf, + // cx: &mut WindowContext, + // ) -> Task>; + // fn reload(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; + // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; + // fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; + // fn on_release( + // &self, + // cx: &mut AppContext, + // callback: Box, + // ) -> gpui2::Subscription; + // fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; + // fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; + // fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; + // fn serialized_item_kind(&self) -> Option<&'static str>; + // fn show_toolbar(&self, cx: &AppContext) -> bool; + // fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option; } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -pub enum ClosePosition { - Left, - #[default] - Right, -} - -impl ClosePosition { - pub fn right(&self) -> bool { - match self { - ClosePosition::Left => false, - ClosePosition::Right => true, - } - } -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct ItemSettingsContent { - git_status: Option, - close_position: Option, -} - -impl Setting for ItemSettings { - const KEY: Option<&'static str> = Some("tabs"); - - type FileContent = ItemSettingsContent; - - fn load( - default_value: &Self::FileContent, - user_values: &[&Self::FileContent], - _: &gpui2::AppContext, - ) -> anyhow::Result { - Self::load_via_json_merge(default_value, user_values) - } -} - -#[derive(Eq, PartialEq, Hash, Debug)] -pub enum ItemEvent { - CloseItem, - UpdateTab, - UpdateBreadcrumbs, - Edit, -} - -// TODO: Combine this with existing HighlightedText struct? -pub struct BreadcrumbText { - pub text: String, - pub highlights: Option, HighlightStyle)>>, -} - -pub trait Item: View { - fn deactivated(&mut self, _: &mut ViewContext) {} - fn workspace_deactivated(&mut self, _: &mut ViewContext) {} - fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { - false - } - fn tab_tooltip_text(&self, _: &AppContext) -> Option> { - None - } - fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { - None - } - fn tab_content( - &self, - detail: Option, - style: &theme2::Tab, - cx: &AppContext, - ) -> AnyElement; - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) { - } // (model id, Item) - fn is_singleton(&self, _cx: &AppContext) -> bool { - false - } - fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} - fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option - where - Self: Sized, - { - None - } - fn is_dirty(&self, _: &AppContext) -> bool { - false - } - fn has_conflict(&self, _: &AppContext) -> bool { - false - } - fn can_save(&self, _cx: &AppContext) -> bool { - false - } - fn save( - &mut self, - _project: ModelHandle, - _cx: &mut ViewContext, - ) -> Task> { - unimplemented!("save() must be implemented if can_save() returns true") - } - fn save_as( - &mut self, - _project: ModelHandle, - _abs_path: PathBuf, - _cx: &mut ViewContext, - ) -> Task> { - unimplemented!("save_as() must be implemented if can_save() returns true") - } - fn reload( - &mut self, - _project: ModelHandle, - _cx: &mut ViewContext, - ) -> Task> { - unimplemented!("reload() must be implemented if can_save() returns true") - } - fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { - SmallVec::new() - } - fn should_close_item_on_event(_: &Self::Event) -> bool { - false - } - fn should_update_tab_on_event(_: &Self::Event) -> bool { - false - } - - fn act_as_type<'a>( - &'a self, - type_id: TypeId, - self_handle: &'a ViewHandle, - _: &'a AppContext, - ) -> Option<&AnyViewHandle> { - if TypeId::of::() == type_id { - Some(self_handle) - } else { - None - } - } - - fn as_searchable(&self, _: &ViewHandle) -> Option> { - None - } - - fn breadcrumb_location(&self) -> ToolbarItemLocation { - ToolbarItemLocation::Hidden - } - - fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { - None - } - - fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} - - fn serialized_item_kind() -> Option<&'static str> { - None - } - - fn deserialize( - _project: ModelHandle, - _workspace: WeakViewHandle, - _workspace_id: WorkspaceId, - _item_id: ItemId, - _cx: &mut ViewContext, - ) -> Task>> { - unimplemented!( - "deserialize() must be implemented if serialized_item_kind() returns Some(_)" - ) - } - fn show_toolbar(&self) -> bool { - true - } - fn pixel_position_of_cursor(&self, _: &AppContext) -> Option { - None - } -} - -pub trait ItemHandle: 'static + fmt::Debug { - fn subscribe_to_item_events( - &self, - cx: &mut WindowContext, - handler: Box, - ) -> gpui2::Subscription; - fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option>; - fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option>; - fn tab_content( - &self, - detail: Option, - style: &theme2::Tab, - cx: &AppContext, - ) -> AnyElement; - fn dragged_tab_content( - &self, - detail: Option, - style: &theme2::Tab, - cx: &AppContext, - ) -> AnyElement; - fn project_path(&self, cx: &AppContext) -> Option; - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; - fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)); - fn is_singleton(&self, cx: &AppContext) -> bool; - fn boxed_clone(&self) -> Box; - fn clone_on_split( - &self, - workspace_id: WorkspaceId, - cx: &mut WindowContext, - ) -> Option>; - fn added_to_pane( - &self, - workspace: &mut Workspace, - pane: ViewHandle, - cx: &mut ViewContext, - ); - fn deactivated(&self, cx: &mut WindowContext); - fn workspace_deactivated(&self, cx: &mut WindowContext); - fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool; - fn id(&self) -> usize; - fn window(&self) -> AnyWindowHandle; - fn as_any(&self) -> &AnyViewHandle; - fn is_dirty(&self, cx: &AppContext) -> bool; - fn has_conflict(&self, cx: &AppContext) -> bool; - fn can_save(&self, cx: &AppContext) -> bool; - fn save(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; - fn save_as( - &self, - project: ModelHandle, - abs_path: PathBuf, - cx: &mut WindowContext, - ) -> Task>; - fn reload(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; - fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; - fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; - fn on_release( - &self, - cx: &mut AppContext, - callback: Box, - ) -> gpui2::Subscription; - fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; - fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; - fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; - fn serialized_item_kind(&self) -> Option<&'static str>; - fn show_toolbar(&self, cx: &AppContext) -> bool; - fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option; -} - -pub trait WeakItemHandle { - fn id(&self) -> usize; - fn window(&self) -> AnyWindowHandle; - fn upgrade(&self, cx: &AppContext) -> Option>; -} - -impl dyn ItemHandle { - pub fn downcast(&self) -> Option> { - self.as_any().clone().downcast() - } - - pub fn act_as(&self, cx: &AppContext) -> Option> { - self.act_as_type(TypeId::of::(), cx) - .and_then(|t| t.clone().downcast()) - } -} - -impl ItemHandle for ViewHandle { - fn subscribe_to_item_events( - &self, - cx: &mut WindowContext, - handler: Box, - ) -> gpui2::Subscription { - cx.subscribe(self, move |_, event, cx| { - for item_event in T::to_item_events(event) { - handler(item_event, cx) - } - }) - } - - fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option> { - self.read(cx).tab_tooltip_text(cx) - } - - fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option> { - self.read(cx).tab_description(detail, cx) - } - - fn tab_content( - &self, - detail: Option, - style: &theme2::Tab, - cx: &AppContext, - ) -> AnyElement { - self.read(cx).tab_content(detail, style, cx) - } - - fn dragged_tab_content( - &self, - detail: Option, - style: &theme2::Tab, - cx: &AppContext, - ) -> AnyElement { - self.read(cx).tab_content(detail, style, cx) - } - - fn project_path(&self, cx: &AppContext) -> Option { - let this = self.read(cx); - let mut result = None; - if this.is_singleton(cx) { - this.for_each_project_item(cx, &mut |_, item| { - result = item.project_path(cx); - }); - } - result - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { - let mut result = SmallVec::new(); - self.read(cx).for_each_project_item(cx, &mut |_, item| { - if let Some(id) = item.entry_id(cx) { - result.push(id); - } - }); - result - } - - fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> { - let mut result = SmallVec::new(); - self.read(cx).for_each_project_item(cx, &mut |id, _| { - result.push(id); - }); - result - } - - fn for_each_project_item( - &self, - cx: &AppContext, - f: &mut dyn FnMut(usize, &dyn project2::Item), - ) { - self.read(cx).for_each_project_item(cx, f) - } - - fn is_singleton(&self, cx: &AppContext) -> bool { - self.read(cx).is_singleton(cx) - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } - - fn clone_on_split( - &self, - workspace_id: WorkspaceId, - cx: &mut WindowContext, - ) -> Option> { - self.update(cx, |item, cx| { - cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx)) - }) - .map(|handle| Box::new(handle) as Box) - } - - fn added_to_pane( - &self, - workspace: &mut Workspace, - pane: ViewHandle, - cx: &mut ViewContext, - ) { - let history = pane.read(cx).nav_history_for_item(self); - self.update(cx, |this, cx| { - this.set_nav_history(history, cx); - this.added_to_workspace(workspace, cx); - }); - - if let Some(followed_item) = self.to_followable_item_handle(cx) { - if let Some(message) = followed_item.to_state_proto(cx) { - workspace.update_followers( - followed_item.is_project_item(cx), - proto::update_followers::Variant::CreateView(proto::View { - id: followed_item - .remote_id(&workspace.app_state.client, cx) - .map(|id| id.to_proto()), - variant: Some(message), - leader_id: workspace.leader_for_pane(&pane), - }), - cx, - ); - } - } - - if workspace - .panes_by_item - .insert(self.id(), pane.downgrade()) - .is_none() - { - let mut pending_autosave = DelayedDebouncedEditAction::new(); - let pending_update = Rc::new(RefCell::new(None)); - let pending_update_scheduled = Rc::new(AtomicBool::new(false)); - - let mut event_subscription = - Some(cx.subscribe(self, move |workspace, item, event, cx| { - let pane = if let Some(pane) = workspace - .panes_by_item - .get(&item.id()) - .and_then(|pane| pane.upgrade(cx)) - { - pane - } else { - log::error!("unexpected item event after pane was dropped"); - return; - }; - - if let Some(item) = item.to_followable_item_handle(cx) { - let is_project_item = item.is_project_item(cx); - let leader_id = workspace.leader_for_pane(&pane); - - if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { - workspace.unfollow(&pane, cx); - } - - if item.add_event_to_update_proto( - event, - &mut *pending_update.borrow_mut(), - cx, - ) && !pending_update_scheduled.load(Ordering::SeqCst) - { - pending_update_scheduled.store(true, Ordering::SeqCst); - cx.after_window_update({ - let pending_update = pending_update.clone(); - let pending_update_scheduled = pending_update_scheduled.clone(); - move |this, cx| { - pending_update_scheduled.store(false, Ordering::SeqCst); - this.update_followers( - is_project_item, - proto::update_followers::Variant::UpdateView( - proto::UpdateView { - id: item - .remote_id(&this.app_state.client, cx) - .map(|id| id.to_proto()), - variant: pending_update.borrow_mut().take(), - leader_id, - }, - ), - cx, - ); - } - }); - } - } - - for item_event in T::to_item_events(event).into_iter() { - match item_event { - ItemEvent::CloseItem => { - pane.update(cx, |pane, cx| { - pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx) - }) - .detach_and_log_err(cx); - return; - } - - ItemEvent::UpdateTab => { - pane.update(cx, |_, cx| { - cx.emit(pane::Event::ChangeItemTitle); - cx.notify(); - }); - } - - ItemEvent::Edit => { - let autosave = settings2::get::(cx).autosave; - if let AutosaveSetting::AfterDelay { milliseconds } = autosave { - let delay = Duration::from_millis(milliseconds); - let item = item.clone(); - pending_autosave.fire_new(delay, cx, move |workspace, cx| { - Pane::autosave_item(&item, workspace.project().clone(), cx) - }); - } - } - - _ => {} - } - } - })); - - cx.observe_focus(self, move |workspace, item, focused, cx| { - if !focused - && settings2::get::(cx).autosave - == AutosaveSetting::OnFocusChange - { - Pane::autosave_item(&item, workspace.project.clone(), cx) - .detach_and_log_err(cx); - } - }) - .detach(); - - let item_id = self.id(); - cx.observe_release(self, move |workspace, _, _| { - workspace.panes_by_item.remove(&item_id); - event_subscription.take(); - }) - .detach(); - } - - cx.defer(|workspace, cx| { - workspace.serialize_workspace(cx); - }); - } - - fn deactivated(&self, cx: &mut WindowContext) { - self.update(cx, |this, cx| this.deactivated(cx)); - } - - fn workspace_deactivated(&self, cx: &mut WindowContext) { - self.update(cx, |this, cx| this.workspace_deactivated(cx)); - } - - fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool { - self.update(cx, |this, cx| this.navigate(data, cx)) - } - - fn id(&self) -> usize { - self.id() - } - - fn window(&self) -> AnyWindowHandle { - AnyViewHandle::window(self) - } - - fn as_any(&self) -> &AnyViewHandle { - self - } - - fn is_dirty(&self, cx: &AppContext) -> bool { - self.read(cx).is_dirty(cx) - } - - fn has_conflict(&self, cx: &AppContext) -> bool { - self.read(cx).has_conflict(cx) - } - - fn can_save(&self, cx: &AppContext) -> bool { - self.read(cx).can_save(cx) - } - - fn save(&self, project: ModelHandle, cx: &mut WindowContext) -> Task> { - self.update(cx, |item, cx| item.save(project, cx)) - } - - fn save_as( - &self, - project: ModelHandle, - abs_path: PathBuf, - cx: &mut WindowContext, - ) -> Task> { - self.update(cx, |item, cx| item.save_as(project, abs_path, cx)) - } - - fn reload(&self, project: ModelHandle, cx: &mut WindowContext) -> Task> { - self.update(cx, |item, cx| item.reload(project, cx)) - } - - fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> { - self.read(cx).act_as_type(type_id, self, cx) - } - - fn to_followable_item_handle(&self, cx: &AppContext) -> Option> { - if cx.has_global::() { - let builders = cx.global::(); - let item = self.as_any(); - Some(builders.get(&item.view_type())?.1(item)) - } else { - None - } - } - - fn on_release( - &self, - cx: &mut AppContext, - callback: Box, - ) -> gpui2::Subscription { - cx.observe_release(self, move |_, cx| callback(cx)) - } - - fn to_searchable_item_handle(&self, cx: &AppContext) -> Option> { - self.read(cx).as_searchable(self) - } - - fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation { - self.read(cx).breadcrumb_location() - } - - fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option> { - self.read(cx).breadcrumbs(theme, cx) - } - - fn serialized_item_kind(&self) -> Option<&'static str> { - T::serialized_item_kind() - } - - fn show_toolbar(&self, cx: &AppContext) -> bool { - self.read(cx).show_toolbar() - } - - fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { - self.read(cx).pixel_position_of_cursor(cx) - } -} - -impl From> for AnyViewHandle { - fn from(val: Box) -> Self { - val.as_any().clone() - } -} - -impl From<&Box> for AnyViewHandle { - fn from(val: &Box) -> Self { - val.as_any().clone() - } -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.boxed_clone() - } -} - -impl WeakItemHandle for WeakViewHandle { - fn id(&self) -> usize { - self.id() - } - - fn window(&self) -> AnyWindowHandle { - self.window() - } - - fn upgrade(&self, cx: &AppContext) -> Option> { - self.upgrade(cx).map(|v| Box::new(v) as Box) - } -} - -pub trait ProjectItem: Item { - type Item: project2::Item + gpui2::Entity; - - fn for_project_item( - project: ModelHandle, - item: ModelHandle, - cx: &mut ViewContext, - ) -> Self; -} - -pub trait FollowableItem: Item { - fn remote_id(&self) -> Option; - fn to_state_proto(&self, cx: &AppContext) -> Option; - fn from_state_proto( - pane: ViewHandle, - project: ViewHandle, - id: ViewId, - state: &mut Option, - cx: &mut AppContext, - ) -> Option>>>; - fn add_event_to_update_proto( - &self, - event: &Self::Event, - update: &mut Option, - cx: &AppContext, - ) -> bool; - fn apply_update_proto( - &mut self, - project: &ModelHandle, - message: proto::update_view::Variant, - cx: &mut ViewContext, - ) -> Task>; - fn is_project_item(&self, cx: &AppContext) -> bool; - - fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); - fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; -} - -pub trait FollowableItemHandle: ItemHandle { - fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; - fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); - fn to_state_proto(&self, cx: &AppContext) -> Option; - fn add_event_to_update_proto( - &self, - event: &dyn Any, - update: &mut Option, - cx: &AppContext, - ) -> bool; - fn apply_update_proto( - &self, - project: &ModelHandle, - message: proto::update_view::Variant, - cx: &mut WindowContext, - ) -> Task>; - fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; - fn is_project_item(&self, cx: &AppContext) -> bool; -} - -impl FollowableItemHandle for ViewHandle { - fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { - self.read(cx).remote_id().or_else(|| { - client.peer_id().map(|creator| ViewId { - creator, - id: self.id() as u64, - }) - }) - } - - fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext) { - self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx)) - } - - fn to_state_proto(&self, cx: &AppContext) -> Option { - self.read(cx).to_state_proto(cx) - } - - fn add_event_to_update_proto( - &self, - event: &dyn Any, - update: &mut Option, - cx: &AppContext, - ) -> bool { - if let Some(event) = event.downcast_ref() { - self.read(cx).add_event_to_update_proto(event, update, cx) - } else { - false - } - } - - fn apply_update_proto( - &self, - project: &ModelHandle, - message: proto::update_view::Variant, - cx: &mut WindowContext, - ) -> Task> { - self.update(cx, |this, cx| this.apply_update_proto(project, message, cx)) - } - - fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool { - if let Some(event) = event.downcast_ref() { - T::should_unfollow_on_event(event, cx) - } else { - false - } - } - - fn is_project_item(&self, cx: &AppContext) -> bool { - self.read(cx).is_project_item(cx) - } -} - -#[cfg(any(test, feature = "test-support"))] -pub mod test { - use super::{Item, ItemEvent}; - use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; - use gpui2::{ - elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, - ViewContext, ViewHandle, WeakViewHandle, - }; - use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId}; - use smallvec::SmallVec; - use std::{any::Any, borrow::Cow, cell::Cell, path::Path}; - - pub struct TestProjectItem { - pub entry_id: Option, - pub project_path: Option, - } - - pub struct TestItem { - pub workspace_id: WorkspaceId, - pub state: String, - pub label: String, - pub save_count: usize, - pub save_as_count: usize, - pub reload_count: usize, - pub is_dirty: bool, - pub is_singleton: bool, - pub has_conflict: bool, - pub project_items: Vec>, - pub nav_history: Option, - pub tab_descriptions: Option>, - pub tab_detail: Cell>, - } - - impl Entity for TestProjectItem { - type Event = (); - } - - impl project2::Item for TestProjectItem { - fn entry_id(&self, _: &AppContext) -> Option { - self.entry_id - } - - fn project_path(&self, _: &AppContext) -> Option { - self.project_path.clone() - } - } - - pub enum TestItemEvent { - Edit, - } - - impl Clone for TestItem { - fn clone(&self) -> Self { - Self { - state: self.state.clone(), - label: self.label.clone(), - save_count: self.save_count, - save_as_count: self.save_as_count, - reload_count: self.reload_count, - is_dirty: self.is_dirty, - is_singleton: self.is_singleton, - has_conflict: self.has_conflict, - project_items: self.project_items.clone(), - nav_history: None, - tab_descriptions: None, - tab_detail: Default::default(), - workspace_id: self.workspace_id, - } - } - } - - impl TestProjectItem { - pub fn new(id: u64, path: &str, cx: &mut AppContext) -> ModelHandle { - let entry_id = Some(ProjectEntryId::from_proto(id)); - let project_path = Some(ProjectPath { - worktree_id: WorktreeId::from_usize(0), - path: Path::new(path).into(), - }); - cx.add_model(|_| Self { - entry_id, - project_path, - }) - } - - pub fn new_untitled(cx: &mut AppContext) -> ModelHandle { - cx.add_model(|_| Self { - project_path: None, - entry_id: None, - }) - } - } - - impl TestItem { - pub fn new() -> Self { - Self { - state: String::new(), - label: String::new(), - save_count: 0, - save_as_count: 0, - reload_count: 0, - is_dirty: false, - has_conflict: false, - project_items: Vec::new(), - is_singleton: true, - nav_history: None, - tab_descriptions: None, - tab_detail: Default::default(), - workspace_id: 0, - } - } - - pub fn new_deserialized(id: WorkspaceId) -> Self { - let mut this = Self::new(); - this.workspace_id = id; - this - } - - pub fn with_label(mut self, state: &str) -> Self { - self.label = state.to_string(); - self - } - - pub fn with_singleton(mut self, singleton: bool) -> Self { - self.is_singleton = singleton; - self - } - - pub fn with_dirty(mut self, dirty: bool) -> Self { - self.is_dirty = dirty; - self - } - - pub fn with_conflict(mut self, has_conflict: bool) -> Self { - self.has_conflict = has_conflict; - self - } - - pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { - self.project_items.clear(); - self.project_items.extend(items.iter().cloned()); - self - } - - pub fn set_state(&mut self, state: String, cx: &mut ViewContext) { - self.push_to_nav_history(cx); - self.state = state; - } - - fn push_to_nav_history(&mut self, cx: &mut ViewContext) { - if let Some(history) = &mut self.nav_history { - history.push(Some(Box::new(self.state.clone())), cx); - } - } - } - - impl Entity for TestItem { - type Event = TestItemEvent; - } - - impl View for TestItem { - fn ui_name() -> &'static str { - "TestItem" - } - - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() - } - } - - impl Item for TestItem { - fn tab_description(&self, detail: usize, _: &AppContext) -> Option> { - self.tab_descriptions.as_ref().and_then(|descriptions| { - let description = *descriptions.get(detail).or_else(|| descriptions.last())?; - Some(description.into()) - }) - } - - fn tab_content( - &self, - detail: Option, - _: &theme2::Tab, - _: &AppContext, - ) -> AnyElement { - self.tab_detail.set(detail); - Empty::new().into_any() - } - - fn for_each_project_item( - &self, - cx: &AppContext, - f: &mut dyn FnMut(usize, &dyn project2::Item), - ) { - self.project_items - .iter() - .for_each(|item| f(item.id(), item.read(cx))) - } - - fn is_singleton(&self, _: &AppContext) -> bool { - self.is_singleton - } - - fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { - self.nav_history = Some(history); - } - - fn navigate(&mut self, state: Box, _: &mut ViewContext) -> bool { - let state = *state.downcast::().unwrap_or_default(); - if state != self.state { - self.state = state; - true - } else { - false - } - } - - fn deactivated(&mut self, cx: &mut ViewContext) { - self.push_to_nav_history(cx); - } - - fn clone_on_split( - &self, - _workspace_id: WorkspaceId, - _: &mut ViewContext, - ) -> Option - where - Self: Sized, - { - Some(self.clone()) - } - - fn is_dirty(&self, _: &AppContext) -> bool { - self.is_dirty - } - - fn has_conflict(&self, _: &AppContext) -> bool { - self.has_conflict - } - - fn can_save(&self, cx: &AppContext) -> bool { - !self.project_items.is_empty() - && self - .project_items - .iter() - .all(|item| item.read(cx).entry_id.is_some()) - } - - fn save( - &mut self, - _: ModelHandle, - _: &mut ViewContext, - ) -> Task> { - self.save_count += 1; - self.is_dirty = false; - Task::ready(Ok(())) - } - - fn save_as( - &mut self, - _: ModelHandle, - _: std::path::PathBuf, - _: &mut ViewContext, - ) -> Task> { - self.save_as_count += 1; - self.is_dirty = false; - Task::ready(Ok(())) - } - - fn reload( - &mut self, - _: ModelHandle, - _: &mut ViewContext, - ) -> Task> { - self.reload_count += 1; - self.is_dirty = false; - Task::ready(Ok(())) - } - - fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> { - [ItemEvent::UpdateTab, ItemEvent::Edit].into() - } - - fn serialized_item_kind() -> Option<&'static str> { - Some("TestItem") - } - - fn deserialize( - _project: ModelHandle, - _workspace: WeakViewHandle, - workspace_id: WorkspaceId, - _item_id: ItemId, - cx: &mut ViewContext, - ) -> Task>> { - let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id)); - Task::Ready(Some(anyhow::Ok(view))) - } - } -} +// pub trait WeakItemHandle { +// fn id(&self) -> usize; +// fn window(&self) -> AnyWindowHandle; +// fn upgrade(&self, cx: &AppContext) -> Option>; +// } + +// impl dyn ItemHandle { +// pub fn downcast(&self) -> Option> { +// self.as_any().clone().downcast() +// } + +// pub fn act_as(&self, cx: &AppContext) -> Option> { +// self.act_as_type(TypeId::of::(), cx) +// .and_then(|t| t.clone().downcast()) +// } +// } + +// impl ItemHandle for ViewHandle { +// fn subscribe_to_item_events( +// &self, +// cx: &mut WindowContext, +// handler: Box, +// ) -> gpui2::Subscription { +// cx.subscribe(self, move |_, event, cx| { +// for item_event in T::to_item_events(event) { +// handler(item_event, cx) +// } +// }) +// } + +// fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option> { +// self.read(cx).tab_tooltip_text(cx) +// } + +// fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option> { +// self.read(cx).tab_description(detail, cx) +// } + +// fn tab_content( +// &self, +// detail: Option, +// style: &theme2::Tab, +// cx: &AppContext, +// ) -> AnyElement { +// self.read(cx).tab_content(detail, style, cx) +// } + +// fn dragged_tab_content( +// &self, +// detail: Option, +// style: &theme2::Tab, +// cx: &AppContext, +// ) -> AnyElement { +// self.read(cx).tab_content(detail, style, cx) +// } + +// fn project_path(&self, cx: &AppContext) -> Option { +// let this = self.read(cx); +// let mut result = None; +// if this.is_singleton(cx) { +// this.for_each_project_item(cx, &mut |_, item| { +// result = item.project_path(cx); +// }); +// } +// result +// } + +// fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> { +// let mut result = SmallVec::new(); +// self.read(cx).for_each_project_item(cx, &mut |_, item| { +// if let Some(id) = item.entry_id(cx) { +// result.push(id); +// } +// }); +// result +// } + +// fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> { +// let mut result = SmallVec::new(); +// self.read(cx).for_each_project_item(cx, &mut |id, _| { +// result.push(id); +// }); +// result +// } + +// fn for_each_project_item( +// &self, +// cx: &AppContext, +// f: &mut dyn FnMut(usize, &dyn project2::Item), +// ) { +// self.read(cx).for_each_project_item(cx, f) +// } + +// fn is_singleton(&self, cx: &AppContext) -> bool { +// self.read(cx).is_singleton(cx) +// } + +// fn boxed_clone(&self) -> Box { +// Box::new(self.clone()) +// } + +// fn clone_on_split( +// &self, +// workspace_id: WorkspaceId, +// cx: &mut WindowContext, +// ) -> Option> { +// self.update(cx, |item, cx| { +// cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx)) +// }) +// .map(|handle| Box::new(handle) as Box) +// } + +// fn added_to_pane( +// &self, +// workspace: &mut Workspace, +// pane: ViewHandle, +// cx: &mut ViewContext, +// ) { +// let history = pane.read(cx).nav_history_for_item(self); +// self.update(cx, |this, cx| { +// this.set_nav_history(history, cx); +// this.added_to_workspace(workspace, cx); +// }); + +// if let Some(followed_item) = self.to_followable_item_handle(cx) { +// if let Some(message) = followed_item.to_state_proto(cx) { +// workspace.update_followers( +// followed_item.is_project_item(cx), +// proto::update_followers::Variant::CreateView(proto::View { +// id: followed_item +// .remote_id(&workspace.app_state.client, cx) +// .map(|id| id.to_proto()), +// variant: Some(message), +// leader_id: workspace.leader_for_pane(&pane), +// }), +// cx, +// ); +// } +// } + +// if workspace +// .panes_by_item +// .insert(self.id(), pane.downgrade()) +// .is_none() +// { +// let mut pending_autosave = DelayedDebouncedEditAction::new(); +// let pending_update = Rc::new(RefCell::new(None)); +// let pending_update_scheduled = Rc::new(AtomicBool::new(false)); + +// let mut event_subscription = +// Some(cx.subscribe(self, move |workspace, item, event, cx| { +// let pane = if let Some(pane) = workspace +// .panes_by_item +// .get(&item.id()) +// .and_then(|pane| pane.upgrade(cx)) +// { +// pane +// } else { +// log::error!("unexpected item event after pane was dropped"); +// return; +// }; + +// if let Some(item) = item.to_followable_item_handle(cx) { +// let is_project_item = item.is_project_item(cx); +// let leader_id = workspace.leader_for_pane(&pane); + +// if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { +// workspace.unfollow(&pane, cx); +// } + +// if item.add_event_to_update_proto( +// event, +// &mut *pending_update.borrow_mut(), +// cx, +// ) && !pending_update_scheduled.load(Ordering::SeqCst) +// { +// pending_update_scheduled.store(true, Ordering::SeqCst); +// cx.after_window_update({ +// let pending_update = pending_update.clone(); +// let pending_update_scheduled = pending_update_scheduled.clone(); +// move |this, cx| { +// pending_update_scheduled.store(false, Ordering::SeqCst); +// this.update_followers( +// is_project_item, +// proto::update_followers::Variant::UpdateView( +// proto::UpdateView { +// id: item +// .remote_id(&this.app_state.client, cx) +// .map(|id| id.to_proto()), +// variant: pending_update.borrow_mut().take(), +// leader_id, +// }, +// ), +// cx, +// ); +// } +// }); +// } +// } + +// for item_event in T::to_item_events(event).into_iter() { +// match item_event { +// ItemEvent::CloseItem => { +// pane.update(cx, |pane, cx| { +// pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx) +// }) +// .detach_and_log_err(cx); +// return; +// } + +// ItemEvent::UpdateTab => { +// pane.update(cx, |_, cx| { +// cx.emit(pane::Event::ChangeItemTitle); +// cx.notify(); +// }); +// } + +// ItemEvent::Edit => { +// let autosave = settings2::get::(cx).autosave; +// if let AutosaveSetting::AfterDelay { milliseconds } = autosave { +// let delay = Duration::from_millis(milliseconds); +// let item = item.clone(); +// pending_autosave.fire_new(delay, cx, move |workspace, cx| { +// Pane::autosave_item(&item, workspace.project().clone(), cx) +// }); +// } +// } + +// _ => {} +// } +// } +// })); + +// cx.observe_focus(self, move |workspace, item, focused, cx| { +// if !focused +// && settings2::get::(cx).autosave +// == AutosaveSetting::OnFocusChange +// { +// Pane::autosave_item(&item, workspace.project.clone(), cx) +// .detach_and_log_err(cx); +// } +// }) +// .detach(); + +// let item_id = self.id(); +// cx.observe_release(self, move |workspace, _, _| { +// workspace.panes_by_item.remove(&item_id); +// event_subscription.take(); +// }) +// .detach(); +// } + +// cx.defer(|workspace, cx| { +// workspace.serialize_workspace(cx); +// }); +// } + +// fn deactivated(&self, cx: &mut WindowContext) { +// self.update(cx, |this, cx| this.deactivated(cx)); +// } + +// fn workspace_deactivated(&self, cx: &mut WindowContext) { +// self.update(cx, |this, cx| this.workspace_deactivated(cx)); +// } + +// fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool { +// self.update(cx, |this, cx| this.navigate(data, cx)) +// } + +// fn id(&self) -> usize { +// self.id() +// } + +// fn window(&self) -> AnyWindowHandle { +// AnyViewHandle::window(self) +// } + +// fn as_any(&self) -> &AnyViewHandle { +// self +// } + +// fn is_dirty(&self, cx: &AppContext) -> bool { +// self.read(cx).is_dirty(cx) +// } + +// fn has_conflict(&self, cx: &AppContext) -> bool { +// self.read(cx).has_conflict(cx) +// } + +// fn can_save(&self, cx: &AppContext) -> bool { +// self.read(cx).can_save(cx) +// } + +// fn save(&self, project: ModelHandle, cx: &mut WindowContext) -> Task> { +// self.update(cx, |item, cx| item.save(project, cx)) +// } + +// fn save_as( +// &self, +// project: ModelHandle, +// abs_path: PathBuf, +// cx: &mut WindowContext, +// ) -> Task> { +// self.update(cx, |item, cx| item.save_as(project, abs_path, cx)) +// } + +// fn reload(&self, project: ModelHandle, cx: &mut WindowContext) -> Task> { +// self.update(cx, |item, cx| item.reload(project, cx)) +// } + +// fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> { +// self.read(cx).act_as_type(type_id, self, cx) +// } + +// fn to_followable_item_handle(&self, cx: &AppContext) -> Option> { +// if cx.has_global::() { +// let builders = cx.global::(); +// let item = self.as_any(); +// Some(builders.get(&item.view_type())?.1(item)) +// } else { +// None +// } +// } + +// fn on_release( +// &self, +// cx: &mut AppContext, +// callback: Box, +// ) -> gpui2::Subscription { +// cx.observe_release(self, move |_, cx| callback(cx)) +// } + +// fn to_searchable_item_handle(&self, cx: &AppContext) -> Option> { +// self.read(cx).as_searchable(self) +// } + +// fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation { +// self.read(cx).breadcrumb_location() +// } + +// fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option> { +// self.read(cx).breadcrumbs(theme, cx) +// } + +// fn serialized_item_kind(&self) -> Option<&'static str> { +// T::serialized_item_kind() +// } + +// fn show_toolbar(&self, cx: &AppContext) -> bool { +// self.read(cx).show_toolbar() +// } + +// fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { +// self.read(cx).pixel_position_of_cursor(cx) +// } +// } + +// impl From> for AnyViewHandle { +// fn from(val: Box) -> Self { +// val.as_any().clone() +// } +// } + +// impl From<&Box> for AnyViewHandle { +// fn from(val: &Box) -> Self { +// val.as_any().clone() +// } +// } + +// impl Clone for Box { +// fn clone(&self) -> Box { +// self.boxed_clone() +// } +// } + +// impl WeakItemHandle for WeakViewHandle { +// fn id(&self) -> usize { +// self.id() +// } + +// fn window(&self) -> AnyWindowHandle { +// self.window() +// } + +// fn upgrade(&self, cx: &AppContext) -> Option> { +// self.upgrade(cx).map(|v| Box::new(v) as Box) +// } +// } + +// pub trait ProjectItem: Item { +// type Item: project2::Item + gpui2::Entity; + +// fn for_project_item( +// project: ModelHandle, +// item: ModelHandle, +// cx: &mut ViewContext, +// ) -> Self; +// } + +// pub trait FollowableItem: Item { +// fn remote_id(&self) -> Option; +// fn to_state_proto(&self, cx: &AppContext) -> Option; +// fn from_state_proto( +// pane: ViewHandle, +// project: ViewHandle, +// id: ViewId, +// state: &mut Option, +// cx: &mut AppContext, +// ) -> Option>>>; +// fn add_event_to_update_proto( +// &self, +// event: &Self::Event, +// update: &mut Option, +// cx: &AppContext, +// ) -> bool; +// fn apply_update_proto( +// &mut self, +// project: &ModelHandle, +// message: proto::update_view::Variant, +// cx: &mut ViewContext, +// ) -> Task>; +// fn is_project_item(&self, cx: &AppContext) -> bool; + +// fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); +// fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; +// } + +// pub trait FollowableItemHandle: ItemHandle { +// fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; +// fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); +// fn to_state_proto(&self, cx: &AppContext) -> Option; +// fn add_event_to_update_proto( +// &self, +// event: &dyn Any, +// update: &mut Option, +// cx: &AppContext, +// ) -> bool; +// fn apply_update_proto( +// &self, +// project: &ModelHandle, +// message: proto::update_view::Variant, +// cx: &mut WindowContext, +// ) -> Task>; +// fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; +// fn is_project_item(&self, cx: &AppContext) -> bool; +// } + +// impl FollowableItemHandle for ViewHandle { +// fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { +// self.read(cx).remote_id().or_else(|| { +// client.peer_id().map(|creator| ViewId { +// creator, +// id: self.id() as u64, +// }) +// }) +// } + +// fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext) { +// self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx)) +// } + +// fn to_state_proto(&self, cx: &AppContext) -> Option { +// self.read(cx).to_state_proto(cx) +// } + +// fn add_event_to_update_proto( +// &self, +// event: &dyn Any, +// update: &mut Option, +// cx: &AppContext, +// ) -> bool { +// if let Some(event) = event.downcast_ref() { +// self.read(cx).add_event_to_update_proto(event, update, cx) +// } else { +// false +// } +// } + +// fn apply_update_proto( +// &self, +// project: &ModelHandle, +// message: proto::update_view::Variant, +// cx: &mut WindowContext, +// ) -> Task> { +// self.update(cx, |this, cx| this.apply_update_proto(project, message, cx)) +// } + +// fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool { +// if let Some(event) = event.downcast_ref() { +// T::should_unfollow_on_event(event, cx) +// } else { +// false +// } +// } + +// fn is_project_item(&self, cx: &AppContext) -> bool { +// self.read(cx).is_project_item(cx) +// } +// } + +// #[cfg(any(test, feature = "test-support"))] +// pub mod test { +// use super::{Item, ItemEvent}; +// use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; +// use gpui2::{ +// elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, +// ViewContext, ViewHandle, WeakViewHandle, +// }; +// use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId}; +// use smallvec::SmallVec; +// use std::{any::Any, borrow::Cow, cell::Cell, path::Path}; + +// pub struct TestProjectItem { +// pub entry_id: Option, +// pub project_path: Option, +// } + +// pub struct TestItem { +// pub workspace_id: WorkspaceId, +// pub state: String, +// pub label: String, +// pub save_count: usize, +// pub save_as_count: usize, +// pub reload_count: usize, +// pub is_dirty: bool, +// pub is_singleton: bool, +// pub has_conflict: bool, +// pub project_items: Vec>, +// pub nav_history: Option, +// pub tab_descriptions: Option>, +// pub tab_detail: Cell>, +// } + +// impl Entity for TestProjectItem { +// type Event = (); +// } + +// impl project2::Item for TestProjectItem { +// fn entry_id(&self, _: &AppContext) -> Option { +// self.entry_id +// } + +// fn project_path(&self, _: &AppContext) -> Option { +// self.project_path.clone() +// } +// } + +// pub enum TestItemEvent { +// Edit, +// } + +// impl Clone for TestItem { +// fn clone(&self) -> Self { +// Self { +// state: self.state.clone(), +// label: self.label.clone(), +// save_count: self.save_count, +// save_as_count: self.save_as_count, +// reload_count: self.reload_count, +// is_dirty: self.is_dirty, +// is_singleton: self.is_singleton, +// has_conflict: self.has_conflict, +// project_items: self.project_items.clone(), +// nav_history: None, +// tab_descriptions: None, +// tab_detail: Default::default(), +// workspace_id: self.workspace_id, +// } +// } +// } + +// impl TestProjectItem { +// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> ModelHandle { +// let entry_id = Some(ProjectEntryId::from_proto(id)); +// let project_path = Some(ProjectPath { +// worktree_id: WorktreeId::from_usize(0), +// path: Path::new(path).into(), +// }); +// cx.add_model(|_| Self { +// entry_id, +// project_path, +// }) +// } + +// pub fn new_untitled(cx: &mut AppContext) -> ModelHandle { +// cx.add_model(|_| Self { +// project_path: None, +// entry_id: None, +// }) +// } +// } + +// impl TestItem { +// pub fn new() -> Self { +// Self { +// state: String::new(), +// label: String::new(), +// save_count: 0, +// save_as_count: 0, +// reload_count: 0, +// is_dirty: false, +// has_conflict: false, +// project_items: Vec::new(), +// is_singleton: true, +// nav_history: None, +// tab_descriptions: None, +// tab_detail: Default::default(), +// workspace_id: 0, +// } +// } + +// pub fn new_deserialized(id: WorkspaceId) -> Self { +// let mut this = Self::new(); +// this.workspace_id = id; +// this +// } + +// pub fn with_label(mut self, state: &str) -> Self { +// self.label = state.to_string(); +// self +// } + +// pub fn with_singleton(mut self, singleton: bool) -> Self { +// self.is_singleton = singleton; +// self +// } + +// pub fn with_dirty(mut self, dirty: bool) -> Self { +// self.is_dirty = dirty; +// self +// } + +// pub fn with_conflict(mut self, has_conflict: bool) -> Self { +// self.has_conflict = has_conflict; +// self +// } + +// pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { +// self.project_items.clear(); +// self.project_items.extend(items.iter().cloned()); +// self +// } + +// pub fn set_state(&mut self, state: String, cx: &mut ViewContext) { +// self.push_to_nav_history(cx); +// self.state = state; +// } + +// fn push_to_nav_history(&mut self, cx: &mut ViewContext) { +// if let Some(history) = &mut self.nav_history { +// history.push(Some(Box::new(self.state.clone())), cx); +// } +// } +// } + +// impl Entity for TestItem { +// type Event = TestItemEvent; +// } + +// impl View for TestItem { +// fn ui_name() -> &'static str { +// "TestItem" +// } + +// fn render(&mut self, _: &mut ViewContext) -> AnyElement { +// Empty::new().into_any() +// } +// } + +// impl Item for TestItem { +// fn tab_description(&self, detail: usize, _: &AppContext) -> Option> { +// self.tab_descriptions.as_ref().and_then(|descriptions| { +// let description = *descriptions.get(detail).or_else(|| descriptions.last())?; +// Some(description.into()) +// }) +// } + +// fn tab_content( +// &self, +// detail: Option, +// _: &theme2::Tab, +// _: &AppContext, +// ) -> AnyElement { +// self.tab_detail.set(detail); +// Empty::new().into_any() +// } + +// fn for_each_project_item( +// &self, +// cx: &AppContext, +// f: &mut dyn FnMut(usize, &dyn project2::Item), +// ) { +// self.project_items +// .iter() +// .for_each(|item| f(item.id(), item.read(cx))) +// } + +// fn is_singleton(&self, _: &AppContext) -> bool { +// self.is_singleton +// } + +// fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { +// self.nav_history = Some(history); +// } + +// fn navigate(&mut self, state: Box, _: &mut ViewContext) -> bool { +// let state = *state.downcast::().unwrap_or_default(); +// if state != self.state { +// self.state = state; +// true +// } else { +// false +// } +// } + +// fn deactivated(&mut self, cx: &mut ViewContext) { +// self.push_to_nav_history(cx); +// } + +// fn clone_on_split( +// &self, +// _workspace_id: WorkspaceId, +// _: &mut ViewContext, +// ) -> Option +// where +// Self: Sized, +// { +// Some(self.clone()) +// } + +// fn is_dirty(&self, _: &AppContext) -> bool { +// self.is_dirty +// } + +// fn has_conflict(&self, _: &AppContext) -> bool { +// self.has_conflict +// } + +// fn can_save(&self, cx: &AppContext) -> bool { +// !self.project_items.is_empty() +// && self +// .project_items +// .iter() +// .all(|item| item.read(cx).entry_id.is_some()) +// } + +// fn save( +// &mut self, +// _: ModelHandle, +// _: &mut ViewContext, +// ) -> Task> { +// self.save_count += 1; +// self.is_dirty = false; +// Task::ready(Ok(())) +// } + +// fn save_as( +// &mut self, +// _: ModelHandle, +// _: std::path::PathBuf, +// _: &mut ViewContext, +// ) -> Task> { +// self.save_as_count += 1; +// self.is_dirty = false; +// Task::ready(Ok(())) +// } + +// fn reload( +// &mut self, +// _: ModelHandle, +// _: &mut ViewContext, +// ) -> Task> { +// self.reload_count += 1; +// self.is_dirty = false; +// Task::ready(Ok(())) +// } + +// fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> { +// [ItemEvent::UpdateTab, ItemEvent::Edit].into() +// } + +// fn serialized_item_kind() -> Option<&'static str> { +// Some("TestItem") +// } + +// fn deserialize( +// _project: ModelHandle, +// _workspace: WeakViewHandle, +// workspace_id: WorkspaceId, +// _item_id: ItemId, +// cx: &mut ViewContext, +// ) -> Task>> { +// let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id)); +// Task::Ready(Some(anyhow::Ok(view))) +// } +// } +// } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index e885408221db980e8baff7c7f52546110e032280..b1fbae1987844f4f1fd8c73a310a5c5f4a53ba0a 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -120,8 +120,6 @@ impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; -pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)]; - pub fn init(cx: &mut AppContext) { cx.add_action(Pane::toggle_zoom); cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { @@ -172,7 +170,6 @@ pub struct Pane { toolbar: ViewHandle, tab_bar_context_menu: TabBarContextMenu, tab_context_menu: ViewHandle, - _background_actions: BackgroundActions, workspace: WeakViewHandle, project: ModelHandle, has_focus: bool, @@ -306,7 +303,6 @@ impl Pane { pub fn new( workspace: WeakViewHandle, project: ModelHandle, - background_actions: BackgroundActions, next_timestamp: Arc, cx: &mut ViewContext, ) -> Self { @@ -339,7 +335,6 @@ impl Pane { handle: context_menu, }, tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), - _background_actions: background_actions, workspace, project, has_focus: false, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 607bc5b61c61774c32e87f4c13cc8b12d91d289e..36dd26eb3cc096c14dbc3d3bbbfeb37944ddc714 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,5 +1,5 @@ // pub mod dock; -// pub mod item; +pub mod item; // pub mod notifications; // pub mod pane; // pub mod pane_group; @@ -11,19 +11,18 @@ // mod workspace_settings; // use anyhow::{anyhow, Context, Result}; -// use call::ActiveCall; -// use client::{ +// use call2::ActiveCall; +// use client2::{ // proto::{self, PeerId}, // Client, Status, TypedEnvelope, UserStore, // }; // use collections::{hash_map, HashMap, HashSet}; -// use drag_and_drop::DragAndDrop; // use futures::{ // channel::{mpsc, oneshot}, // future::try_join_all, // FutureExt, StreamExt, // }; -// use gpui::{ +// use gpui2::{ // actions, // elements::*, // geometry::{ @@ -41,19 +40,8 @@ // }; // use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; // use itertools::Itertools; -// use language::{LanguageRegistry, Rope}; -// use node_runtime::NodeRuntime; -// use std::{ -// any::TypeId, -// borrow::Cow, -// cmp, env, -// future::Future, -// path::{Path, PathBuf}, -// rc::Rc, -// str, -// sync::{atomic::AtomicUsize, Arc}, -// time::Duration, -// }; +// use language2::{LanguageRegistry, Rope}; +// use node_runtime::NodeRuntime;// // // use crate::{ // notifications::{simple_message_notification::MessageNotification, NotificationTracker}, @@ -446,32 +434,31 @@ // }); // } -// pub struct AppState { -// pub languages: Arc, -// pub client: Arc, -// pub user_store: ModelHandle, -// pub workspace_store: ModelHandle, -// pub fs: Arc, -// pub build_window_options: -// fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, -// pub initialize_workspace: -// fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, -// pub background_actions: BackgroundActions, -// pub node_runtime: Arc, -// } - -// pub struct WorkspaceStore { -// workspaces: HashSet>, -// followers: Vec, -// client: Arc, -// _subscriptions: Vec, -// } - -// #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] -// struct Follower { -// project_id: Option, -// peer_id: PeerId, -// } +pub struct AppState { + pub languages: Arc, + pub client: Arc, + pub user_store: Handle, + pub workspace_store: Handle, + pub fs: Arc, + pub build_window_options: + fn(Option, Option, &MainThread) -> WindowOptions, + pub initialize_workspace: + fn(WeakHandle, bool, Arc, AsyncAppContext) -> Task>, + pub node_runtime: Arc, +} + +pub struct WorkspaceStore { + workspaces: HashSet>, + followers: Vec, + client: Arc, + _subscriptions: Vec, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +struct Follower { + project_id: Option, + peer_id: PeerId, +} // impl AppState { // #[cfg(any(test, feature = "test-support"))] @@ -504,7 +491,6 @@ // node_runtime: FakeNodeRuntime::new(), // initialize_workspace: |_, _, _, _| Task::ready(Ok(())), // build_window_options: |_, _, _| Default::default(), -// background_actions: || &[], // }) // } // } @@ -560,37 +546,37 @@ // ContactRequestedJoin(u64), // } -// pub struct Workspace { -// weak_self: WeakViewHandle, -// modal: Option, -// zoomed: Option, -// zoomed_position: Option, -// center: PaneGroup, -// left_dock: ViewHandle, -// bottom_dock: ViewHandle, -// right_dock: ViewHandle, -// panes: Vec>, -// panes_by_item: HashMap>, -// active_pane: ViewHandle, -// last_active_center_pane: Option>, -// last_active_view_id: Option, -// status_bar: ViewHandle, -// titlebar_item: Option, -// notifications: Vec<(TypeId, usize, Box)>, -// project: ModelHandle, -// follower_states: HashMap, FollowerState>, -// last_leaders_by_pane: HashMap, PeerId>, -// window_edited: bool, -// active_call: Option<(ModelHandle, Vec)>, -// leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, -// database_id: WorkspaceId, -// app_state: Arc, -// subscriptions: Vec, -// _apply_leader_updates: Task>, -// _observe_current_user: Task>, -// _schedule_serialize: Option>, -// pane_history_timestamp: Arc, -// } +pub struct Workspace { + weak_self: WeakHandle, + // modal: Option, + // zoomed: Option, + // zoomed_position: Option, + // center: PaneGroup, + // left_dock: ViewHandle, + // bottom_dock: ViewHandle, + // right_dock: ViewHandle, + // panes: Vec>, + // panes_by_item: HashMap>, + // active_pane: ViewHandle, + // last_active_center_pane: Option>, + // last_active_view_id: Option, + // status_bar: ViewHandle, + // titlebar_item: Option, + // notifications: Vec<(TypeId, usize, Box)>, + project: Handle, + // follower_states: HashMap, FollowerState>, + // last_leaders_by_pane: HashMap, PeerId>, + // window_edited: bool, + // active_call: Option<(ModelHandle, Vec)>, + // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, + // database_id: WorkspaceId, + // app_state: Arc, + // subscriptions: Vec, + // _apply_leader_updates: Task>, + // _observe_current_user: Task>, + // _schedule_serialize: Option>, + // pane_history_timestamp: Arc, +} // struct ActiveModal { // view: Box, @@ -669,7 +655,6 @@ // Pane::new( // weak_handle.clone(), // project.clone(), -// app_state.background_actions, // pane_history_timestamp.clone(), // cx, // ) @@ -1976,7 +1961,6 @@ // Pane::new( // self.weak_handle(), // self.project.clone(), -// self.app_state.background_actions, // self.pane_history_timestamp.clone(), // cx, // ) @@ -3536,7 +3520,6 @@ // fs: project.read(cx).fs().clone(), // build_window_options: |_, _, _| Default::default(), // initialize_workspace: |_, _, _, _| Task::ready(Ok(())), -// background_actions: || &[], // node_runtime: FakeNodeRuntime::new(), // }); // Self::new(0, project, app_state, cx) @@ -4117,30 +4100,36 @@ // pub struct WorkspaceCreated(pub WeakViewHandle); -// pub fn activate_workspace_for_project( -// cx: &mut AsyncAppContext, -// predicate: impl Fn(&mut Project, &mut ModelContext) -> bool, -// ) -> Option> { -// for window in cx.windows() { -// let handle = window -// .update(cx, |cx| { -// if let Some(workspace_handle) = cx.root_view().clone().downcast::() { -// let project = workspace_handle.read(cx).project.clone(); -// if project.update(cx, &predicate) { -// cx.activate_window(); -// return Some(workspace_handle.clone()); -// } -// } -// None -// }) -// .flatten(); - -// if let Some(handle) = handle { -// return Some(handle.downgrade()); -// } -// } -// None -// } +pub async fn activate_workspace_for_project( + cx: &mut AsyncAppContext, + predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static, +) -> Option> { + cx.run_on_main(move |cx| { + for window in cx.windows() { + let handle = cx + .update_window(window, |cx| { + if let Some(workspace_handle) = cx.root_view()?.downcast::() { + let project = workspace_handle.read(cx).project.clone(); + if project.update(cx, |project, cx| predicate(project, cx)) { + cx.activate_window(); + return Some(workspace_handle.clone()); + } + } + None + }) + .log_err() + .flatten(); + + if let Some(handle) = handle { + return Some(handle.downgrade()); + } + } + + None + }) + .ok()? + .await +} // pub async fn last_opened_workspace_paths() -> Option { // DB.last_workspace().await.log_err().flatten() @@ -4328,44 +4317,58 @@ // None // } -// #[allow(clippy::type_complexity)] -// pub fn open_paths( -// abs_paths: &[PathBuf], -// app_state: &Arc, -// requesting_window: Option>, -// cx: &mut AppContext, -// ) -> Task< -// Result<( -// WeakViewHandle, -// Vec, anyhow::Error>>>, -// )>, -// > { -// let app_state = app_state.clone(); -// let abs_paths = abs_paths.to_vec(); -// cx.spawn(|mut cx| async move { -// // Open paths in existing workspace if possible -// let existing = activate_workspace_for_project(&mut cx, |project, cx| { -// project.contains_paths(&abs_paths, cx) -// }); - -// if let Some(existing) = existing { -// Ok(( -// existing.clone(), -// existing -// .update(&mut cx, |workspace, cx| { -// workspace.open_paths(abs_paths, true, cx) -// })? -// .await, -// )) -// } else { -// Ok(cx -// .update(|cx| { -// Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) -// }) -// .await) -// } -// }) -// } +use client2::{proto::PeerId, Client, UserStore}; +use collections::HashSet; +use gpui2::{ + AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, WeakHandle, WindowBounds, + WindowHandle, WindowOptions, +}; +use item::ItemHandle; +use language2::LanguageRegistry; +use node_runtime::NodeRuntime; +use project2::Project; +use std::{path::PathBuf, sync::Arc}; +use util::ResultExt; + +#[allow(clippy::type_complexity)] +pub fn open_paths( + abs_paths: &[PathBuf], + app_state: &Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task< + anyhow::Result<( + WeakHandle, + Vec, anyhow::Error>>>, + )>, +> { + let app_state = app_state.clone(); + let abs_paths = abs_paths.to_vec(); + cx.spawn(|mut cx| async move { + // Open paths in existing workspace if possible + let existing = activate_workspace_for_project(&mut cx, |project, cx| { + project.contains_paths(&abs_paths, cx) + }) + .await; + + if let Some(existing) = existing { + Ok(( + existing.clone(), + existing + .update(&mut cx, |workspace, cx| { + workspace.open_paths(abs_paths, true, cx) + })? + .await, + )) + } else { + Ok(cx + .update(|cx| { + Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) + }) + .await) + } + }) +} // pub fn open_new( // app_state: &Arc,