From c8b5b085f4bc8a3c425725beaa49bc5b4dddb8ab Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Oct 2023 12:27:20 +0200 Subject: [PATCH 01/66] WIP --- Cargo.lock | 38 + Cargo.toml | 2 +- crates/gpui2/src/app.rs | 14 + crates/gpui2/src/app/model_context.rs | 14 +- crates/gpui2/src/window.rs | 24 +- crates/workspace2/Cargo.toml | 65 + crates/workspace2/src/dock.rs | 744 +++ crates/workspace2/src/item.rs | 1081 ++++ crates/workspace2/src/notifications.rs | 400 ++ crates/workspace2/src/pane.rs | 2742 ++++++++ .../src/pane/dragged_item_receiver.rs | 239 + crates/workspace2/src/pane_group.rs | 989 +++ crates/workspace2/src/persistence.rs | 972 +++ crates/workspace2/src/persistence/model.rs | 344 + crates/workspace2/src/searchable.rs | 282 + crates/workspace2/src/shared_screen.rs | 151 + crates/workspace2/src/status_bar.rs | 271 + crates/workspace2/src/toolbar.rs | 301 + crates/workspace2/src/workspace2.rs | 5520 +++++++++++++++++ crates/workspace2/src/workspace_settings.rs | 56 + crates/zed2/Cargo.toml | 2 +- crates/zed2/src/zed2.rs | 292 +- 22 files changed, 14373 insertions(+), 170 deletions(-) create mode 100644 crates/workspace2/Cargo.toml create mode 100644 crates/workspace2/src/dock.rs create mode 100644 crates/workspace2/src/item.rs create mode 100644 crates/workspace2/src/notifications.rs create mode 100644 crates/workspace2/src/pane.rs create mode 100644 crates/workspace2/src/pane/dragged_item_receiver.rs create mode 100644 crates/workspace2/src/pane_group.rs create mode 100644 crates/workspace2/src/persistence.rs create mode 100644 crates/workspace2/src/persistence/model.rs create mode 100644 crates/workspace2/src/searchable.rs create mode 100644 crates/workspace2/src/shared_screen.rs create mode 100644 crates/workspace2/src/status_bar.rs create mode 100644 crates/workspace2/src/toolbar.rs create mode 100644 crates/workspace2/src/workspace2.rs create mode 100644 crates/workspace2/src/workspace_settings.rs diff --git a/Cargo.lock b/Cargo.lock index cbbcbc7914..cd79c19630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10549,6 +10549,43 @@ dependencies = [ "uuid 1.4.1", ] +[[package]] +name = "workspace2" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-recursion 1.0.5", + "bincode", + "call2", + "client2", + "collections", + "db2", + "env_logger 0.9.3", + "fs2", + "futures 0.3.28", + "gpui2", + "indoc", + "install_cli2", + "itertools 0.10.5", + "language2", + "lazy_static", + "log", + "node_runtime", + "parking_lot 0.11.2", + "postage", + "project2", + "schemars", + "serde", + "serde_derive", + "serde_json", + "settings2", + "smallvec", + "terminal2", + "theme2", + "util", + "uuid 1.4.1", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -10857,6 +10894,7 @@ dependencies = [ "urlencoding", "util", "uuid 1.4.1", + "workspace2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f8ce95ea6b..0bb710fc1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ members = [ "crates/semantic_index", "crates/vim", "crates/vcs_menu", - "crates/workspace", + "crates/workspace2", "crates/welcome", "crates/xtask", "crates/zed", diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 75f202e710..260ec0b6b3 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -607,6 +607,20 @@ impl AppContext { self.globals_by_type.insert(global_type, lease.global); } + pub fn observe_release( + &mut self, + handle: &Handle, + mut on_release: impl FnMut(&mut E, &mut AppContext) + Send + Sync + 'static, + ) -> Subscription { + self.release_listeners.insert( + handle.entity_id, + Box::new(move |entity, cx| { + let entity = entity.downcast_mut().expect("invalid entity type"); + on_release(entity, cx) + }), + ) + } + pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) { self.text_style_stack.push(text_style); } diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 6f88bf1aa6..f3d0bf2397 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -115,15 +115,11 @@ impl<'a, T: 'static> ModelContext<'a, T> { T: Any + Send + Sync, { let this = self.weak_handle(); - self.app.release_listeners.insert( - handle.entity_id, - Box::new(move |entity, cx| { - let entity = entity.downcast_mut().expect("invalid entity type"); - if let Some(this) = this.upgrade() { - this.update(cx, |this, cx| on_release(this, entity, cx)); - } - }), - ) + self.app.observe_release(handle, move |entity, cx| { + if let Some(this) = this.upgrade() { + this.update(cx, |this, cx| on_release(this, entity, cx)); + } + }) } pub fn observe_global( diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 8b3aa7a117..55cf04c51d 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,7 +1,7 @@ use crate::{ px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, - Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, ExternalPaths, - Edges, Effect, Element, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, + 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, @@ -1517,22 +1517,14 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { &mut self, handle: &Handle, mut on_release: impl FnMut(&mut V, &mut T, &mut ViewContext<'_, '_, V>) + Send + Sync + 'static, - ) -> Subscription - where - V: Any + Send + Sync, - { + ) -> Subscription { let this = self.handle(); let window_handle = self.window.handle; - self.app.release_listeners.insert( - handle.entity_id, - Box::new(move |entity, cx| { - let entity = entity.downcast_mut().expect("invalid entity type"); - // todo!("are we okay with silently swallowing the error?") - let _ = cx.update_window(window_handle.id, |cx| { - this.update(cx, |this, cx| on_release(this, entity, cx)) - }); - }), - ) + self.app.observe_release(handle, move |entity, cx| { + let _ = cx.update_window(window_handle.id, |cx| { + this.update(cx, |this, cx| on_release(this, entity, cx)) + }); + }) } pub fn notify(&mut self) { diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml new file mode 100644 index 0000000000..7c55d8bedb --- /dev/null +++ b/crates/workspace2/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "workspace2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/workspace2.rs" +doctest = false + +[features] +test-support = [ + "call2/test-support", + "client2/test-support", + "project2/test-support", + "settings2/test-support", + "gpui2/test-support", + "fs2/test-support" +] + +[dependencies] +db2 = { path = "../db2" } +call2 = { path = "../call2" } +client2 = { path = "../client2" } +collections = { path = "../collections" } +# context_menu = { path = "../context_menu" } +fs2 = { path = "../fs2" } +gpui2 = { path = "../gpui2" } +install_cli2 = { path = "../install_cli2" } +language2 = { path = "../language2" } +#menu = { path = "../menu" } +node_runtime = { path = "../node_runtime" } +project2 = { path = "../project2" } +settings2 = { path = "../settings2" } +terminal2 = { path = "../terminal2" } +theme2 = { path = "../theme2" } +util = { path = "../util" } + +async-recursion = "1.0.0" +itertools = "0.10" +bincode = "1.2.1" +anyhow.workspace = true +futures.workspace = true +lazy_static.workspace = true +log.workspace = true +parking_lot.workspace = true +postage.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true +smallvec.workspace = true +uuid.workspace = true + +[dev-dependencies] +call2 = { path = "../call2", features = ["test-support"] } +client2 = { path = "../client2", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +project2 = { path = "../project2", features = ["test-support"] } +settings2 = { path = "../settings2", features = ["test-support"] } +fs2 = { path = "../fs2", features = ["test-support"] } +db2 = { path = "../db2", features = ["test-support"] } + +indoc.workspace = true +env_logger.workspace = true diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs new file mode 100644 index 0000000000..5445f89050 --- /dev/null +++ b/crates/workspace2/src/dock.rs @@ -0,0 +1,744 @@ +use crate::{StatusItemView, Workspace, WorkspaceBounds}; +use gpui2::{ + elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext, + Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::rc::Rc; +use theme2::ThemeSettings; + +pub trait Panel: View { + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition) -> bool; + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&mut self, size: Option, cx: &mut ViewContext); + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>; + fn icon_tooltip(&self) -> (String, Option>); + fn icon_label(&self, _: &WindowContext) -> Option { + None + } + fn should_change_position_on_event(_: &Self::Event) -> bool; + fn should_zoom_in_on_event(_: &Self::Event) -> bool { + false + } + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + fn is_zoomed(&self, _cx: &WindowContext) -> bool { + false + } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) {} + fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) {} + fn should_activate_on_event(_: &Self::Event) -> bool { + false + } + fn should_close_on_event(_: &Self::Event) -> bool { + false + } + fn has_focus(&self, cx: &WindowContext) -> bool; + fn is_focus_event(_: &Self::Event) -> bool; +} + +pub trait PanelHandle { + fn id(&self) -> usize; + fn position(&self, cx: &WindowContext) -> DockPosition; + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; + fn set_position(&self, position: DockPosition, cx: &mut WindowContext); + fn is_zoomed(&self, cx: &WindowContext) -> bool; + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); + fn set_active(&self, active: bool, cx: &mut WindowContext); + fn size(&self, cx: &WindowContext) -> f32; + fn set_size(&self, size: Option, cx: &mut WindowContext); + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>; + fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>); + fn icon_label(&self, cx: &WindowContext) -> Option; + fn has_focus(&self, cx: &WindowContext) -> bool; + fn as_any(&self) -> &AnyViewHandle; +} + +impl PanelHandle for ViewHandle +where + T: Panel, +{ + fn id(&self) -> usize { + self.id() + } + + fn position(&self, cx: &WindowContext) -> DockPosition { + self.read(cx).position(cx) + } + + fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool { + self.read(cx).position_is_valid(position) + } + + fn set_position(&self, position: DockPosition, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_position(position, cx)) + } + + fn size(&self, cx: &WindowContext) -> f32 { + self.read(cx).size(cx) + } + + fn set_size(&self, size: Option, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_size(size, cx)) + } + + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.read(cx).is_zoomed(cx) + } + + fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_zoomed(zoomed, cx)) + } + + fn set_active(&self, active: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_active(active, cx)) + } + + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str> { + self.read(cx).icon_path(cx) + } + + fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>) { + self.read(cx).icon_tooltip() + } + + fn icon_label(&self, cx: &WindowContext) -> Option { + self.read(cx).icon_label(cx) + } + + fn has_focus(&self, cx: &WindowContext) -> bool { + self.read(cx).has_focus(cx) + } + + fn as_any(&self) -> &AnyViewHandle { + self + } +} + +impl From<&dyn PanelHandle> for AnyViewHandle { + fn from(val: &dyn PanelHandle) -> Self { + val.as_any().clone() + } +} + +pub struct Dock { + position: DockPosition, + panel_entries: Vec, + is_open: bool, + active_panel_index: usize, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum DockPosition { + Left, + Bottom, + Right, +} + +impl DockPosition { + fn to_label(&self) -> &'static str { + match self { + Self::Left => "left", + Self::Bottom => "bottom", + Self::Right => "right", + } + } + + fn to_resize_handle_side(self) -> HandleSide { + match self { + Self::Left => HandleSide::Right, + Self::Bottom => HandleSide::Top, + Self::Right => HandleSide::Left, + } + } + + pub fn axis(&self) -> Axis { + match self { + Self::Left | Self::Right => Axis::Horizontal, + Self::Bottom => Axis::Vertical, + } + } +} + +struct PanelEntry { + panel: Rc, + context_menu: ViewHandle, + _subscriptions: [Subscription; 2], +} + +pub struct PanelButtons { + dock: ViewHandle, + workspace: WeakViewHandle, +} + +impl Dock { + pub fn new(position: DockPosition) -> Self { + Self { + position, + panel_entries: Default::default(), + active_panel_index: 0, + is_open: false, + } + } + + pub fn position(&self) -> DockPosition { + self.position + } + + pub fn is_open(&self) -> bool { + self.is_open + } + + pub fn has_focus(&self, cx: &WindowContext) -> bool { + self.visible_panel() + .map_or(false, |panel| panel.has_focus(cx)) + } + + pub fn panel(&self) -> Option> { + self.panel_entries + .iter() + .find_map(|entry| entry.panel.as_any().clone().downcast()) + } + + pub fn panel_index_for_type(&self) -> Option { + self.panel_entries + .iter() + .position(|entry| entry.panel.as_any().is::()) + } + + pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { + self.panel_entries.iter().position(|entry| { + let panel = entry.panel.as_any(); + cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) + }) + } + + pub fn active_panel_index(&self) -> usize { + self.active_panel_index + } + + pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { + if open != self.is_open { + self.is_open = open; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(open, cx); + } + + cx.notify(); + } + } + + pub fn set_panel_zoomed( + &mut self, + panel: &AnyViewHandle, + zoomed: bool, + cx: &mut ViewContext, + ) { + for entry in &mut self.panel_entries { + if entry.panel.as_any() == panel { + if zoomed != entry.panel.is_zoomed(cx) { + entry.panel.set_zoomed(zoomed, cx); + } + } else if entry.panel.is_zoomed(cx) { + entry.panel.set_zoomed(false, cx); + } + } + + cx.notify(); + } + + pub fn zoom_out(&mut self, cx: &mut ViewContext) { + for entry in &mut self.panel_entries { + if entry.panel.is_zoomed(cx) { + entry.panel.set_zoomed(false, cx); + } + } + } + + pub(crate) fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { + let subscriptions = [ + cx.observe(&panel, |_, _, cx| cx.notify()), + cx.subscribe(&panel, |this, panel, event, cx| { + if T::should_activate_on_event(event) { + if let Some(ix) = this + .panel_entries + .iter() + .position(|entry| entry.panel.id() == panel.id()) + { + this.set_open(true, cx); + this.activate_panel(ix, cx); + cx.focus(&panel); + } + } else if T::should_close_on_event(event) + && this.visible_panel().map_or(false, |p| p.id() == panel.id()) + { + this.set_open(false, cx); + } + }), + ]; + + let dock_view_id = cx.view_id(); + self.panel_entries.push(PanelEntry { + panel: Rc::new(panel), + context_menu: cx.add_view(|cx| { + let mut menu = ContextMenu::new(dock_view_id, cx); + menu.set_position_mode(OverlayPositionMode::Local); + menu + }), + _subscriptions: subscriptions, + }); + cx.notify() + } + + pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { + if let Some(panel_ix) = self + .panel_entries + .iter() + .position(|entry| entry.panel.id() == panel.id()) + { + if panel_ix == self.active_panel_index { + self.active_panel_index = 0; + self.set_open(false, cx); + } else if panel_ix < self.active_panel_index { + self.active_panel_index -= 1; + } + self.panel_entries.remove(panel_ix); + cx.notify(); + } + } + + pub fn panels_len(&self) -> usize { + self.panel_entries.len() + } + + pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + if panel_ix != self.active_panel_index { + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(false, cx); + } + + self.active_panel_index = panel_ix; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(true, cx); + } + + cx.notify(); + } + } + + pub fn visible_panel(&self) -> Option<&Rc> { + let entry = self.visible_entry()?; + Some(&entry.panel) + } + + pub fn active_panel(&self) -> Option<&Rc> { + Some(&self.panel_entries.get(self.active_panel_index)?.panel) + } + + fn visible_entry(&self) -> Option<&PanelEntry> { + if self.is_open { + self.panel_entries.get(self.active_panel_index) + } else { + None + } + } + + pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { + let entry = self.visible_entry()?; + if entry.panel.is_zoomed(cx) { + Some(entry.panel.clone()) + } else { + None + } + } + + pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { + self.panel_entries + .iter() + .find(|entry| entry.panel.id() == panel.id()) + .map(|entry| entry.panel.size(cx)) + } + + pub fn active_panel_size(&self, cx: &WindowContext) -> Option { + if self.is_open { + self.panel_entries + .get(self.active_panel_index) + .map(|entry| entry.panel.size(cx)) + } else { + None + } + } + + pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { + if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { + entry.panel.set_size(size, cx); + cx.notify(); + } + } + + pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { + if let Some(active_entry) = self.visible_entry() { + Empty::new() + .into_any() + .contained() + .with_style(self.style(cx)) + .resizable::( + self.position.to_resize_handle_side(), + active_entry.panel.size(cx), + |_, _, _| {}, + ) + .into_any() + } else { + Empty::new().into_any() + } + } + + fn style(&self, cx: &WindowContext) -> ContainerStyle { + let theme = &settings::get::(cx).theme; + let style = match self.position { + DockPosition::Left => theme.workspace.dock.left, + DockPosition::Bottom => theme.workspace.dock.bottom, + DockPosition::Right => theme.workspace.dock.right, + }; + style + } +} + +impl Entity for Dock { + type Event = (); +} + +impl View for Dock { + fn ui_name() -> &'static str { + "Dock" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + if let Some(active_entry) = self.visible_entry() { + let style = self.style(cx); + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style) + .resizable::( + self.position.to_resize_handle_side(), + active_entry.panel.size(cx), + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() + } else { + Empty::new().into_any() + } + } + + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + if cx.is_self_focused() { + if let Some(active_entry) = self.visible_entry() { + cx.focus(active_entry.panel.as_any()); + } else { + cx.focus_parent(); + } + } + } +} + +impl PanelButtons { + pub fn new( + dock: ViewHandle, + workspace: WeakViewHandle, + cx: &mut ViewContext, + ) -> Self { + cx.observe(&dock, |_, _, cx| cx.notify()).detach(); + Self { dock, workspace } + } +} + +impl Entity for PanelButtons { + type Event = (); +} + +impl View for PanelButtons { + fn ui_name() -> &'static str { + "PanelButtons" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + let theme = &settings::get::(cx).theme; + let tooltip_style = theme.tooltip.clone(); + let theme = &theme.workspace.status_bar.panel_buttons; + let button_style = theme.button.clone(); + let dock = self.dock.read(cx); + let active_ix = dock.active_panel_index; + let is_open = dock.is_open; + let dock_position = dock.position; + let group_style = match dock_position { + DockPosition::Left => theme.group_left, + DockPosition::Bottom => theme.group_bottom, + DockPosition::Right => theme.group_right, + }; + let menu_corner = match dock_position { + DockPosition::Left => AnchorCorner::BottomLeft, + DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, + }; + + let panels = dock + .panel_entries + .iter() + .map(|item| (item.panel.clone(), item.context_menu.clone())) + .collect::>(); + Flex::row() + .with_children(panels.into_iter().enumerate().filter_map( + |(panel_ix, (view, context_menu))| { + let icon_path = view.icon_path(cx)?; + let is_active = is_open && panel_ix == active_ix; + let (tooltip, tooltip_action) = if is_active { + ( + format!("Close {} dock", dock_position.to_label()), + Some(match dock_position { + DockPosition::Left => crate::ToggleLeftDock.boxed_clone(), + DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(), + DockPosition::Right => crate::ToggleRightDock.boxed_clone(), + }), + ) + } else { + view.icon_tooltip(cx) + }; + Some( + Stack::new() + .with_child( + MouseEventHandler::new::(panel_ix, cx, |state, cx| { + let style = button_style.in_state(is_active); + + let style = style.style_for(state); + Flex::row() + .with_child( + Svg::new(icon_path) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .aligned(), + ) + .with_children(if let Some(label) = view.icon_label(cx) { + Some( + Label::new(label, style.label.text.clone()) + .contained() + .with_style(style.label.container) + .aligned(), + ) + } else { + None + }) + .constrained() + .with_height(style.icon_size) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let tooltip_action = + tooltip_action.as_ref().map(|action| action.boxed_clone()); + move |_, this, cx| { + if let Some(tooltip_action) = &tooltip_action { + let window = cx.window(); + let view_id = this.workspace.id(); + let tooltip_action = tooltip_action.boxed_clone(); + cx.spawn(|_, mut cx| async move { + window.dispatch_action( + view_id, + &*tooltip_action, + &mut cx, + ); + }) + .detach(); + } + } + }) + .on_click(MouseButton::Right, { + let view = view.clone(); + let menu = context_menu.clone(); + move |_, _, cx| { + const POSITIONS: [DockPosition; 3] = [ + DockPosition::Left, + DockPosition::Right, + DockPosition::Bottom, + ]; + + menu.update(cx, |menu, cx| { + let items = POSITIONS + .into_iter() + .filter(|position| { + *position != dock_position + && view.position_is_valid(*position, cx) + }) + .map(|position| { + let view = view.clone(); + ContextMenuItem::handler( + format!("Dock {}", position.to_label()), + move |cx| view.set_position(position, cx), + ) + }) + .collect(); + menu.show(Default::default(), menu_corner, items, cx); + }) + } + }) + .with_tooltip::( + panel_ix, + tooltip, + tooltip_action, + tooltip_style.clone(), + cx, + ), + ) + .with_child(ChildView::new(&context_menu, cx)), + ) + }, + )) + .contained() + .with_style(group_style) + .into_any() + } +} + +impl StatusItemView for PanelButtons { + fn set_active_pane_item( + &mut self, + _: Option<&dyn crate::ItemHandle>, + _: &mut ViewContext, + ) { + } +} + +#[cfg(any(test, feature = "test-support"))] +pub mod test { + use super::*; + use gpui2::{ViewContext, WindowContext}; + + #[derive(Debug)] + pub enum TestPanelEvent { + PositionChanged, + Activated, + Closed, + ZoomIn, + ZoomOut, + Focus, + } + + pub struct TestPanel { + pub position: DockPosition, + pub zoomed: bool, + pub active: bool, + pub has_focus: bool, + pub size: f32, + } + + impl TestPanel { + pub fn new(position: DockPosition) -> Self { + Self { + position, + zoomed: false, + active: false, + has_focus: false, + size: 300., + } + } + } + + impl Entity for TestPanel { + type Event = TestPanelEvent; + } + + impl View for TestPanel { + fn ui_name() -> &'static str { + "TestPanel" + } + + fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { + Empty::new().into_any() + } + + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + self.has_focus = true; + cx.emit(TestPanelEvent::Focus); + } + + fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } + } + + impl Panel for TestPanel { + fn position(&self, _: &gpui::WindowContext) -> super::DockPosition { + self.position + } + + fn position_is_valid(&self, _: super::DockPosition) -> bool { + true + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + self.position = position; + cx.emit(TestPanelEvent::PositionChanged); + } + + fn is_zoomed(&self, _: &WindowContext) -> bool { + self.zoomed + } + + fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext) { + self.zoomed = zoomed; + } + + fn set_active(&mut self, active: bool, _cx: &mut ViewContext) { + self.active = active; + } + + fn size(&self, _: &WindowContext) -> f32 { + self.size + } + + fn set_size(&mut self, size: Option, _: &mut ViewContext) { + self.size = size.unwrap_or(300.); + } + + fn icon_path(&self, _: &WindowContext) -> Option<&'static str> { + Some("icons/test_panel.svg") + } + + fn icon_tooltip(&self) -> (String, Option>) { + ("Test Panel".into(), None) + } + + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::PositionChanged) + } + + fn should_zoom_in_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomIn) + } + + fn should_zoom_out_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::ZoomOut) + } + + fn should_activate_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::Activated) + } + + fn should_close_on_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::Closed) + } + + fn has_focus(&self, _cx: &WindowContext) -> bool { + self.has_focus + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, TestPanelEvent::Focus) + } + } +} diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs new file mode 100644 index 0000000000..39a1f0d51a --- /dev/null +++ b/crates/workspace2/src/item.rs @@ -0,0 +1,1081 @@ +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 + } +} + +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))) + } + } +} diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs new file mode 100644 index 0000000000..7846c7470a --- /dev/null +++ b/crates/workspace2/src/notifications.rs @@ -0,0 +1,400 @@ +use crate::{Toast, Workspace}; +use collections::HashMap; +use gpui2::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle}; +use std::{any::TypeId, ops::DerefMut}; + +pub fn init(cx: &mut AppContext) { + cx.set_global(NotificationTracker::new()); + simple_message_notification::init(cx); +} + +pub trait Notification: View { + fn should_dismiss_notification_on_event(&self, event: &::Event) -> bool; +} + +pub trait NotificationHandle { + fn id(&self) -> usize; + fn as_any(&self) -> &AnyViewHandle; +} + +impl NotificationHandle for ViewHandle { + fn id(&self) -> usize { + self.id() + } + + fn as_any(&self) -> &AnyViewHandle { + self + } +} + +impl From<&dyn NotificationHandle> for AnyViewHandle { + fn from(val: &dyn NotificationHandle) -> Self { + val.as_any().clone() + } +} + +pub(crate) struct NotificationTracker { + notifications_sent: HashMap>, +} + +impl std::ops::Deref for NotificationTracker { + type Target = HashMap>; + + fn deref(&self) -> &Self::Target { + &self.notifications_sent + } +} + +impl DerefMut for NotificationTracker { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.notifications_sent + } +} + +impl NotificationTracker { + fn new() -> Self { + Self { + notifications_sent: Default::default(), + } + } +} + +impl Workspace { + pub fn has_shown_notification_once( + &self, + id: usize, + cx: &ViewContext, + ) -> bool { + cx.global::() + .get(&TypeId::of::()) + .map(|ids| ids.contains(&id)) + .unwrap_or(false) + } + + pub fn show_notification_once( + &mut self, + id: usize, + cx: &mut ViewContext, + build_notification: impl FnOnce(&mut ViewContext) -> ViewHandle, + ) { + if !self.has_shown_notification_once::(id, cx) { + cx.update_global::(|tracker, _| { + let entry = tracker.entry(TypeId::of::()).or_default(); + entry.push(id); + }); + + self.show_notification::(id, cx, build_notification) + } + } + + pub fn show_notification( + &mut self, + id: usize, + cx: &mut ViewContext, + build_notification: impl FnOnce(&mut ViewContext) -> ViewHandle, + ) { + let type_id = TypeId::of::(); + if self + .notifications + .iter() + .all(|(existing_type_id, existing_id, _)| { + (*existing_type_id, *existing_id) != (type_id, id) + }) + { + let notification = build_notification(cx); + cx.subscribe(¬ification, move |this, handle, event, cx| { + if handle.read(cx).should_dismiss_notification_on_event(event) { + this.dismiss_notification_internal(type_id, id, cx); + } + }) + .detach(); + self.notifications + .push((type_id, id, Box::new(notification))); + cx.notify(); + } + } + + pub fn dismiss_notification(&mut self, id: usize, cx: &mut ViewContext) { + let type_id = TypeId::of::(); + + self.dismiss_notification_internal(type_id, id, cx) + } + + pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext) { + self.dismiss_notification::(toast.id, cx); + self.show_notification(toast.id, cx, |cx| { + cx.add_view(|_cx| match toast.on_click.as_ref() { + Some((click_msg, on_click)) => { + let on_click = on_click.clone(); + simple_message_notification::MessageNotification::new(toast.msg.clone()) + .with_click_message(click_msg.clone()) + .on_click(move |cx| on_click(cx)) + } + None => simple_message_notification::MessageNotification::new(toast.msg.clone()), + }) + }) + } + + pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext) { + self.dismiss_notification::(id, cx); + } + + fn dismiss_notification_internal( + &mut self, + type_id: TypeId, + id: usize, + cx: &mut ViewContext, + ) { + self.notifications + .retain(|(existing_type_id, existing_id, _)| { + if (*existing_type_id, *existing_id) == (type_id, id) { + cx.notify(); + false + } else { + true + } + }); + } +} + +pub mod simple_message_notification { + use super::Notification; + use crate::Workspace; + use gpui2::{ + actions, + elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, + fonts::TextStyle, + impl_actions, + platform::{CursorStyle, MouseButton}, + AnyElement, AppContext, Element, Entity, View, ViewContext, + }; + use menu::Cancel; + use serde::Deserialize; + use std::{borrow::Cow, sync::Arc}; + + actions!(message_notifications, [CancelMessageNotification]); + + #[derive(Clone, Default, Deserialize, PartialEq)] + pub struct OsOpen(pub Cow<'static, str>); + + impl OsOpen { + pub fn new>>(url: I) -> Self { + OsOpen(url.into()) + } + } + + impl_actions!(message_notifications, [OsOpen]); + + pub fn init(cx: &mut AppContext) { + cx.add_action(MessageNotification::dismiss); + cx.add_action( + |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext| { + cx.platform().open_url(open_action.0.as_ref()); + }, + ) + } + + enum NotificationMessage { + Text(Cow<'static, str>), + Element(fn(TextStyle, &AppContext) -> AnyElement), + } + + pub struct MessageNotification { + message: NotificationMessage, + on_click: Option)>>, + click_message: Option>, + } + + pub enum MessageNotificationEvent { + Dismiss, + } + + impl Entity for MessageNotification { + type Event = MessageNotificationEvent; + } + + impl MessageNotification { + pub fn new(message: S) -> MessageNotification + where + S: Into>, + { + Self { + message: NotificationMessage::Text(message.into()), + on_click: None, + click_message: None, + } + } + + pub fn new_element( + message: fn(TextStyle, &AppContext) -> AnyElement, + ) -> MessageNotification { + Self { + message: NotificationMessage::Element(message), + on_click: None, + click_message: None, + } + } + + pub fn with_click_message(mut self, message: S) -> Self + where + S: Into>, + { + self.click_message = Some(message.into()); + self + } + + pub fn on_click(mut self, on_click: F) -> Self + where + F: 'static + Fn(&mut ViewContext), + { + self.on_click = Some(Arc::new(on_click)); + self + } + + pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext) { + cx.emit(MessageNotificationEvent::Dismiss); + } + } + + impl View for MessageNotification { + fn ui_name() -> &'static str { + "MessageNotification" + } + + fn render(&mut self, cx: &mut gpui2::ViewContext) -> gpui::AnyElement { + let theme = theme2::current(cx).clone(); + let theme = &theme.simple_message_notification; + + enum MessageNotificationTag {} + + let click_message = self.click_message.clone(); + let message = match &self.message { + NotificationMessage::Text(text) => { + Text::new(text.to_owned(), theme.message.text.clone()).into_any() + } + NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), + }; + let on_click = self.on_click.clone(); + let has_click_action = on_click.is_some(); + + Flex::column() + .with_child( + Flex::row() + .with_child( + message + .contained() + .with_style(theme.message.container) + .aligned() + .top() + .left() + .flex(1., true), + ) + .with_child( + MouseEventHandler::new::(0, cx, |state, _| { + let style = theme.dismiss_button.style_for(state); + Svg::new("icons/x.svg") + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .contained() + .with_style(style.container) + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + }) + .with_padding(Padding::uniform(5.)) + .on_click(MouseButton::Left, move |_, this, cx| { + this.dismiss(&Default::default(), cx); + }) + .with_cursor_style(CursorStyle::PointingHand) + .aligned() + .constrained() + .with_height(cx.font_cache().line_height(theme.message.text.font_size)) + .aligned() + .top() + .flex_float(), + ), + ) + .with_children({ + click_message + .map(|click_message| { + MouseEventHandler::new::( + 0, + cx, + |state, _| { + let style = theme.action_message.style_for(state); + + Flex::row() + .with_child( + Text::new(click_message, style.text.clone()) + .contained() + .with_style(style.container), + ) + .contained() + }, + ) + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(on_click) = on_click.as_ref() { + on_click(cx); + this.dismiss(&Default::default(), cx); + } + }) + // Since we're not using a proper overlay, we have to capture these extra events + .on_down(MouseButton::Left, |_, _, _| {}) + .on_up(MouseButton::Left, |_, _, _| {}) + .with_cursor_style(if has_click_action { + CursorStyle::PointingHand + } else { + CursorStyle::Arrow + }) + }) + .into_iter() + }) + .into_any() + } + } + + impl Notification for MessageNotification { + fn should_dismiss_notification_on_event(&self, event: &::Event) -> bool { + match event { + MessageNotificationEvent::Dismiss => true, + } + } + } +} + +pub trait NotifyResultExt { + type Ok; + + fn notify_err( + self, + workspace: &mut Workspace, + cx: &mut ViewContext, + ) -> Option; +} + +impl NotifyResultExt for Result +where + E: std::fmt::Debug, +{ + type Ok = T; + + fn notify_err(self, workspace: &mut Workspace, cx: &mut ViewContext) -> Option { + match self { + Ok(value) => Some(value), + Err(err) => { + workspace.show_notification(0, cx, |cx| { + cx.add_view(|_cx| { + simple_message_notification::MessageNotification::new(format!( + "Error: {:?}", + err, + )) + }) + }); + + None + } + } + } +} diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs new file mode 100644 index 0000000000..e885408221 --- /dev/null +++ b/crates/workspace2/src/pane.rs @@ -0,0 +1,2742 @@ +mod dragged_item_receiver; + +use super::{ItemHandle, SplitDirection}; +pub use crate::toolbar::Toolbar; +use crate::{ + item::{ItemSettings, WeakItemHandle}, + notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom, + Workspace, WorkspaceSettings, +}; +use anyhow::Result; +use collections::{HashMap, HashSet, VecDeque}; +// use context_menu::{ContextMenu, ContextMenuItem}; + +use dragged_item_receiver::dragged_item_receiver; +use fs2::repository::GitFileStatus; +use futures::StreamExt; +use gpui2::{ + actions, + elements::*, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + impl_actions, + keymap_matcher::KeymapContext, + platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel}, + Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, + ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, +}; +use project2::{Project, ProjectEntryId, ProjectPath}; +use serde::Deserialize; +use std::{ + any::Any, + cell::RefCell, + cmp, mem, + path::{Path, PathBuf}, + rc::Rc, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; +use theme2::{Theme, ThemeSettings}; +use util::truncate_and_remove_front; + +#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum SaveIntent { + /// write all files (even if unchanged) + /// prompt before overwriting on-disk changes + Save, + /// write any files that have local changes + /// prompt before overwriting on-disk changes + SaveAll, + /// always prompt for a new path + SaveAs, + /// prompt "you have unsaved changes" before writing + Close, + /// write all dirty files, don't prompt on conflict + Overwrite, + /// skip all save-related behavior + Skip, +} + +#[derive(Clone, Deserialize, PartialEq)] +pub struct ActivateItem(pub usize); + +#[derive(Clone, PartialEq)] +pub struct CloseItemById { + pub item_id: usize, + pub pane: WeakViewHandle, +} + +#[derive(Clone, PartialEq)] +pub struct CloseItemsToTheLeftById { + pub item_id: usize, + pub pane: WeakViewHandle, +} + +#[derive(Clone, PartialEq)] +pub struct CloseItemsToTheRightById { + pub item_id: usize, + pub pane: WeakViewHandle, +} + +#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct CloseActiveItem { + pub save_intent: Option, +} + +#[derive(Clone, PartialEq, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CloseAllItems { + pub save_intent: Option, +} + +actions!( + pane, + [ + ActivatePrevItem, + ActivateNextItem, + ActivateLastItem, + CloseInactiveItems, + CloseCleanItems, + CloseItemsToTheLeft, + CloseItemsToTheRight, + GoBack, + GoForward, + ReopenClosedItem, + SplitLeft, + SplitUp, + SplitRight, + SplitDown, + ] +); + +impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); + +const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; + +pub 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| { + 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 { + AddItem { item: Box }, + ActivateItem { local: bool }, + Remove, + RemoveItem { item_id: usize }, + Split(SplitDirection), + ChangeItemTitle, + Focus, + ZoomIn, + ZoomOut, +} + +pub struct Pane { + items: Vec>, + activation_history: Vec, + zoomed: bool, + active_item_index: usize, + last_focused_view_by_item: HashMap, + autoscroll: bool, + nav_history: NavHistory, + toolbar: ViewHandle, + tab_bar_context_menu: TabBarContextMenu, + tab_context_menu: ViewHandle, + _background_actions: BackgroundActions, + workspace: WeakViewHandle, + project: ModelHandle, + has_focus: bool, + can_drop: Rc, &WindowContext) -> bool>, + can_split: bool, + render_tab_bar_buttons: Rc) -> AnyElement>, +} + +pub struct ItemNavHistory { + history: NavHistory, + item: Rc, +} + +#[derive(Clone)] +pub struct NavHistory(Rc>); + +struct NavHistoryState { + mode: NavigationMode, + backward_stack: VecDeque, + forward_stack: VecDeque, + closed_stack: VecDeque, + paths_by_item: HashMap)>, + pane: WeakViewHandle, + next_timestamp: Arc, +} + +#[derive(Copy, Clone)] +pub enum NavigationMode { + Normal, + GoingBack, + GoingForward, + ClosingItem, + ReopeningClosedItem, + Disabled, +} + +impl Default for NavigationMode { + fn default() -> Self { + Self::Normal + } +} + +pub struct NavigationEntry { + pub item: Rc, + pub data: Option>, + pub timestamp: usize, +} + +pub struct DraggedItem { + pub handle: Box, + pub pane: WeakViewHandle, +} + +pub enum ReorderBehavior { + None, + MoveAfterActive, + MoveToIndex(usize), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum TabBarContextMenuKind { + New, + Split, +} + +struct TabBarContextMenu { + kind: TabBarContextMenuKind, + handle: ViewHandle, +} + +impl TabBarContextMenu { + fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { + if self.kind == kind { + return Some(self.handle.clone()); + } + None + } +} + +#[allow(clippy::too_many_arguments)] +fn nav_button)>( + svg_path: &'static str, + style: theme2::Interactive, + nav_button_height: f32, + tooltip_style: TooltipStyle, + enabled: bool, + on_click: F, + tooltip_action: A, + action_name: &str, + cx: &mut ViewContext, +) -> AnyElement { + MouseEventHandler::new::(0, cx, |state, _| { + let style = if enabled { + style.style_for(state) + } else { + style.disabled_style() + }; + Svg::new(svg_path) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .contained() + .with_style(style.container) + .constrained() + .with_width(style.button_width) + .with_height(nav_button_height) + .aligned() + .top() + }) + .with_cursor_style(if enabled { + CursorStyle::PointingHand + } else { + CursorStyle::default() + }) + .on_click(MouseButton::Left, move |_, toolbar, cx| { + on_click(toolbar, cx) + }) + .with_tooltip::( + 0, + action_name.to_string(), + Some(Box::new(tooltip_action)), + tooltip_style, + cx, + ) + .contained() + .into_any_named("nav button") +} + +impl Pane { + pub fn new( + workspace: WeakViewHandle, + project: ModelHandle, + background_actions: BackgroundActions, + next_timestamp: Arc, + cx: &mut ViewContext, + ) -> Self { + let pane_view_id = cx.view_id(); + let handle = cx.weak_handle(); + let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); + context_menu.update(cx, |menu, _| { + menu.set_position_mode(OverlayPositionMode::Local) + }); + + Self { + items: Vec::new(), + activation_history: Vec::new(), + zoomed: false, + active_item_index: 0, + last_focused_view_by_item: Default::default(), + autoscroll: false, + nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState { + mode: NavigationMode::Normal, + backward_stack: Default::default(), + forward_stack: Default::default(), + closed_stack: Default::default(), + paths_by_item: Default::default(), + pane: handle.clone(), + next_timestamp, + }))), + toolbar: cx.add_view(|_| Toolbar::new()), + tab_bar_context_menu: TabBarContextMenu { + kind: TabBarContextMenuKind::New, + handle: context_menu, + }, + tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), + _background_actions: background_actions, + workspace, + project, + has_focus: false, + can_drop: Rc::new(|_, _| true), + can_split: true, + render_tab_bar_buttons: Rc::new(move |pane, cx| { + Flex::row() + // New menu + .with_child(Self::render_tab_bar_button( + 0, + "icons/plus.svg", + false, + Some(("New...".into(), None)), + cx, + |pane, cx| pane.deploy_new_menu(cx), + |pane, cx| { + pane.tab_bar_context_menu + .handle + .update(cx, |menu, _| menu.delay_cancel()) + }, + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::New), + )) + .with_child(Self::render_tab_bar_button( + 1, + "icons/split.svg", + false, + Some(("Split Pane".into(), None)), + cx, + |pane, cx| pane.deploy_split_menu(cx), + |pane, cx| { + pane.tab_bar_context_menu + .handle + .update(cx, |menu, _| menu.delay_cancel()) + }, + pane.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), + )) + .with_child({ + let icon_path; + let tooltip_label; + if pane.is_zoomed() { + icon_path = "icons/minimize.svg"; + tooltip_label = "Zoom In"; + } else { + icon_path = "icons/maximize.svg"; + tooltip_label = "Zoom In"; + } + + Pane::render_tab_bar_button( + 2, + icon_path, + pane.is_zoomed(), + Some((tooltip_label, Some(Box::new(ToggleZoom)))), + cx, + move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + move |_, _| {}, + None, + ) + }) + .into_any() + }), + } + } + + pub(crate) fn workspace(&self) -> &WeakViewHandle { + &self.workspace + } + + pub fn has_focus(&self) -> bool { + self.has_focus + } + + pub fn active_item_index(&self) -> usize { + self.active_item_index + } + + pub fn on_can_drop(&mut self, can_drop: F) + where + F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + { + self.can_drop = Rc::new(can_drop); + } + + pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + self.can_split = can_split; + cx.notify(); + } + + pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { + self.toolbar.update(cx, |toolbar, cx| { + toolbar.set_can_navigate(can_navigate, cx); + }); + cx.notify(); + } + + pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + where + F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + { + self.render_tab_bar_buttons = Rc::new(render); + cx.notify(); + } + + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { + ItemNavHistory { + history: self.nav_history.clone(), + item: Rc::new(item.downgrade()), + } + } + + pub fn nav_history(&self) -> &NavHistory { + &self.nav_history + } + + pub fn nav_history_mut(&mut self) -> &mut NavHistory { + &mut self.nav_history + } + + pub fn disable_history(&mut self) { + self.nav_history.disable(); + } + + pub fn enable_history(&mut self) { + self.nav_history.enable(); + } + + pub fn can_navigate_backward(&self) -> bool { + !self.nav_history.0.borrow().backward_stack.is_empty() + } + + pub fn can_navigate_forward(&self) -> bool { + !self.nav_history.0.borrow().forward_stack.is_empty() + } + + fn history_updated(&mut self, cx: &mut ViewContext) { + self.toolbar.update(cx, |_, cx| cx.notify()); + } + + pub(crate) fn open_item( + &mut self, + project_entry_id: ProjectEntryId, + focus_item: bool, + cx: &mut ViewContext, + build_item: impl FnOnce(&mut ViewContext) -> Box, + ) -> Box { + let mut existing_item = None; + for (index, item) in self.items.iter().enumerate() { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id] + { + let item = item.boxed_clone(); + existing_item = Some((index, item)); + break; + } + } + + if let Some((index, existing_item)) = existing_item { + self.activate_item(index, focus_item, focus_item, cx); + existing_item + } else { + let new_item = build_item(cx); + self.add_item(new_item.clone(), true, focus_item, None, cx); + new_item + } + } + + pub fn add_item( + &mut self, + item: Box, + activate_pane: bool, + focus_item: bool, + destination_index: Option, + cx: &mut ViewContext, + ) { + if item.is_singleton(cx) { + if let Some(&entry_id) = item.project_entry_ids(cx).get(0) { + let project = self.project.read(cx); + if let Some(project_path) = project.path_for_entry(entry_id, cx) { + let abs_path = project.absolute_path(&project_path, cx); + self.nav_history + .0 + .borrow_mut() + .paths_by_item + .insert(item.id(), (project_path, abs_path)); + } + } + } + // If no destination index is specified, add or move the item after the active item. + let mut insertion_index = { + cmp::min( + if let Some(destination_index) = destination_index { + destination_index + } else { + self.active_item_index + 1 + }, + self.items.len(), + ) + }; + + // Does the item already exist? + let project_entry_id = if item.is_singleton(cx) { + item.project_entry_ids(cx).get(0).copied() + } else { + None + }; + + let existing_item_index = self.items.iter().position(|existing_item| { + if existing_item.id() == item.id() { + true + } else if existing_item.is_singleton(cx) { + existing_item + .project_entry_ids(cx) + .get(0) + .map_or(false, |existing_entry_id| { + Some(existing_entry_id) == project_entry_id.as_ref() + }) + } else { + false + } + }); + + if let Some(existing_item_index) = existing_item_index { + // If the item already exists, move it to the desired destination and activate it + + if existing_item_index != insertion_index { + let existing_item_is_active = existing_item_index == self.active_item_index; + + // If the caller didn't specify a destination and the added item is already + // the active one, don't move it + if existing_item_is_active && destination_index.is_none() { + insertion_index = existing_item_index; + } else { + self.items.remove(existing_item_index); + if existing_item_index < self.active_item_index { + self.active_item_index -= 1; + } + insertion_index = insertion_index.min(self.items.len()); + + self.items.insert(insertion_index, item.clone()); + + if existing_item_is_active { + self.active_item_index = insertion_index; + } else if insertion_index <= self.active_item_index { + self.active_item_index += 1; + } + } + + cx.notify(); + } + + self.activate_item(insertion_index, activate_pane, focus_item, cx); + } else { + self.items.insert(insertion_index, item.clone()); + if insertion_index <= self.active_item_index { + self.active_item_index += 1; + } + + self.activate_item(insertion_index, activate_pane, focus_item, cx); + cx.notify(); + } + + cx.emit(Event::AddItem { item }); + } + + pub fn items_len(&self) -> usize { + self.items.len() + } + + pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { + self.items.iter() + } + + pub fn items_of_type(&self) -> impl '_ + Iterator> { + self.items + .iter() + .filter_map(|item| item.as_any().clone().downcast()) + } + + pub fn active_item(&self) -> Option> { + self.items.get(self.active_item_index).cloned() + } + + pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { + self.items + .get(self.active_item_index)? + .pixel_position_of_cursor(cx) + } + + pub fn item_for_entry( + &self, + entry_id: ProjectEntryId, + cx: &AppContext, + ) -> Option> { + self.items.iter().find_map(|item| { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + Some(item.boxed_clone()) + } else { + None + } + }) + } + + pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { + self.items.iter().position(|i| i.id() == item.id()) + } + + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // Potentially warn the user of the new keybinding + let workspace_handle = self.workspace().clone(); + cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) + .detach(); + + if self.zoomed { + cx.emit(Event::ZoomOut); + } else if !self.items.is_empty() { + if !self.has_focus { + cx.focus_self(); + } + cx.emit(Event::ZoomIn); + } + } + + pub fn activate_item( + &mut self, + index: usize, + activate_pane: bool, + focus_item: bool, + cx: &mut ViewContext, + ) { + use NavigationMode::{GoingBack, GoingForward}; + + if index < self.items.len() { + let prev_active_item_ix = mem::replace(&mut self.active_item_index, index); + if prev_active_item_ix != self.active_item_index + || matches!(self.nav_history.mode(), GoingBack | GoingForward) + { + if let Some(prev_item) = self.items.get(prev_active_item_ix) { + prev_item.deactivated(cx); + } + + cx.emit(Event::ActivateItem { + local: activate_pane, + }); + } + + if let Some(newly_active_item) = self.items.get(index) { + self.activation_history + .retain(|&previously_active_item_id| { + previously_active_item_id != newly_active_item.id() + }); + self.activation_history.push(newly_active_item.id()); + } + + self.update_toolbar(cx); + + if focus_item { + self.focus_active_item(cx); + } + + self.autoscroll = true; + cx.notify(); + } + } + + pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { + let mut index = self.active_item_index; + if index > 0 { + index -= 1; + } else if !self.items.is_empty() { + index = self.items.len() - 1; + } + self.activate_item(index, activate_pane, activate_pane, cx); + } + + pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { + let mut index = self.active_item_index; + if index + 1 < self.items.len() { + index += 1; + } else { + index = 0; + } + self.activate_item(index, activate_pane, activate_pane, cx); + } + + pub fn close_active_item( + &mut self, + action: &CloseActiveItem, + cx: &mut ViewContext, + ) -> Option>> { + if self.items.is_empty() { + return None; + } + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_item_by_id( + active_item_id, + action.save_intent.unwrap_or(SaveIntent::Close), + cx, + )) + } + + pub fn close_item_by_id( + &mut self, + item_id_to_close: usize, + save_intent: SaveIntent, + cx: &mut ViewContext, + ) -> Task> { + self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + } + + pub fn close_inactive_items( + &mut self, + _: &CloseInactiveItems, + cx: &mut ViewContext, + ) -> Option>> { + if self.items.is_empty() { + return None; + } + + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + item_id != active_item_id + })) + } + + pub fn close_clean_items( + &mut self, + _: &CloseCleanItems, + cx: &mut ViewContext, + ) -> Option>> { + let item_ids: Vec<_> = self + .items() + .filter(|item| !item.is_dirty(cx)) + .map(|item| item.id()) + .collect(); + Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + item_ids.contains(&item_id) + })) + } + + pub fn close_items_to_the_left( + &mut self, + _: &CloseItemsToTheLeft, + cx: &mut ViewContext, + ) -> Option>> { + if self.items.is_empty() { + return None; + } + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_left_by_id(active_item_id, cx)) + } + + pub fn close_items_to_the_left_by_id( + &mut self, + item_id: usize, + cx: &mut ViewContext, + ) -> Task> { + let item_ids: Vec<_> = self + .items() + .take_while(|item| item.id() != item_id) + .map(|item| item.id()) + .collect(); + self.close_items(cx, SaveIntent::Close, move |item_id| { + item_ids.contains(&item_id) + }) + } + + pub fn close_items_to_the_right( + &mut self, + _: &CloseItemsToTheRight, + cx: &mut ViewContext, + ) -> Option>> { + if self.items.is_empty() { + return None; + } + let active_item_id = self.items[self.active_item_index].id(); + Some(self.close_items_to_the_right_by_id(active_item_id, cx)) + } + + pub fn close_items_to_the_right_by_id( + &mut self, + item_id: usize, + cx: &mut ViewContext, + ) -> Task> { + let item_ids: Vec<_> = self + .items() + .rev() + .take_while(|item| item.id() != item_id) + .map(|item| item.id()) + .collect(); + self.close_items(cx, SaveIntent::Close, move |item_id| { + item_ids.contains(&item_id) + }) + } + + pub fn close_all_items( + &mut self, + action: &CloseAllItems, + cx: &mut ViewContext, + ) -> Option>> { + if self.items.is_empty() { + return None; + } + + Some( + self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { + true + }), + ) + } + + pub(super) fn file_names_for_prompt( + items: &mut dyn Iterator>, + all_dirty_items: usize, + cx: &AppContext, + ) -> String { + /// Quantity of item paths displayed in prompt prior to cutoff.. + const FILE_NAMES_CUTOFF_POINT: usize = 10; + let mut file_names: Vec<_> = items + .filter_map(|item| { + item.project_path(cx).and_then(|project_path| { + project_path + .path + .file_name() + .and_then(|name| name.to_str().map(ToOwned::to_owned)) + }) + }) + .take(FILE_NAMES_CUTOFF_POINT) + .collect(); + let should_display_followup_text = + all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; + if should_display_followup_text { + let not_shown_files = all_dirty_items - file_names.len(); + if not_shown_files == 1 { + file_names.push(".. 1 file not shown".into()); + } else { + file_names.push(format!(".. {} files not shown", not_shown_files).into()); + } + } + let file_names = file_names.join("\n"); + format!( + "Do you want to save changes to the following {} files?\n{file_names}", + all_dirty_items + ) + } + + pub fn close_items( + &mut self, + cx: &mut ViewContext, + mut save_intent: SaveIntent, + should_close: impl 'static + Fn(usize) -> bool, + ) -> Task> { + // Find the items to close. + let mut items_to_close = Vec::new(); + let mut dirty_items = Vec::new(); + for item in &self.items { + if should_close(item.id()) { + items_to_close.push(item.boxed_clone()); + if item.is_dirty(cx) { + dirty_items.push(item.boxed_clone()); + } + } + } + + // If a buffer is open both in a singleton editor and in a multibuffer, make sure + // to focus the singleton buffer when prompting to save that buffer, as opposed + // to focusing the multibuffer, because this gives the user a more clear idea + // of what content they would be saving. + items_to_close.sort_by_key(|item| !item.is_singleton(cx)); + + let workspace = self.workspace.clone(); + cx.spawn(|pane, mut cx| async move { + if save_intent == SaveIntent::Close && dirty_items.len() > 1 { + let mut answer = pane.update(&mut cx, |_, cx| { + let prompt = + Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); + cx.prompt( + PromptLevel::Warning, + &prompt, + &["Save all", "Discard all", "Cancel"], + ) + })?; + match answer.next().await { + Some(0) => save_intent = SaveIntent::SaveAll, + Some(1) => save_intent = SaveIntent::Skip, + _ => {} + } + } + let mut saved_project_items_ids = HashSet::default(); + for item in items_to_close.clone() { + // Find the item's current index and its set of project item models. Avoid + // storing these in advance, in case they have changed since this task + // was started. + let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { + (pane.index_for_item(&*item), item.project_item_model_ids(cx)) + })?; + let item_ix = if let Some(ix) = item_ix { + ix + } else { + continue; + }; + + // Check if this view has any project items that are not open anywhere else + // in the workspace, AND that the user has not already been prompted to save. + // If there are any such project entries, prompt the user to save this item. + let project = workspace.read_with(&cx, |workspace, cx| { + for item in workspace.items(cx) { + if !items_to_close + .iter() + .any(|item_to_close| item_to_close.id() == item.id()) + { + let other_project_item_ids = item.project_item_model_ids(cx); + project_item_ids.retain(|id| !other_project_item_ids.contains(id)); + } + } + workspace.project().clone() + })?; + let should_save = project_item_ids + .iter() + .any(|id| saved_project_items_ids.insert(*id)); + + if should_save + && !Self::save_item( + project.clone(), + &pane, + item_ix, + &*item, + save_intent, + &mut cx, + ) + .await? + { + break; + } + + // Remove the item from the pane. + pane.update(&mut cx, |pane, cx| { + if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { + pane.remove_item(item_ix, false, cx); + } + })?; + } + + pane.update(&mut cx, |_, cx| cx.notify())?; + Ok(()) + }) + } + + pub fn remove_item( + &mut self, + item_index: usize, + activate_pane: bool, + cx: &mut ViewContext, + ) { + self.activation_history + .retain(|&history_entry| history_entry != self.items[item_index].id()); + + if item_index == self.active_item_index { + let index_to_activate = self + .activation_history + .pop() + .and_then(|last_activated_item| { + self.items.iter().enumerate().find_map(|(index, item)| { + (item.id() == last_activated_item).then_some(index) + }) + }) + // We didn't have a valid activation history entry, so fallback + // to activating the item to the left + .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); + + let should_activate = activate_pane || self.has_focus; + self.activate_item(index_to_activate, should_activate, should_activate, cx); + } + + let item = self.items.remove(item_index); + + cx.emit(Event::RemoveItem { item_id: item.id() }); + if self.items.is_empty() { + item.deactivated(cx); + self.update_toolbar(cx); + cx.emit(Event::Remove); + } + + if item_index < self.active_item_index { + self.active_item_index -= 1; + } + + self.nav_history.set_mode(NavigationMode::ClosingItem); + item.deactivated(cx); + self.nav_history.set_mode(NavigationMode::Normal); + + if let Some(path) = item.project_path(cx) { + let abs_path = self + .nav_history + .0 + .borrow() + .paths_by_item + .get(&item.id()) + .and_then(|(_, abs_path)| abs_path.clone()); + + self.nav_history + .0 + .borrow_mut() + .paths_by_item + .insert(item.id(), (path, abs_path)); + } else { + self.nav_history + .0 + .borrow_mut() + .paths_by_item + .remove(&item.id()); + } + + if self.items.is_empty() && self.zoomed { + cx.emit(Event::ZoomOut); + } + + cx.notify(); + } + + pub async fn save_item( + project: ModelHandle, + pane: &WeakViewHandle, + item_ix: usize, + item: &dyn ItemHandle, + save_intent: SaveIntent, + cx: &mut AsyncAppContext, + ) -> Result { + const CONFLICT_MESSAGE: &str = + "This file has changed on disk since you started editing it. Do you want to overwrite it?"; + + if save_intent == SaveIntent::Skip { + return Ok(true); + } + + let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| { + ( + item.has_conflict(cx), + item.is_dirty(cx), + item.can_save(cx), + item.is_singleton(cx), + ) + }); + + // when saving a single buffer, we ignore whether or not it's dirty. + if save_intent == SaveIntent::Save { + is_dirty = true; + } + + if save_intent == SaveIntent::SaveAs { + is_dirty = true; + has_conflict = false; + can_save = false; + } + + if save_intent == SaveIntent::Overwrite { + has_conflict = false; + } + + if has_conflict && can_save { + let mut answer = pane.update(cx, |pane, cx| { + pane.activate_item(item_ix, true, true, cx); + cx.prompt( + PromptLevel::Warning, + CONFLICT_MESSAGE, + &["Overwrite", "Discard", "Cancel"], + ) + })?; + match answer.next().await { + Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, + _ => return Ok(false), + } + } else if is_dirty && (can_save || can_save_as) { + if save_intent == SaveIntent::Close { + let will_autosave = cx.read(|cx| { + matches!( + settings::get::(cx).autosave, + AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange + ) && Self::can_autosave_item(&*item, cx) + }); + if !will_autosave { + let mut answer = pane.update(cx, |pane, cx| { + pane.activate_item(item_ix, true, true, cx); + let prompt = dirty_message_for(item.project_path(cx)); + cx.prompt( + PromptLevel::Warning, + &prompt, + &["Save", "Don't Save", "Cancel"], + ) + })?; + match answer.next().await { + Some(0) => {} + Some(1) => return Ok(true), // Don't save his file + _ => return Ok(false), // Cancel + } + } + } + + if can_save { + pane.update(cx, |_, cx| item.save(project, cx))?.await?; + } else if can_save_as { + let start_abs_path = project + .read_with(cx, |project, cx| { + let worktree = project.visible_worktrees(cx).next()?; + Some(worktree.read(cx).as_local()?.abs_path().to_path_buf()) + }) + .unwrap_or_else(|| Path::new("").into()); + + let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path)); + if let Some(abs_path) = abs_path.next().await.flatten() { + pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))? + .await?; + } else { + return Ok(false); + } + } + } + Ok(true) + } + + fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool { + let is_deleted = item.project_entry_ids(cx).is_empty(); + item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted + } + + pub fn autosave_item( + item: &dyn ItemHandle, + project: ModelHandle, + cx: &mut WindowContext, + ) -> Task> { + if Self::can_autosave_item(item, cx) { + item.save(project, cx) + } else { + Task::ready(Ok(())) + } + } + + pub fn focus_active_item(&mut self, cx: &mut ViewContext) { + if let Some(active_item) = self.active_item() { + cx.focus(active_item.as_any()); + } + } + + pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { + cx.emit(Event::Split(direction)); + } + + fn deploy_split_menu(&mut self, cx: &mut ViewContext) { + self.tab_bar_context_menu.handle.update(cx, |menu, cx| { + menu.toggle( + Default::default(), + AnchorCorner::TopRight, + vec![ + ContextMenuItem::action("Split Right", SplitRight), + ContextMenuItem::action("Split Left", SplitLeft), + ContextMenuItem::action("Split Up", SplitUp), + ContextMenuItem::action("Split Down", SplitDown), + ], + cx, + ); + }); + + self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; + } + + fn deploy_new_menu(&mut self, cx: &mut ViewContext) { + self.tab_bar_context_menu.handle.update(cx, |menu, cx| { + menu.toggle( + Default::default(), + AnchorCorner::TopRight, + vec![ + ContextMenuItem::action("New File", NewFile), + ContextMenuItem::action("New Terminal", NewCenterTerminal), + ContextMenuItem::action("New Search", NewSearch), + ], + cx, + ); + }); + + self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; + } + + fn deploy_tab_context_menu( + &mut self, + position: Vector2F, + target_item_id: usize, + cx: &mut ViewContext, + ) { + let active_item_id = self.items[self.active_item_index].id(); + let is_active_item = target_item_id == active_item_id; + let target_pane = cx.weak_handle(); + + // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab + + self.tab_context_menu.update(cx, |menu, cx| { + menu.show( + position, + AnchorCorner::TopLeft, + if is_active_item { + vec![ + ContextMenuItem::action( + "Close Active Item", + CloseActiveItem { save_intent: None }, + ), + ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + ContextMenuItem::action("Close Clean Items", CloseCleanItems), + ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), + ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), + ContextMenuItem::action( + "Close All Items", + CloseAllItems { save_intent: None }, + ), + ] + } else { + // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. + vec![ + ContextMenuItem::handler("Close Inactive Item", { + let pane = target_pane.clone(); + move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_item_by_id( + target_item_id, + SaveIntent::Close, + cx, + ) + .detach_and_log_err(cx); + }) + } + } + }), + ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + ContextMenuItem::action("Close Clean Items", CloseCleanItems), + ContextMenuItem::handler("Close Items To The Left", { + let pane = target_pane.clone(); + move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left_by_id(target_item_id, cx) + .detach_and_log_err(cx); + }) + } + } + }), + ContextMenuItem::handler("Close Items To The Right", { + let pane = target_pane.clone(); + move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right_by_id(target_item_id, cx) + .detach_and_log_err(cx); + }) + } + } + }), + ContextMenuItem::action( + "Close All Items", + CloseAllItems { save_intent: None }, + ), + ] + }, + cx, + ); + }); + } + + pub fn toolbar(&self) -> &ViewHandle { + &self.toolbar + } + + pub fn handle_deleted_project_item( + &mut self, + entry_id: ProjectEntryId, + cx: &mut ViewContext, + ) -> Option<()> { + let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + Some((i, item.id())) + } else { + None + } + })?; + + self.remove_item(item_index_to_delete, false, cx); + self.nav_history.remove_item(item_id); + + Some(()) + } + + fn update_toolbar(&mut self, cx: &mut ViewContext) { + let active_item = self + .items + .get(self.active_item_index) + .map(|item| item.as_ref()); + self.toolbar.update(cx, |toolbar, cx| { + toolbar.set_active_item(active_item, cx); + }); + } + + fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { + let theme = theme::current(cx).clone(); + + let pane = cx.handle().downgrade(); + let autoscroll = if mem::take(&mut self.autoscroll) { + Some(self.active_item_index) + } else { + None + }; + + let pane_active = self.has_focus; + + enum Tabs {} + let mut row = Flex::row().scrollable::(1, autoscroll, cx); + for (ix, (item, detail)) in self + .items + .iter() + .cloned() + .zip(self.tab_details(cx)) + .enumerate() + { + let git_status = item + .project_path(cx) + .and_then(|path| self.project.read(cx).entry_for_path(&path, cx)) + .and_then(|entry| entry.git_status()); + + let detail = if detail == 0 { None } else { Some(detail) }; + let tab_active = ix == self.active_item_index; + + row.add_child({ + enum TabDragReceiver {} + let mut receiver = + dragged_item_receiver::(self, ix, ix, true, None, cx, { + let item = item.clone(); + let pane = pane.clone(); + let detail = detail.clone(); + + let theme = theme::current(cx).clone(); + let mut tooltip_theme = theme.tooltip.clone(); + tooltip_theme.max_text_width = None; + let tab_tooltip_text = + item.tab_tooltip_text(cx).map(|text| text.into_owned()); + + let mut tab_style = theme + .workspace + .tab_bar + .tab_style(pane_active, tab_active) + .clone(); + let should_show_status = settings::get::(cx).git_status; + if should_show_status && git_status != None { + tab_style.label.text.color = match git_status.unwrap() { + GitFileStatus::Added => tab_style.git.inserted, + GitFileStatus::Modified => tab_style.git.modified, + GitFileStatus::Conflict => tab_style.git.conflict, + }; + } + + move |mouse_state, cx| { + let hovered = mouse_state.hovered(); + + enum Tab {} + let mouse_event_handler = + MouseEventHandler::new::(ix, cx, |_, cx| { + Self::render_tab( + &item, + pane.clone(), + ix == 0, + detail, + hovered, + &tab_style, + cx, + ) + }) + .on_down(MouseButton::Left, move |_, this, cx| { + this.activate_item(ix, true, true, cx); + }) + .on_click(MouseButton::Middle, { + let item_id = item.id(); + move |_, pane, cx| { + pane.close_item_by_id(item_id, SaveIntent::Close, cx) + .detach_and_log_err(cx); + } + }) + .on_down( + MouseButton::Right, + move |event, pane, cx| { + pane.deploy_tab_context_menu(event.position, item.id(), cx); + }, + ); + + if let Some(tab_tooltip_text) = tab_tooltip_text { + mouse_event_handler + .with_tooltip::( + ix, + tab_tooltip_text, + None, + tooltip_theme, + cx, + ) + .into_any() + } else { + mouse_event_handler.into_any() + } + } + }); + + if !pane_active || !tab_active { + receiver = receiver.with_cursor_style(CursorStyle::PointingHand); + } + + receiver.as_draggable( + DraggedItem { + handle: item, + pane: pane.clone(), + }, + { + let theme = theme::current(cx).clone(); + + let detail = detail.clone(); + move |_, dragged_item: &DraggedItem, cx: &mut ViewContext| { + let tab_style = &theme.workspace.tab_bar.dragged_tab; + Self::render_dragged_tab( + &dragged_item.handle, + dragged_item.pane.clone(), + false, + detail, + false, + &tab_style, + cx, + ) + } + }, + ) + }) + } + + // Use the inactive tab style along with the current pane's active status to decide how to render + // the filler + let filler_index = self.items.len(); + let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); + enum Filler {} + row.add_child( + dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { + Empty::new() + .contained() + .with_style(filler_style.container) + .with_border(filler_style.container.border) + }) + .flex(1., true) + .into_any_named("filler"), + ); + + row + } + + fn tab_details(&self, cx: &AppContext) -> Vec { + let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); + + let mut tab_descriptions = HashMap::default(); + let mut done = false; + while !done { + done = true; + + // Store item indices by their tab description. + for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { + if let Some(description) = item.tab_description(*detail, cx) { + if *detail == 0 + || Some(&description) != item.tab_description(detail - 1, cx).as_ref() + { + tab_descriptions + .entry(description) + .or_insert(Vec::new()) + .push(ix); + } + } + } + + // If two or more items have the same tab description, increase their level + // of detail and try again. + for (_, item_ixs) in tab_descriptions.drain() { + if item_ixs.len() > 1 { + done = false; + for ix in item_ixs { + tab_details[ix] += 1; + } + } + } + } + + tab_details + } + + fn render_tab( + item: &Box, + pane: WeakViewHandle, + first: bool, + detail: Option, + hovered: bool, + tab_style: &theme::Tab, + cx: &mut ViewContext, + ) -> AnyElement { + let title = item.tab_content(detail, &tab_style, cx); + Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) + } + + fn render_dragged_tab( + item: &Box, + pane: WeakViewHandle, + first: bool, + detail: Option, + hovered: bool, + tab_style: &theme::Tab, + cx: &mut ViewContext, + ) -> AnyElement { + let title = item.dragged_tab_content(detail, &tab_style, cx); + Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) + } + + fn render_tab_with_title( + title: AnyElement, + item: &Box, + pane: WeakViewHandle, + first: bool, + hovered: bool, + tab_style: &theme::Tab, + cx: &mut ViewContext, + ) -> AnyElement { + let mut container = tab_style.container.clone(); + if first { + container.border.left = false; + } + + let buffer_jewel_element = { + let diameter = 7.0; + let icon_color = if item.has_conflict(cx) { + Some(tab_style.icon_conflict) + } else if item.is_dirty(cx) { + Some(tab_style.icon_dirty) + } else { + None + }; + + Canvas::new(move |bounds, _, _, cx| { + if let Some(color) = icon_color { + let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); + cx.scene().push_quad(Quad { + bounds: square, + background: Some(color), + border: Default::default(), + corner_radii: (diameter / 2.).into(), + }); + } + }) + .constrained() + .with_width(diameter) + .with_height(diameter) + .aligned() + }; + + let title_element = title.aligned().contained().with_style(ContainerStyle { + margin: Margin { + left: tab_style.spacing, + right: tab_style.spacing, + ..Default::default() + }, + ..Default::default() + }); + + let close_element = if hovered { + let item_id = item.id(); + enum TabCloseButton {} + let icon = Svg::new("icons/x.svg"); + MouseEventHandler::new::(item_id, cx, |mouse_state, _| { + if mouse_state.hovered() { + icon.with_color(tab_style.icon_close_active) + } else { + icon.with_color(tab_style.icon_close) + } + }) + .with_padding(Padding::uniform(4.)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let pane = pane.clone(); + move |_, _, cx| { + let pane = pane.clone(); + cx.window_context().defer(move |cx| { + if let Some(pane) = pane.upgrade(cx) { + pane.update(cx, |pane, cx| { + pane.close_item_by_id(item_id, SaveIntent::Close, cx) + .detach_and_log_err(cx); + }); + } + }); + } + }) + .into_any_named("close-tab-icon") + .constrained() + } else { + Empty::new().constrained() + } + .with_width(tab_style.close_icon_width) + .aligned(); + + let close_right = settings::get::(cx).close_position.right(); + + if close_right { + Flex::row() + .with_child(buffer_jewel_element) + .with_child(title_element) + .with_child(close_element) + } else { + Flex::row() + .with_child(close_element) + .with_child(title_element) + .with_child(buffer_jewel_element) + } + .contained() + .with_style(container) + .constrained() + .with_height(tab_style.height) + .into_any() + } + + pub fn render_tab_bar_button< + F1: 'static + Fn(&mut Pane, &mut EventContext), + F2: 'static + Fn(&mut Pane, &mut EventContext), + >( + index: usize, + icon: &'static str, + is_active: bool, + tooltip: Option<(&'static str, Option>)>, + cx: &mut ViewContext, + on_click: F1, + on_down: F2, + context_menu: Option>, + ) -> AnyElement { + enum TabBarButton {} + + let mut button = MouseEventHandler::new::(index, cx, |mouse_state, cx| { + let theme = &settings::get::(cx).theme.workspace.tab_bar; + let style = theme.pane_button.in_state(is_active).style_for(mouse_state); + Svg::new(icon) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) + .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) + .into_any(); + if let Some((tooltip, action)) = tooltip { + let tooltip_style = settings::get::(cx).theme.tooltip.clone(); + button = button + .with_tooltip::(index, tooltip, action, tooltip_style, cx) + .into_any(); + } + + Stack::new() + .with_child(button) + .with_children( + context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), + ) + .flex(1., false) + .into_any_named("tab bar button") + } + + fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { + let background = theme.workspace.background; + Empty::new() + .contained() + .with_background_color(background) + .into_any() + } + + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.zoomed = zoomed; + cx.notify(); + } + + pub fn is_zoomed(&self) -> bool { + self.zoomed + } +} + +impl Entity for Pane { + type Event = Event; +} + +impl View for Pane { + fn ui_name() -> &'static str { + "Pane" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + enum MouseNavigationHandler {} + + MouseEventHandler::new::(0, cx, |_, cx| { + let active_item_index = self.active_item_index; + + if let Some(active_item) = self.active_item() { + Flex::column() + .with_child({ + let theme = theme::current(cx).clone(); + + let mut stack = Stack::new(); + + enum TabBarEventHandler {} + stack.add_child( + MouseEventHandler::new::(0, cx, |_, _| { + Empty::new() + .contained() + .with_style(theme.workspace.tab_bar.container) + }) + .on_down( + MouseButton::Left, + move |_, this, cx| { + this.activate_item(active_item_index, true, true, cx); + }, + ), + ); + let tooltip_style = theme.tooltip.clone(); + let tab_bar_theme = theme.workspace.tab_bar.clone(); + + let nav_button_height = tab_bar_theme.height; + let button_style = tab_bar_theme.nav_button; + let border_for_nav_buttons = tab_bar_theme + .tab_style(false, false) + .container + .border + .clone(); + + let mut tab_row = Flex::row() + .with_child(nav_button( + "icons/arrow_left.svg", + button_style.clone(), + nav_button_height, + tooltip_style.clone(), + self.can_navigate_backward(), + { + move |pane, cx| { + if let Some(workspace) = pane.workspace.upgrade(cx) { + let pane = cx.weak_handle(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace + .go_back(pane, cx) + .detach_and_log_err(cx) + }) + }) + } + } + }, + super::GoBack, + "Go Back", + cx, + )) + .with_child( + nav_button( + "icons/arrow_right.svg", + button_style.clone(), + nav_button_height, + tooltip_style, + self.can_navigate_forward(), + { + move |pane, cx| { + if let Some(workspace) = pane.workspace.upgrade(cx) { + let pane = cx.weak_handle(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace + .go_forward(pane, cx) + .detach_and_log_err(cx) + }) + }) + } + } + }, + super::GoForward, + "Go Forward", + cx, + ) + .contained() + .with_border(border_for_nav_buttons), + ) + .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); + + if self.has_focus { + let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); + tab_row.add_child( + (render_tab_bar_buttons)(self, cx) + .contained() + .with_style(theme.workspace.tab_bar.pane_button_container) + .flex(1., false) + .into_any(), + ) + } + + stack.add_child(tab_row); + stack + .constrained() + .with_height(theme.workspace.tab_bar.height) + .flex(1., false) + .into_any_named("tab bar") + }) + .with_child({ + enum PaneContentTabDropTarget {} + dragged_item_receiver::( + self, + 0, + self.active_item_index + 1, + !self.can_split, + if self.can_split { Some(100.) } else { None }, + cx, + { + let toolbar = self.toolbar.clone(); + let toolbar_hidden = toolbar.read(cx).hidden(); + move |_, cx| { + Flex::column() + .with_children( + (!toolbar_hidden) + .then(|| ChildView::new(&toolbar, cx).expanded()), + ) + .with_child( + ChildView::new(active_item.as_any(), cx).flex(1., true), + ) + } + }, + ) + .flex(1., true) + }) + .with_child(ChildView::new(&self.tab_context_menu, cx)) + .into_any() + } else { + enum EmptyPane {} + let theme = theme::current(cx).clone(); + + dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { + self.render_blank_pane(&theme, cx) + }) + .on_down(MouseButton::Left, |_, _, cx| { + cx.focus_parent(); + }) + .into_any() + } + }) + .on_down( + MouseButton::Navigate(NavigationDirection::Back), + move |_, pane, cx| { + if let Some(workspace) = pane.workspace.upgrade(cx) { + let pane = cx.weak_handle(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.go_back(pane, cx).detach_and_log_err(cx) + }) + }) + } + }, + ) + .on_down(MouseButton::Navigate(NavigationDirection::Forward), { + move |_, pane, cx| { + if let Some(workspace) = pane.workspace.upgrade(cx) { + let pane = cx.weak_handle(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + workspace.go_forward(pane, cx).detach_and_log_err(cx) + }) + }) + } + } + }) + .into_any_named("pane") + } + + fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + cx.notify(); + } + + self.toolbar.update(cx, |toolbar, cx| { + toolbar.focus_changed(true, cx); + }); + + if let Some(active_item) = self.active_item() { + if cx.is_self_focused() { + // Pane was focused directly. We need to either focus a view inside the active item, + // or focus the active item itself + if let Some(weak_last_focused_view) = + self.last_focused_view_by_item.get(&active_item.id()) + { + if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) { + cx.focus(&last_focused_view); + return; + } else { + self.last_focused_view_by_item.remove(&active_item.id()); + } + } + + cx.focus(active_item.as_any()); + } else if focused != self.tab_bar_context_menu.handle { + self.last_focused_view_by_item + .insert(active_item.id(), focused.downgrade()); + } + } + } + + fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + self.has_focus = false; + self.toolbar.update(cx, |toolbar, cx| { + toolbar.focus_changed(false, cx); + }); + cx.notify(); + } + + fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { + Self::reset_to_default_keymap_context(keymap); + } +} + +impl ItemNavHistory { + pub fn push(&mut self, data: Option, cx: &mut WindowContext) { + self.history.push(data, self.item.clone(), cx); + } + + pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { + self.history.pop(NavigationMode::GoingBack, cx) + } + + pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { + self.history.pop(NavigationMode::GoingForward, cx) + } +} + +impl NavHistory { + pub fn for_each_entry( + &self, + cx: &AppContext, + mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), + ) { + let borrowed_history = self.0.borrow(); + borrowed_history + .forward_stack + .iter() + .chain(borrowed_history.backward_stack.iter()) + .chain(borrowed_history.closed_stack.iter()) + .for_each(|entry| { + if let Some(project_and_abs_path) = + borrowed_history.paths_by_item.get(&entry.item.id()) + { + f(entry, project_and_abs_path.clone()); + } else if let Some(item) = entry.item.upgrade(cx) { + if let Some(path) = item.project_path(cx) { + f(entry, (path, None)); + } + } + }) + } + + pub fn set_mode(&mut self, mode: NavigationMode) { + self.0.borrow_mut().mode = mode; + } + + pub fn mode(&self) -> NavigationMode { + self.0.borrow().mode + } + + pub fn disable(&mut self) { + self.0.borrow_mut().mode = NavigationMode::Disabled; + } + + pub fn enable(&mut self) { + self.0.borrow_mut().mode = NavigationMode::Normal; + } + + pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { + let mut state = self.0.borrow_mut(); + let entry = match mode { + NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { + return None + } + NavigationMode::GoingBack => &mut state.backward_stack, + NavigationMode::GoingForward => &mut state.forward_stack, + NavigationMode::ReopeningClosedItem => &mut state.closed_stack, + } + .pop_back(); + if entry.is_some() { + state.did_update(cx); + } + entry + } + + pub fn push( + &mut self, + data: Option, + item: Rc, + cx: &mut WindowContext, + ) { + let state = &mut *self.0.borrow_mut(); + match state.mode { + NavigationMode::Disabled => {} + NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { + if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.backward_stack.pop_front(); + } + state.backward_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + state.forward_stack.clear(); + } + NavigationMode::GoingBack => { + if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.forward_stack.pop_front(); + } + state.forward_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + } + NavigationMode::GoingForward => { + if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.backward_stack.pop_front(); + } + state.backward_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + } + NavigationMode::ClosingItem => { + if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.closed_stack.pop_front(); + } + state.closed_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + } + } + state.did_update(cx); + } + + pub fn remove_item(&mut self, item_id: usize) { + let mut state = self.0.borrow_mut(); + state.paths_by_item.remove(&item_id); + state + .backward_stack + .retain(|entry| entry.item.id() != item_id); + state + .forward_stack + .retain(|entry| entry.item.id() != item_id); + state + .closed_stack + .retain(|entry| entry.item.id() != item_id); + } + + pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { + self.0.borrow().paths_by_item.get(&item_id).cloned() + } +} + +impl NavHistoryState { + pub fn did_update(&self, cx: &mut WindowContext) { + if let Some(pane) = self.pane.upgrade(cx) { + cx.defer(move |cx| { + pane.update(cx, |pane, cx| pane.history_updated(cx)); + }); + } + } +} + +pub struct PaneBackdrop { + child_view: usize, + child: AnyElement, +} + +impl PaneBackdrop { + pub fn new(pane_item_view: usize, child: AnyElement) -> Self { + PaneBackdrop { + child, + child_view: pane_item_view, + } + } +} + +impl Element for PaneBackdrop { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + view: &mut V, + cx: &mut ViewContext, + ) -> (Vector2F, Self::LayoutState) { + let size = self.child.layout(constraint, view, cx); + (size, ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _: &mut Self::LayoutState, + view: &mut V, + cx: &mut ViewContext, + ) -> Self::PaintState { + let background = theme::current(cx).editor.background; + + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + cx.scene().push_quad(gpui::Quad { + bounds: RectF::new(bounds.origin(), bounds.size()), + background: Some(background), + ..Default::default() + }); + + let child_view_id = self.child_view; + cx.scene().push_mouse_region( + MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( + gpui::platform::MouseButton::Left, + move |_, _: &mut V, cx| { + let window = cx.window(); + cx.app_context().focus(window, Some(child_view_id)) + }, + ), + ); + + cx.scene().push_layer(Some(bounds)); + self.child.paint(bounds.origin(), visible_bounds, view, cx); + cx.scene().pop_layer(); + } + + fn rect_for_text_range( + &self, + range_utf16: std::ops::Range, + _bounds: RectF, + _visible_bounds: RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + view: &V, + cx: &gpui::ViewContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, view, cx) + } + + fn debug( + &self, + _bounds: RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + view: &V, + cx: &gpui::ViewContext, + ) -> serde_json::Value { + gpui::json::json!({ + "type": "Pane Back Drop", + "view": self.child_view, + "child": self.child.debug(view, cx), + }) + } +} + +fn dirty_message_for(buffer_path: Option) -> String { + let path = buffer_path + .as_ref() + .and_then(|p| p.path.to_str()) + .unwrap_or(&"This buffer"); + let path = truncate_and_remove_front(path, 80); + format!("{path} contains unsaved edits. Do you want to save it?") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::item::test::{TestItem, TestProjectItem}; + use gpui::TestAppContext; + use project::FakeFs; + use settings::SettingsStore; + + #[gpui::test] + async fn test_remove_active_empty(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + pane.update(cx, |pane, cx| { + assert!(pane + .close_active_item(&CloseActiveItem { save_intent: None }, cx) + .is_none()) + }); + } + + #[gpui::test] + async fn test_add_item_with_new_item(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + // 1. Add with a destination index + // a. Add before the active item + set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + Some(0), + cx, + ); + }); + assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + + // b. Add after the active item + set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + Some(2), + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + + // c. Add at the end of the item list (including off the length) + set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + Some(5), + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + + // 2. Add without a destination index + // a. Add with active item at the start of the item list + set_labeled_items(&pane, ["A*", "B", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + None, + cx, + ); + }); + set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); + + // b. Add with active item at the end of the item list + set_labeled_items(&pane, ["A", "B", "C*"], cx); + pane.update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + } + + #[gpui::test] + async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + // 1. Add with a destination index + // 1a. Add before the active item + let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(d, false, false, Some(0), cx); + }); + assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + + // 1b. Add after the active item + let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(d, false, false, Some(2), cx); + }); + assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + + // 1c. Add at the end of the item list (including off the length) + let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(a, false, false, Some(5), cx); + }); + assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + + // 1d. Add same item to active index + let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(b, false, false, Some(1), cx); + }); + assert_item_labels(&pane, ["A", "B*", "C"], cx); + + // 1e. Add item to index after same item in last position + let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(c, false, false, Some(2), cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + // 2. Add without a destination index + // 2a. Add with active item at the start of the item list + let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(d, false, false, None, cx); + }); + assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); + + // 2b. Add with active item at the end of the item list + let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(a, false, false, None, cx); + }); + assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + + // 2c. Add active item to active item at end of list + let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(c, false, false, None, cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + // 2d. Add active item to active item at start of list + let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); + pane.update(cx, |pane, cx| { + pane.add_item(a, false, false, None, cx); + }); + assert_item_labels(&pane, ["A*", "B", "C"], cx); + } + + #[gpui::test] + async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + // singleton view + pane.update(cx, |pane, cx| { + let item = TestItem::new() + .with_singleton(true) + .with_label("buffer 1") + .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); + + pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); + }); + assert_item_labels(&pane, ["buffer 1*"], cx); + + // new singleton view with the same project entry + pane.update(cx, |pane, cx| { + let item = TestItem::new() + .with_singleton(true) + .with_label("buffer 1") + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + + pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); + }); + assert_item_labels(&pane, ["buffer 1*"], cx); + + // new singleton view with different project entry + pane.update(cx, |pane, cx| { + let item = TestItem::new() + .with_singleton(true) + .with_label("buffer 2") + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); + pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); + }); + assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); + + // new multibuffer view with the same project entry + pane.update(cx, |pane, cx| { + let item = TestItem::new() + .with_singleton(false) + .with_label("multibuffer 1") + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + + pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); + }); + assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); + + // another multibuffer view with the same project entry + pane.update(cx, |pane, cx| { + let item = TestItem::new() + .with_singleton(false) + .with_label("multibuffer 1b") + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + + pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); + }); + assert_item_labels( + &pane, + ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], + cx, + ); + } + + #[gpui::test] + async fn test_remove_item_ordering(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", false, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", false, cx); + add_labeled_item(&pane, "D", false, cx); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + + pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx)); + add_labeled_item(&pane, "1", false, cx); + assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); + + pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "B*", "C"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "C*"], cx); + + pane.update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A*"], cx); + } + + #[gpui::test] + async fn test_close_inactive_items(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + + pane.update(cx, |pane, cx| { + pane.close_inactive_items(&CloseInactiveItems, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["C*"], cx); + } + + #[gpui::test] + async fn test_close_clean_items(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", true, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", true, cx); + add_labeled_item(&pane, "D", false, cx); + add_labeled_item(&pane, "E", false, cx); + assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); + + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A^", "C*^"], cx); + } + + #[gpui::test] + async fn test_close_items_to_the_left(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["C*", "D", "E"], cx); + } + + #[gpui::test] + async fn test_close_items_to_the_right(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right(&CloseItemsToTheRight, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + } + + #[gpui::test] + async fn test_close_all_items(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root(cx); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + add_labeled_item(&pane, "A", false, cx); + add_labeled_item(&pane, "B", false, cx); + add_labeled_item(&pane, "C", false, cx); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + pane.update(cx, |pane, cx| { + pane.close_all_items(&CloseAllItems { save_intent: None }, cx) + }) + .unwrap() + .await + .unwrap(); + assert_item_labels(&pane, [], cx); + + add_labeled_item(&pane, "A", true, cx); + add_labeled_item(&pane, "B", true, cx); + add_labeled_item(&pane, "C", true, cx); + assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); + + let save = pane + .update(cx, |pane, cx| { + pane.close_all_items(&CloseAllItems { save_intent: None }, cx) + }) + .unwrap(); + + cx.foreground().run_until_parked(); + window.simulate_prompt_answer(2, cx); + save.await.unwrap(); + assert_item_labels(&pane, [], cx); + } + + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + crate::init_settings(cx); + Project::init_settings(cx); + }); + } + + fn add_labeled_item( + pane: &ViewHandle, + label: &str, + is_dirty: bool, + cx: &mut TestAppContext, + ) -> Box> { + pane.update(cx, |pane, cx| { + let labeled_item = + Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty))); + pane.add_item(labeled_item.clone(), false, false, None, cx); + labeled_item + }) + } + + fn set_labeled_items( + pane: &ViewHandle, + labels: [&str; COUNT], + cx: &mut TestAppContext, + ) -> [Box>; COUNT] { + pane.update(cx, |pane, cx| { + pane.items.clear(); + let mut active_item_index = 0; + + let mut index = 0; + let items = labels.map(|mut label| { + if label.ends_with("*") { + label = label.trim_end_matches("*"); + active_item_index = index; + } + + let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); + pane.add_item(labeled_item.clone(), false, false, None, cx); + index += 1; + labeled_item + }); + + pane.activate_item(active_item_index, false, false, cx); + + items + }) + } + + // Assert the item label, with the active item label suffixed with a '*' + fn assert_item_labels( + pane: &ViewHandle, + expected_states: [&str; COUNT], + cx: &mut TestAppContext, + ) { + pane.read_with(cx, |pane, cx| { + let actual_states = pane + .items + .iter() + .enumerate() + .map(|(ix, item)| { + let mut state = item + .as_any() + .downcast_ref::() + .unwrap() + .read(cx) + .label + .clone(); + if ix == pane.active_item_index { + state.push('*'); + } + if item.is_dirty(cx) { + state.push('^'); + } + state + }) + .collect::>(); + + assert_eq!( + actual_states, expected_states, + "pane items do not match expectation" + ); + }) + } +} diff --git a/crates/workspace2/src/pane/dragged_item_receiver.rs b/crates/workspace2/src/pane/dragged_item_receiver.rs new file mode 100644 index 0000000000..292529e787 --- /dev/null +++ b/crates/workspace2/src/pane/dragged_item_receiver.rs @@ -0,0 +1,239 @@ +use super::DraggedItem; +use crate::{Pane, SplitDirection, Workspace}; +use gpui2::{ + color::Color, + elements::{Canvas, MouseEventHandler, ParentElement, Stack}, + geometry::{rect::RectF, vector::Vector2F}, + platform::MouseButton, + scene::MouseUp, + AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle, +}; +use project2::ProjectEntryId; + +pub fn dragged_item_receiver( + pane: &Pane, + region_id: usize, + drop_index: usize, + allow_same_pane: bool, + split_margin: Option, + cx: &mut ViewContext, + render_child: F, +) -> MouseEventHandler +where + Tag: 'static, + D: Element, + F: FnOnce(&mut MouseState, &mut ViewContext) -> D, +{ + let drag_and_drop = cx.global::>(); + let drag_position = if (pane.can_drop)(drag_and_drop, cx) { + drag_and_drop + .currently_dragged::(cx.window()) + .map(|(drag_position, _)| drag_position) + .or_else(|| { + drag_and_drop + .currently_dragged::(cx.window()) + .map(|(drag_position, _)| drag_position) + }) + } else { + None + }; + + let mut handler = MouseEventHandler::above::(region_id, cx, |state, cx| { + // Observing hovered will cause a render when the mouse enters regardless + // of if mouse position was accessed before + let drag_position = if state.dragging() { + drag_position + } else { + None + }; + Stack::new() + .with_child(render_child(state, cx)) + .with_children(drag_position.map(|drag_position| { + Canvas::new(move |bounds, _, _, cx| { + if bounds.contains_point(drag_position) { + let overlay_region = split_margin + .and_then(|split_margin| { + drop_split_direction(drag_position, bounds, split_margin) + .map(|dir| (dir, split_margin)) + }) + .map(|(dir, margin)| dir.along_edge(bounds, margin)) + .unwrap_or(bounds); + + cx.scene().push_stacking_context(None, None); + let background = overlay_color(cx); + cx.scene().push_quad(Quad { + bounds: overlay_region, + background: Some(background), + border: Default::default(), + corner_radii: Default::default(), + }); + cx.scene().pop_stacking_context(); + } + }) + })) + }); + + if drag_position.is_some() { + handler = handler + .on_up(MouseButton::Left, { + move |event, pane, cx| { + let workspace = pane.workspace.clone(); + let pane = cx.weak_handle(); + handle_dropped_item( + event, + workspace, + &pane, + drop_index, + allow_same_pane, + split_margin, + cx, + ); + cx.notify(); + } + }) + .on_move(|_, _, cx| { + let drag_and_drop = cx.global::>(); + + if drag_and_drop + .currently_dragged::(cx.window()) + .is_some() + || drag_and_drop + .currently_dragged::(cx.window()) + .is_some() + { + cx.notify(); + } else { + cx.propagate_event(); + } + }) + } + + handler +} + +pub fn handle_dropped_item( + event: MouseUp, + workspace: WeakViewHandle, + pane: &WeakViewHandle, + index: usize, + allow_same_pane: bool, + split_margin: Option, + cx: &mut EventContext, +) { + enum Action { + Move(WeakViewHandle, usize), + Open(ProjectEntryId), + } + let drag_and_drop = cx.global::>(); + let action = if let Some((_, dragged_item)) = + drag_and_drop.currently_dragged::(cx.window()) + { + Action::Move(dragged_item.pane.clone(), dragged_item.handle.id()) + } else if let Some((_, project_entry)) = + drag_and_drop.currently_dragged::(cx.window()) + { + Action::Open(*project_entry) + } else { + cx.propagate_event(); + return; + }; + + if let Some(split_direction) = + split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin)) + { + let pane_to_split = pane.clone(); + match action { + Action::Move(from, item_id_to_move) => { + cx.window_context().defer(move |cx| { + if let Some(workspace) = workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + workspace.split_pane_with_item( + pane_to_split, + split_direction, + from, + item_id_to_move, + cx, + ); + }) + } + }); + } + Action::Open(project_entry) => { + cx.window_context().defer(move |cx| { + if let Some(workspace) = workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + if let Some(task) = workspace.split_pane_with_project_entry( + pane_to_split, + split_direction, + project_entry, + cx, + ) { + task.detach_and_log_err(cx); + } + }) + } + }); + } + }; + } else { + match action { + Action::Move(from, item_id) => { + if pane != &from || allow_same_pane { + let pane = pane.clone(); + cx.window_context().defer(move |cx| { + if let Some(((workspace, from), to)) = workspace + .upgrade(cx) + .zip(from.upgrade(cx)) + .zip(pane.upgrade(cx)) + { + workspace.update(cx, |workspace, cx| { + workspace.move_item(from, to, item_id, index, cx); + }) + } + }); + } else { + cx.propagate_event(); + } + } + Action::Open(project_entry) => { + let pane = pane.clone(); + cx.window_context().defer(move |cx| { + if let Some(workspace) = workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + if let Some(path) = + workspace.project.read(cx).path_for_entry(project_entry, cx) + { + workspace + .open_path(path, Some(pane), true, cx) + .detach_and_log_err(cx); + } + }); + } + }); + } + } + } +} + +fn drop_split_direction( + position: Vector2F, + region: RectF, + split_margin: f32, +) -> Option { + let mut min_direction = None; + let mut min_distance = split_margin; + for direction in SplitDirection::all() { + let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs(); + + if edge_distance < min_distance { + min_direction = Some(direction); + min_distance = edge_distance; + } + } + + min_direction +} + +fn overlay_color(cx: &AppContext) -> Color { + theme2::current(cx).workspace.drop_target_overlay_color +} diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs new file mode 100644 index 0000000000..aef03dcda0 --- /dev/null +++ b/crates/workspace2/src/pane_group.rs @@ -0,0 +1,989 @@ +use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace}; +use anyhow::{anyhow, Result}; +use call::{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 serde::Deserialize; +use std::{cell::RefCell, rc::Rc, sync::Arc}; +use theme::Theme; + +const HANDLE_HITBOX_SIZE: f32 = 4.0; +const HORIZONTAL_MIN_SIZE: f32 = 80.; +const VERTICAL_MIN_SIZE: f32 = 100.; + +#[derive(Clone, Debug, PartialEq)] +pub struct PaneGroup { + pub(crate) root: Member, +} + +impl PaneGroup { + pub(crate) fn with_root(root: Member) -> Self { + Self { root } + } + + pub fn new(pane: ViewHandle) -> Self { + Self { + root: Member::Pane(pane), + } + } + + pub fn split( + &mut self, + old_pane: &ViewHandle, + new_pane: &ViewHandle, + direction: SplitDirection, + ) -> Result<()> { + match &mut self.root { + Member::Pane(pane) => { + if pane == old_pane { + self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); + Ok(()) + } else { + Err(anyhow!("Pane not found")) + } + } + Member::Axis(axis) => axis.split(old_pane, new_pane, direction), + } + } + + pub fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + match &self.root { + Member::Pane(_) => None, + Member::Axis(axis) => axis.bounding_box_for_pane(pane), + } + } + + pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + match &self.root { + Member::Pane(pane) => Some(pane), + Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), + } + } + + /// Returns: + /// - Ok(true) if it found and removed a pane + /// - Ok(false) if it found but did not remove the pane + /// - Err(_) if it did not find the pane + pub fn remove(&mut self, pane: &ViewHandle) -> Result { + match &mut self.root { + Member::Pane(_) => Ok(false), + Member::Axis(axis) => { + if let Some(last_pane) = axis.remove(pane)? { + self.root = last_pane; + } + Ok(true) + } + } + } + + pub fn swap(&mut self, from: &ViewHandle, to: &ViewHandle) { + match &mut self.root { + Member::Pane(_) => {} + Member::Axis(axis) => axis.swap(from, to), + }; + } + + pub(crate) fn render( + &self, + project: &ModelHandle, + theme: &Theme, + follower_states: &HashMap, FollowerState>, + active_call: Option<&ModelHandle>, + active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, + app_state: &Arc, + cx: &mut ViewContext, + ) -> AnyElement { + self.root.render( + project, + 0, + theme, + follower_states, + active_call, + active_pane, + zoomed, + app_state, + cx, + ) + } + + pub(crate) fn panes(&self) -> Vec<&ViewHandle> { + let mut panes = Vec::new(); + self.root.collect_panes(&mut panes); + panes + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Member { + Axis(PaneAxis), + Pane(ViewHandle), +} + +impl Member { + fn new_axis( + old_pane: ViewHandle, + new_pane: ViewHandle, + direction: SplitDirection, + ) -> Self { + use Axis::*; + use SplitDirection::*; + + let axis = match direction { + Up | Down => Vertical, + Left | Right => Horizontal, + }; + + let members = match direction { + Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)], + Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)], + }; + + Member::Axis(PaneAxis::new(axis, members)) + } + + fn contains(&self, needle: &ViewHandle) -> bool { + match self { + Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)), + Member::Pane(pane) => pane == needle, + } + } + + pub fn render( + &self, + project: &ModelHandle, + basis: usize, + theme: &Theme, + follower_states: &HashMap, FollowerState>, + active_call: Option<&ModelHandle>, + active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, + app_state: &Arc, + cx: &mut ViewContext, + ) -> AnyElement { + enum FollowIntoExternalProject {} + + match self { + Member::Pane(pane) => { + let pane_element = if Some(&**pane) == zoomed { + Empty::new().into_any() + } else { + ChildView::new(pane, cx).into_any() + }; + + let leader = follower_states.get(pane).and_then(|state| { + let room = active_call?.read(cx).room()?.read(cx); + room.remote_participant_for_peer_id(state.leader_id) + }); + + let mut leader_border = Border::default(); + let mut leader_status_box = None; + if let Some(leader) = &leader { + let leader_color = theme + .editor + .selection_style_for_room_participant(leader.participant_index.0) + .cursor; + leader_border = Border::all(theme.workspace.leader_border_width, leader_color); + leader_border + .color + .fade_out(1. - theme.workspace.leader_border_opacity); + leader_border.overlay = true; + + leader_status_box = match leader.location { + ParticipantLocation::SharedProject { + project_id: leader_project_id, + } => { + if Some(leader_project_id) == project.read(cx).remote_id() { + None + } else { + let leader_user = leader.user.clone(); + let leader_user_id = leader.user.id; + Some( + MouseEventHandler::new::( + pane.id(), + cx, + |_, _| { + Label::new( + format!( + "Follow {} to their active project", + leader_user.github_login, + ), + theme + .workspace + .external_location_message + .text + .clone(), + ) + .contained() + .with_style( + theme.workspace.external_location_message.container, + ) + }, + ) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, this, cx| { + crate::join_remote_project( + leader_project_id, + leader_user_id, + this.app_state().clone(), + cx, + ) + .detach_and_log_err(cx); + }) + .aligned() + .bottom() + .right() + .into_any(), + ) + } + } + ParticipantLocation::UnsharedProject => Some( + Label::new( + format!( + "{} is viewing an unshared Zed project", + leader.user.github_login + ), + theme.workspace.external_location_message.text.clone(), + ) + .contained() + .with_style(theme.workspace.external_location_message.container) + .aligned() + .bottom() + .right() + .into_any(), + ), + ParticipantLocation::External => Some( + Label::new( + format!( + "{} is viewing a window outside of Zed", + leader.user.github_login + ), + theme.workspace.external_location_message.text.clone(), + ) + .contained() + .with_style(theme.workspace.external_location_message.container) + .aligned() + .bottom() + .right() + .into_any(), + ), + }; + } + + Stack::new() + .with_child(pane_element.contained().with_border(leader_border)) + .with_children(leader_status_box) + .into_any() + } + Member::Axis(axis) => axis.render( + project, + basis + 1, + theme, + follower_states, + active_call, + active_pane, + zoomed, + app_state, + cx, + ), + } + } + + fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle>) { + match self { + Member::Axis(axis) => { + for member in &axis.members { + member.collect_panes(panes); + } + } + Member::Pane(pane) => panes.push(pane), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct PaneAxis { + pub axis: Axis, + pub members: Vec, + pub flexes: Rc>>, + pub bounding_boxes: Rc>>>, +} + +impl PaneAxis { + pub fn new(axis: Axis, members: Vec) -> Self { + let flexes = Rc::new(RefCell::new(vec![1.; members.len()])); + let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()])); + Self { + axis, + members, + flexes, + bounding_boxes, + } + } + + pub fn load(axis: Axis, members: Vec, flexes: Option>) -> Self { + let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]); + debug_assert!(members.len() == flexes.len()); + + let flexes = Rc::new(RefCell::new(flexes)); + let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()])); + Self { + axis, + members, + flexes, + bounding_boxes, + } + } + + fn split( + &mut self, + old_pane: &ViewHandle, + new_pane: &ViewHandle, + direction: SplitDirection, + ) -> Result<()> { + for (mut idx, member) in self.members.iter_mut().enumerate() { + match member { + Member::Axis(axis) => { + if axis.split(old_pane, new_pane, direction).is_ok() { + return Ok(()); + } + } + Member::Pane(pane) => { + if pane == old_pane { + if direction.axis() == self.axis { + if direction.increasing() { + idx += 1; + } + + self.members.insert(idx, Member::Pane(new_pane.clone())); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + } else { + *member = + Member::new_axis(old_pane.clone(), new_pane.clone(), direction); + } + return Ok(()); + } + } + } + } + Err(anyhow!("Pane not found")) + } + + fn remove(&mut self, pane_to_remove: &ViewHandle) -> Result> { + let mut found_pane = false; + let mut remove_member = None; + for (idx, member) in self.members.iter_mut().enumerate() { + match member { + Member::Axis(axis) => { + if let Ok(last_pane) = axis.remove(pane_to_remove) { + if let Some(last_pane) = last_pane { + *member = last_pane; + } + found_pane = true; + break; + } + } + Member::Pane(pane) => { + if pane == pane_to_remove { + found_pane = true; + remove_member = Some(idx); + break; + } + } + } + } + + if found_pane { + if let Some(idx) = remove_member { + self.members.remove(idx); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + } + + if self.members.len() == 1 { + let result = self.members.pop(); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + Ok(result) + } else { + Ok(None) + } + } else { + Err(anyhow!("Pane not found")) + } + } + + fn swap(&mut self, from: &ViewHandle, to: &ViewHandle) { + for member in self.members.iter_mut() { + match member { + Member::Axis(axis) => axis.swap(from, to), + Member::Pane(pane) => { + if pane == from { + *member = Member::Pane(to.clone()); + } else if pane == to { + *member = Member::Pane(from.clone()) + } + } + } + } + } + + fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); + + for (idx, member) in self.members.iter().enumerate() { + match member { + Member::Pane(found) => { + if pane == found { + return self.bounding_boxes.borrow()[idx]; + } + } + Member::Axis(axis) => { + if let Some(rect) = axis.bounding_box_for_pane(pane) { + return Some(rect); + } + } + } + } + None + } + + fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); + + let bounding_boxes = self.bounding_boxes.borrow(); + + for (idx, member) in self.members.iter().enumerate() { + if let Some(coordinates) = bounding_boxes[idx] { + if coordinates.contains_point(coordinate) { + return match member { + Member::Pane(found) => Some(found), + Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), + }; + } + } + } + None + } + + fn render( + &self, + project: &ModelHandle, + basis: usize, + theme: &Theme, + follower_states: &HashMap, FollowerState>, + active_call: Option<&ModelHandle>, + active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, + app_state: &Arc, + cx: &mut ViewContext, + ) -> AnyElement { + debug_assert!(self.members.len() == self.flexes.borrow().len()); + + let mut pane_axis = PaneAxisElement::new( + self.axis, + basis, + self.flexes.clone(), + self.bounding_boxes.clone(), + ); + let mut active_pane_ix = None; + + let mut members = self.members.iter().enumerate().peekable(); + while let Some((ix, member)) = members.next() { + let last = members.peek().is_none(); + + if member.contains(active_pane) { + active_pane_ix = Some(ix); + } + + let mut member = member.render( + project, + (basis + ix) * 10, + theme, + follower_states, + active_call, + active_pane, + zoomed, + app_state, + cx, + ); + + if !last { + let mut border = theme.workspace.pane_divider; + border.left = false; + border.right = false; + border.top = false; + border.bottom = false; + + match self.axis { + Axis::Vertical => border.bottom = true, + Axis::Horizontal => border.right = true, + } + + member = member.contained().with_border(border).into_any(); + } + + pane_axis = pane_axis.with_child(member.into_any()); + } + pane_axis.set_active_pane(active_pane_ix); + pane_axis.into_any() + } +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +pub enum SplitDirection { + Up, + Down, + Left, + Right, +} + +impl SplitDirection { + pub fn all() -> [Self; 4] { + [Self::Up, Self::Down, Self::Left, Self::Right] + } + + pub fn edge(&self, rect: RectF) -> f32 { + match self { + Self::Up => rect.min_y(), + Self::Down => rect.max_y(), + Self::Left => rect.min_x(), + Self::Right => rect.max_x(), + } + } + + // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection + pub fn along_edge(&self, rect: RectF, size: f32) -> RectF { + match self { + Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)), + Self::Down => RectF::new( + rect.lower_left() - Vector2F::new(0., size), + Vector2F::new(rect.width(), size), + ), + Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())), + Self::Right => RectF::new( + rect.upper_right() - Vector2F::new(size, 0.), + Vector2F::new(size, rect.height()), + ), + } + } + + pub fn axis(&self) -> Axis { + match self { + Self::Up | Self::Down => Axis::Vertical, + Self::Left | Self::Right => Axis::Horizontal, + } + } + + pub fn increasing(&self) -> bool { + match self { + Self::Left | Self::Up => false, + Self::Down | Self::Right => true, + } + } +} + +mod element { + use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; + + use gpui::{ + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::{self, ToJson}, + platform::{CursorStyle, MouseButton}, + scene::MouseDrag, + AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt, + SizeConstraint, Vector2FExt, ViewContext, + }; + + use crate::{ + pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, + Workspace, WorkspaceSettings, + }; + + pub struct PaneAxisElement { + axis: Axis, + basis: usize, + active_pane_ix: Option, + flexes: Rc>>, + children: Vec>, + bounding_boxes: Rc>>>, + } + + impl PaneAxisElement { + pub fn new( + axis: Axis, + basis: usize, + flexes: Rc>>, + bounding_boxes: Rc>>>, + ) -> Self { + Self { + axis, + basis, + flexes, + bounding_boxes, + active_pane_ix: None, + children: Default::default(), + } + } + + pub fn set_active_pane(&mut self, active_pane_ix: Option) { + self.active_pane_ix = active_pane_ix; + } + + fn layout_children( + &mut self, + active_pane_magnification: f32, + constraint: SizeConstraint, + remaining_space: &mut f32, + remaining_flex: &mut f32, + cross_axis_max: &mut f32, + view: &mut Workspace, + cx: &mut ViewContext, + ) { + let flexes = self.flexes.borrow(); + let cross_axis = self.axis.invert(); + for (ix, child) in self.children.iter_mut().enumerate() { + let flex = if active_pane_magnification != 1. { + if let Some(active_pane_ix) = self.active_pane_ix { + if ix == active_pane_ix { + active_pane_magnification + } else { + 1. + } + } else { + 1. + } + } else { + flexes[ix] + }; + + let child_size = if *remaining_flex == 0.0 { + *remaining_space + } else { + let space_per_flex = *remaining_space / *remaining_flex; + space_per_flex * flex + }; + + let child_constraint = match self.axis { + Axis::Horizontal => SizeConstraint::new( + vec2f(child_size, constraint.min.y()), + vec2f(child_size, constraint.max.y()), + ), + Axis::Vertical => SizeConstraint::new( + vec2f(constraint.min.x(), child_size), + vec2f(constraint.max.x(), child_size), + ), + }; + let child_size = child.layout(child_constraint, view, cx); + *remaining_space -= child_size.along(self.axis); + *remaining_flex -= flex; + *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); + } + } + + fn handle_resize( + flexes: Rc>>, + axis: Axis, + preceding_ix: usize, + child_start: Vector2F, + drag_bounds: RectF, + ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { + let size = move |ix, flexes: &[f32]| { + drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) + }; + + move |drag, workspace: &mut Workspace, cx| { + if drag.end { + // TODO: Clear cascading resize state + return; + } + let min_size = match axis { + Axis::Horizontal => HORIZONTAL_MIN_SIZE, + Axis::Vertical => VERTICAL_MIN_SIZE, + }; + let mut flexes = flexes.borrow_mut(); + + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - 1. > size(preceding_ix, flexes.as_slice()) { + return; + } + + let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) + - size(preceding_ix, flexes.as_slice()); + + let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { + let flex_change = pixel_dx / drag_bounds.length_along(axis); + let current_target_flex = flexes[target_ix] + flex_change; + let next_target_flex = + flexes[(target_ix as isize + next) as usize] - flex_change; + (current_target_flex, next_target_flex) + }; + + let mut successors = from_fn({ + let forward = proposed_current_pixel_change > 0.; + let mut ix_offset = 0; + let len = flexes.len(); + move || { + let result = if forward { + (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) + } else { + (preceding_ix as isize - ix_offset as isize >= 0) + .then(|| preceding_ix - ix_offset) + }; + + ix_offset += 1; + + result + } + }); + + while proposed_current_pixel_change.abs() > 0. { + let Some(current_ix) = successors.next() else { + break; + }; + + let next_target_size = f32::max( + size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, + min_size, + ); + + let current_target_size = f32::max( + size(current_ix, flexes.as_slice()) + + size(current_ix + 1, flexes.as_slice()) + - next_target_size, + min_size, + ); + + let current_pixel_change = + current_target_size - size(current_ix, flexes.as_slice()); + + let (current_target_flex, next_target_flex) = + flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); + + flexes[current_ix] = current_target_flex; + flexes[current_ix + 1] = next_target_flex; + + proposed_current_pixel_change -= current_pixel_change; + } + + workspace.schedule_serialize(cx); + cx.notify(); + } + } + } + + impl Extend> for PaneAxisElement { + fn extend>>(&mut self, children: T) { + self.children.extend(children); + } + } + + impl Element for PaneAxisElement { + type LayoutState = f32; + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut Workspace, + cx: &mut ViewContext, + ) -> (Vector2F, Self::LayoutState) { + debug_assert!(self.children.len() == self.flexes.borrow().len()); + + let active_pane_magnification = + settings::get::(cx).active_pane_magnification; + + let mut remaining_flex = 0.; + + if active_pane_magnification != 1. { + let active_pane_flex = self + .active_pane_ix + .map(|_| active_pane_magnification) + .unwrap_or(1.); + remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; + } else { + for flex in self.flexes.borrow().iter() { + remaining_flex += flex; + } + } + + let mut cross_axis_max: f32 = 0.0; + let mut remaining_space = constraint.max_along(self.axis); + + if remaining_space.is_infinite() { + panic!("flex contains flexible children but has an infinite constraint along the flex axis"); + } + + self.layout_children( + active_pane_magnification, + constraint, + &mut remaining_space, + &mut remaining_flex, + &mut cross_axis_max, + view, + cx, + ); + + let mut size = match self.axis { + Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), + Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), + }; + + if constraint.min.x().is_finite() { + size.set_x(size.x().max(constraint.min.x())); + } + if constraint.min.y().is_finite() { + size.set_y(size.y().max(constraint.min.y())); + } + + if size.x() > constraint.max.x() { + size.set_x(constraint.max.x()); + } + if size.y() > constraint.max.y() { + size.set_y(constraint.max.y()); + } + + (size, remaining_space) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + remaining_space: &mut Self::LayoutState, + view: &mut Workspace, + cx: &mut ViewContext, + ) -> Self::PaintState { + let can_resize = settings::get::(cx).active_pane_magnification == 1.; + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + let overflowing = *remaining_space < 0.; + if overflowing { + cx.scene().push_layer(Some(visible_bounds)); + } + + let mut child_origin = bounds.origin(); + + let mut bounding_boxes = self.bounding_boxes.borrow_mut(); + bounding_boxes.clear(); + + let mut children_iter = self.children.iter_mut().enumerate().peekable(); + while let Some((ix, child)) = children_iter.next() { + let child_start = child_origin.clone(); + child.paint(child_origin, visible_bounds, view, cx); + + bounding_boxes.push(Some(RectF::new(child_origin, child.size()))); + + match self.axis { + Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), + Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), + } + + if can_resize && children_iter.peek().is_some() { + cx.scene().push_stacking_context(None, None); + + let handle_origin = match self.axis { + Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), + Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), + }; + + let handle_bounds = match self.axis { + Axis::Horizontal => RectF::new( + handle_origin, + vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), + ), + Axis::Vertical => RectF::new( + handle_origin, + vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), + ), + }; + + let style = match self.axis { + Axis::Horizontal => CursorStyle::ResizeLeftRight, + Axis::Vertical => CursorStyle::ResizeUpDown, + }; + + cx.scene().push_cursor_region(CursorRegion { + bounds: handle_bounds, + style, + }); + + enum ResizeHandle {} + let mut mouse_region = MouseRegion::new::( + cx.view_id(), + self.basis + ix, + handle_bounds, + ); + mouse_region = mouse_region + .on_drag( + MouseButton::Left, + Self::handle_resize( + self.flexes.clone(), + self.axis, + ix, + child_start, + visible_bounds.clone(), + ), + ) + .on_click(MouseButton::Left, { + let flexes = self.flexes.clone(); + move |e, v: &mut Workspace, cx| { + if e.click_count >= 2 { + let mut borrow = flexes.borrow_mut(); + *borrow = vec![1.; borrow.len()]; + v.schedule_serialize(cx); + cx.notify(); + } + } + }); + cx.scene().push_mouse_region(mouse_region); + + cx.scene().pop_stacking_context(); + } + } + + if overflowing { + cx.scene().pop_layer(); + } + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &Workspace, + cx: &ViewContext, + ) -> Option { + self.children + .iter() + .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &Workspace, + cx: &ViewContext, + ) -> json::Value { + serde_json::json!({ + "type": "PaneAxis", + "bounds": bounds.to_json(), + "axis": self.axis.to_json(), + "flexes": *self.flexes.borrow(), + "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() + }) + } + } +} diff --git a/crates/workspace2/src/persistence.rs b/crates/workspace2/src/persistence.rs new file mode 100644 index 0000000000..2a4062c079 --- /dev/null +++ b/crates/workspace2/src/persistence.rs @@ -0,0 +1,972 @@ +#![allow(dead_code)] + +pub mod model; + +use std::path::Path; + +use anyhow::{anyhow, bail, Context, Result}; +use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; +use gpui::{platform::WindowBounds, Axis}; + +use util::{unzip_option, ResultExt}; +use uuid::Uuid; + +use crate::WorkspaceId; + +use model::{ + GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + WorkspaceLocation, +}; + +use self::model::DockStructure; + +define_connection! { + // Current schema shape using pseudo-rust syntax: + // + // workspaces( + // workspace_id: usize, // Primary key for workspaces + // workspace_location: Bincode>, + // dock_visible: bool, // Deprecated + // dock_anchor: DockAnchor, // Deprecated + // dock_pane: Option, // Deprecated + // left_sidebar_open: boolean, + // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS + // window_state: String, // WindowBounds Discriminant + // window_x: Option, // WindowBounds::Fixed RectF x + // window_y: Option, // WindowBounds::Fixed RectF y + // window_width: Option, // WindowBounds::Fixed RectF width + // window_height: Option, // WindowBounds::Fixed RectF height + // display: Option, // Display id + // ) + // + // pane_groups( + // group_id: usize, // Primary key for pane_groups + // workspace_id: usize, // References workspaces table + // parent_group_id: Option, // None indicates that this is the root node + // position: Optiopn, // None indicates that this is the root node + // axis: Option, // 'Vertical', 'Horizontal' + // flexes: Option>, // A JSON array of floats + // ) + // + // panes( + // pane_id: usize, // Primary key for panes + // workspace_id: usize, // References workspaces table + // active: bool, + // ) + // + // center_panes( + // pane_id: usize, // Primary key for center_panes + // parent_group_id: Option, // References pane_groups. If none, this is the root + // position: Option, // None indicates this is the root + // ) + // + // CREATE TABLE items( + // item_id: usize, // This is the item's view id, so this is not unique + // workspace_id: usize, // References workspaces table + // pane_id: usize, // References panes table + // kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global + // position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column + // active: bool, // Indicates if this item is the active one in the pane + // ) + pub static ref DB: WorkspaceDb<()> = + &[sql!( + CREATE TABLE workspaces( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. + dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. + dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. + left_sidebar_open INTEGER, // Boolean + timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) + ) STRICT; + + CREATE TABLE pane_groups( + group_id INTEGER PRIMARY KEY, + workspace_id INTEGER NOT NULL, + parent_group_id INTEGER, // NULL indicates that this is a root node + position INTEGER, // NULL indicates that this is a root node + axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE + ) STRICT; + + CREATE TABLE panes( + pane_id INTEGER PRIMARY KEY, + workspace_id INTEGER NOT NULL, + active INTEGER NOT NULL, // Boolean + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) STRICT; + + CREATE TABLE center_panes( + pane_id INTEGER PRIMARY KEY, + parent_group_id INTEGER, // NULL means that this is a root pane + position INTEGER, // NULL means that this is a root pane + FOREIGN KEY(pane_id) REFERENCES panes(pane_id) + ON DELETE CASCADE, + FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE + ) STRICT; + + CREATE TABLE items( + item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique + workspace_id INTEGER NOT NULL, + pane_id INTEGER NOT NULL, + kind TEXT NOT NULL, + position INTEGER NOT NULL, + active INTEGER NOT NULL, + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY(pane_id) REFERENCES panes(pane_id) + ON DELETE CASCADE, + PRIMARY KEY(item_id, workspace_id) + ) STRICT; + ), + sql!( + ALTER TABLE workspaces ADD COLUMN window_state TEXT; + ALTER TABLE workspaces ADD COLUMN window_x REAL; + ALTER TABLE workspaces ADD COLUMN window_y REAL; + ALTER TABLE workspaces ADD COLUMN window_width REAL; + ALTER TABLE workspaces ADD COLUMN window_height REAL; + ALTER TABLE workspaces ADD COLUMN display BLOB; + ), + // Drop foreign key constraint from workspaces.dock_pane to panes table. + sql!( + CREATE TABLE workspaces_2( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed. + dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed. + dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed. + left_sidebar_open INTEGER, // Boolean + timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, + window_state TEXT, + window_x REAL, + window_y REAL, + window_width REAL, + window_height REAL, + display BLOB + ) STRICT; + INSERT INTO workspaces_2 SELECT * FROM workspaces; + DROP TABLE workspaces; + ALTER TABLE workspaces_2 RENAME TO workspaces; + ), + // Add panels related information + sql!( + ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT; + ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT; + ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT; + ), + // Add panel zoom persistence + sql!( + ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool + ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool + ), + // Add pane group flex data + sql!( + ALTER TABLE pane_groups ADD COLUMN flexes TEXT; + ) + ]; +} + +impl WorkspaceDb { + /// Returns a serialized workspace for the given worktree_roots. If the passed array + /// is empty, the most recent workspace is returned instead. If no workspace for the + /// passed roots is stored, returns none. + pub fn workspace_for_roots>( + &self, + worktree_roots: &[P], + ) -> Option { + let workspace_location: WorkspaceLocation = worktree_roots.into(); + + // Note that we re-assign the workspace_id here in case it's empty + // and we've grabbed the most recent workspace + let (workspace_id, workspace_location, bounds, display, docks): ( + WorkspaceId, + WorkspaceLocation, + Option, + Option, + DockStructure, + ) = self + .select_row_bound(sql! { + SELECT + workspace_id, + workspace_location, + window_state, + window_x, + window_y, + window_width, + window_height, + display, + left_dock_visible, + left_dock_active_panel, + left_dock_zoom, + right_dock_visible, + right_dock_active_panel, + right_dock_zoom, + bottom_dock_visible, + bottom_dock_active_panel, + bottom_dock_zoom + FROM workspaces + WHERE workspace_location = ? + }) + .and_then(|mut prepared_statement| (prepared_statement)(&workspace_location)) + .context("No workspaces found") + .warn_on_err() + .flatten()?; + + Some(SerializedWorkspace { + id: workspace_id, + location: workspace_location.clone(), + center_group: self + .get_center_pane_group(workspace_id) + .context("Getting center group") + .log_err()?, + bounds, + display, + docks, + }) + } + + /// Saves a workspace using the worktree roots. Will garbage collect any workspaces + /// that used this workspace previously + pub async fn save_workspace(&self, workspace: SerializedWorkspace) { + self.write(move |conn| { + conn.with_savepoint("update_worktrees", || { + // Clear out panes and pane_groups + conn.exec_bound(sql!( + DELETE FROM pane_groups WHERE workspace_id = ?1; + DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) + .expect("Clearing old panes"); + + conn.exec_bound(sql!( + DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? + ))?((&workspace.location, workspace.id.clone())) + .context("clearing out old locations")?; + + // Upsert + conn.exec_bound(sql!( + INSERT INTO workspaces( + workspace_id, + workspace_location, + left_dock_visible, + left_dock_active_panel, + left_dock_zoom, + right_dock_visible, + right_dock_active_panel, + right_dock_zoom, + bottom_dock_visible, + bottom_dock_active_panel, + bottom_dock_zoom, + timestamp + ) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP) + ON CONFLICT DO + UPDATE SET + workspace_location = ?2, + left_dock_visible = ?3, + left_dock_active_panel = ?4, + left_dock_zoom = ?5, + right_dock_visible = ?6, + right_dock_active_panel = ?7, + right_dock_zoom = ?8, + bottom_dock_visible = ?9, + bottom_dock_active_panel = ?10, + bottom_dock_zoom = ?11, + timestamp = CURRENT_TIMESTAMP + ))?((workspace.id, &workspace.location, workspace.docks)) + .context("Updating workspace")?; + + // Save center pane group + Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) + .context("save pane group in save workspace")?; + + Ok(()) + }) + .log_err(); + }) + .await; + } + + query! { + pub async fn next_id() -> Result { + INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id + } + } + + query! { + fn recent_workspaces() -> Result> { + SELECT workspace_id, workspace_location + FROM workspaces + WHERE workspace_location IS NOT NULL + ORDER BY timestamp DESC + } + } + + query! { + async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { + DELETE FROM workspaces + WHERE workspace_id IS ? + } + } + + // Returns the recent locations which are still valid on disk and deletes ones which no longer + // exist. + pub async fn recent_workspaces_on_disk(&self) -> Result> { + let mut result = Vec::new(); + let mut delete_tasks = Vec::new(); + for (id, location) in self.recent_workspaces()? { + if location.paths().iter().all(|path| path.exists()) + && location.paths().iter().any(|path| path.is_dir()) + { + result.push((id, location)); + } else { + delete_tasks.push(self.delete_stale_workspace(id)); + } + } + + futures::future::join_all(delete_tasks).await; + Ok(result) + } + + pub async fn last_workspace(&self) -> Result> { + Ok(self + .recent_workspaces_on_disk() + .await? + .into_iter() + .next() + .map(|(_, location)| location)) + } + + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { + Ok(self + .get_pane_group(workspace_id, None)? + .into_iter() + .next() + .unwrap_or_else(|| { + SerializedPaneGroup::Pane(SerializedPane { + active: true, + children: vec![], + }) + })) + } + + fn get_pane_group( + &self, + workspace_id: WorkspaceId, + group_id: Option, + ) -> Result> { + type GroupKey = (Option, WorkspaceId); + type GroupOrPane = ( + Option, + Option, + Option, + Option, + Option, + ); + self.select_bound::(sql!( + SELECT group_id, axis, pane_id, active, flexes + FROM (SELECT + group_id, + axis, + NULL as pane_id, + NULL as active, + position, + parent_group_id, + workspace_id, + flexes + FROM pane_groups + UNION + SELECT + NULL, + NULL, + center_panes.pane_id, + panes.active as active, + position, + parent_group_id, + panes.workspace_id as workspace_id, + NULL + FROM center_panes + JOIN panes ON center_panes.pane_id = panes.pane_id) + WHERE parent_group_id IS ? AND workspace_id = ? + ORDER BY position + ))?((group_id, workspace_id))? + .into_iter() + .map(|(group_id, axis, pane_id, active, flexes)| { + if let Some((group_id, axis)) = group_id.zip(axis) { + let flexes = flexes + .map(|flexes| serde_json::from_str::>(&flexes)) + .transpose()?; + + Ok(SerializedPaneGroup::Group { + axis, + children: self.get_pane_group(workspace_id, Some(group_id))?, + flexes, + }) + } else if let Some((pane_id, active)) = pane_id.zip(active) { + Ok(SerializedPaneGroup::Pane(SerializedPane::new( + self.get_items(pane_id)?, + active, + ))) + } else { + bail!("Pane Group Child was neither a pane group or a pane"); + } + }) + // Filter out panes and pane groups which don't have any children or items + .filter(|pane_group| match pane_group { + Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(), + Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), + _ => true, + }) + .collect::>() + } + + fn save_pane_group( + conn: &Connection, + workspace_id: WorkspaceId, + pane_group: &SerializedPaneGroup, + parent: Option<(GroupId, usize)>, + ) -> Result<()> { + match pane_group { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { + let (parent_id, position) = unzip_option(parent); + + let flex_string = flexes + .as_ref() + .map(|flexes| serde_json::json!(flexes).to_string()); + + let group_id = conn.select_row_bound::<_, i64>(sql!( + INSERT INTO pane_groups( + workspace_id, + parent_group_id, + position, + axis, + flexes + ) + VALUES (?, ?, ?, ?, ?) + RETURNING group_id + ))?(( + workspace_id, + parent_id, + position, + *axis, + flex_string, + ))? + .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; + + for (position, group) in children.iter().enumerate() { + Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))? + } + + Ok(()) + } + SerializedPaneGroup::Pane(pane) => { + Self::save_pane(conn, workspace_id, &pane, parent)?; + Ok(()) + } + } + } + + fn save_pane( + conn: &Connection, + workspace_id: WorkspaceId, + pane: &SerializedPane, + parent: Option<(GroupId, usize)>, + ) -> Result { + let pane_id = conn.select_row_bound::<_, i64>(sql!( + INSERT INTO panes(workspace_id, active) + VALUES (?, ?) + RETURNING pane_id + ))?((workspace_id, pane.active))? + .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; + + let (parent_id, order) = unzip_option(parent); + conn.exec_bound(sql!( + INSERT INTO center_panes(pane_id, parent_group_id, position) + VALUES (?, ?, ?) + ))?((pane_id, parent_id, order))?; + + Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; + + Ok(pane_id) + } + + fn get_items(&self, pane_id: PaneId) -> Result> { + Ok(self.select_bound(sql!( + SELECT kind, item_id, active FROM items + WHERE pane_id = ? + ORDER BY position + ))?(pane_id)?) + } + + fn save_items( + conn: &Connection, + workspace_id: WorkspaceId, + pane_id: PaneId, + items: &[SerializedItem], + ) -> Result<()> { + let mut insert = conn.exec_bound(sql!( + INSERT INTO items(workspace_id, pane_id, position, kind, item_id, active) VALUES (?, ?, ?, ?, ?, ?) + )).context("Preparing insertion")?; + for (position, item) in items.iter().enumerate() { + insert((workspace_id, pane_id, position, item))?; + } + + Ok(()) + } + + query! { + pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { + UPDATE workspaces + SET timestamp = CURRENT_TIMESTAMP + WHERE workspace_id = ? + } + } + + query! { + pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> { + UPDATE workspaces + SET window_state = ?2, + window_x = ?3, + window_y = ?4, + window_width = ?5, + window_height = ?6, + display = ?7 + WHERE workspace_id = ?1 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use db::open_test_db; + + #[gpui::test] + async fn test_next_id_stability() { + env_logger::try_init().ok(); + + let db = WorkspaceDb(open_test_db("test_next_id_stability").await); + + db.write(|conn| { + conn.migrate( + "test_table", + &[sql!( + CREATE TABLE test_table( + text TEXT, + workspace_id INTEGER, + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ) STRICT; + )], + ) + .unwrap(); + }) + .await; + + let id = db.next_id().await.unwrap(); + // Assert the empty row got inserted + assert_eq!( + Some(id), + db.select_row_bound::(sql!( + SELECT workspace_id FROM workspaces WHERE workspace_id = ? + )) + .unwrap()(id) + .unwrap() + ); + + db.write(move |conn| { + conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) + .unwrap()(("test-text-1", id)) + .unwrap() + }) + .await; + + let test_text_1 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(1) + .unwrap() + .unwrap(); + assert_eq!(test_text_1, "test-text-1"); + } + + #[gpui::test] + async fn test_workspace_id_stability() { + env_logger::try_init().ok(); + + let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); + + db.write(|conn| { + conn.migrate( + "test_table", + &[sql!( + CREATE TABLE test_table( + text TEXT, + workspace_id INTEGER, + FOREIGN KEY(workspace_id) + REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ) STRICT;)], + ) + }) + .await + .unwrap(); + + let mut workspace_1 = SerializedWorkspace { + id: 1, + location: (["/tmp", "/tmp2"]).into(), + center_group: Default::default(), + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + }; + + let workspace_2 = SerializedWorkspace { + id: 2, + location: (["/tmp"]).into(), + center_group: Default::default(), + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + }; + + db.save_workspace(workspace_1.clone()).await; + + db.write(|conn| { + conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) + .unwrap()(("test-text-1", 1)) + .unwrap(); + }) + .await; + + db.save_workspace(workspace_2.clone()).await; + + db.write(|conn| { + conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) + .unwrap()(("test-text-2", 2)) + .unwrap(); + }) + .await; + + workspace_1.location = (["/tmp", "/tmp3"]).into(); + db.save_workspace(workspace_1.clone()).await; + db.save_workspace(workspace_1).await; + db.save_workspace(workspace_2).await; + + let test_text_2 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(2) + .unwrap() + .unwrap(); + assert_eq!(test_text_2, "test-text-2"); + + let test_text_1 = db + .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) + .unwrap()(1) + .unwrap() + .unwrap(); + assert_eq!(test_text_1, "test-text-1"); + } + + fn group(axis: gpui::Axis, children: Vec) -> SerializedPaneGroup { + SerializedPaneGroup::Group { + axis, + flexes: None, + children, + } + } + + #[gpui::test] + async fn test_full_workspace_serialization() { + env_logger::try_init().ok(); + + let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); + + // ----------------- + // | 1,2 | 5,6 | + // | - - - | | + // | 3,4 | | + // ----------------- + let center_group = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 5, false), + SerializedItem::new("Terminal", 6, true), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 7, true), + SerializedItem::new("Terminal", 8, false), + ], + false, + )), + ], + ), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 9, false), + SerializedItem::new("Terminal", 10, true), + ], + false, + )), + ], + ); + + let workspace = SerializedWorkspace { + id: 5, + location: (["/tmp", "/tmp2"]).into(), + center_group, + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + }; + + db.save_workspace(workspace.clone()).await; + let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); + + assert_eq!(workspace, round_trip_workspace.unwrap()); + + // Test guaranteed duplicate IDs + db.save_workspace(workspace.clone()).await; + db.save_workspace(workspace.clone()).await; + + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); + assert_eq!(workspace, round_trip_workspace.unwrap()); + } + + #[gpui::test] + async fn test_workspace_assignment() { + env_logger::try_init().ok(); + + let db = WorkspaceDb(open_test_db("test_basic_functionality").await); + + let workspace_1 = SerializedWorkspace { + id: 1, + location: (["/tmp", "/tmp2"]).into(), + center_group: Default::default(), + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + }; + + let mut workspace_2 = SerializedWorkspace { + id: 2, + location: (["/tmp"]).into(), + center_group: Default::default(), + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + }; + + db.save_workspace(workspace_1.clone()).await; + db.save_workspace(workspace_2.clone()).await; + + // Test that paths are treated as a set + assert_eq!( + db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), + workspace_1 + ); + assert_eq!( + db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), + workspace_1 + ); + + // Make sure that other keys work + assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); + assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); + + // Test 'mutate' case of updating a pre-existing id + workspace_2.location = (["/tmp", "/tmp2"]).into(); + + db.save_workspace(workspace_2.clone()).await; + assert_eq!( + db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), + workspace_2 + ); + + // Test other mechanism for mutating + let mut workspace_3 = SerializedWorkspace { + id: 3, + location: (&["/tmp", "/tmp2"]).into(), + center_group: Default::default(), + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + }; + + db.save_workspace(workspace_3.clone()).await; + assert_eq!( + db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), + workspace_3 + ); + + // Make sure that updating paths differently also works + workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); + db.save_workspace(workspace_3.clone()).await; + assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None); + assert_eq!( + db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"]) + .unwrap(), + workspace_3 + ); + } + + use crate::persistence::model::SerializedWorkspace; + use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; + + fn default_workspace>( + workspace_id: &[P], + center_group: &SerializedPaneGroup, + ) -> SerializedWorkspace { + SerializedWorkspace { + id: 4, + location: workspace_id.into(), + center_group: center_group.clone(), + bounds: Default::default(), + display: Default::default(), + docks: Default::default(), + } + } + + #[gpui::test] + async fn test_simple_split() { + env_logger::try_init().ok(); + + let db = WorkspaceDb(open_test_db("simple_split").await); + + // ----------------- + // | 1,2 | 5,6 | + // | - - - | | + // | 3,4 | | + // ----------------- + let center_pane = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 1, false), + SerializedItem::new("Terminal", 2, true), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 4, false), + SerializedItem::new("Terminal", 3, true), + ], + true, + )), + ], + ), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 5, true), + SerializedItem::new("Terminal", 6, false), + ], + false, + )), + ], + ); + + let workspace = default_workspace(&["/tmp"], ¢er_pane); + + db.save_workspace(workspace.clone()).await; + + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); + + assert_eq!(workspace.center_group, new_workspace.center_group); + } + + #[gpui::test] + async fn test_cleanup_panes() { + env_logger::try_init().ok(); + + let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); + + let center_pane = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 1, false), + SerializedItem::new("Terminal", 2, true), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 4, false), + SerializedItem::new("Terminal", 3, true), + ], + true, + )), + ], + ), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 5, false), + SerializedItem::new("Terminal", 6, true), + ], + false, + )), + ], + ); + + let id = &["/tmp"]; + + let mut workspace = default_workspace(id, ¢er_pane); + + db.save_workspace(workspace.clone()).await; + + workspace.center_group = group( + gpui::Axis::Vertical, + vec![ + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 1, false), + SerializedItem::new("Terminal", 2, true), + ], + false, + )), + SerializedPaneGroup::Pane(SerializedPane::new( + vec![ + SerializedItem::new("Terminal", 4, true), + SerializedItem::new("Terminal", 3, false), + ], + true, + )), + ], + ); + + db.save_workspace(workspace.clone()).await; + + let new_workspace = db.workspace_for_roots(id).unwrap(); + + assert_eq!(workspace.center_group, new_workspace.center_group); + } +} diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs new file mode 100644 index 0000000000..5f4c29cd5b --- /dev/null +++ b/crates/workspace2/src/persistence/model.rs @@ -0,0 +1,344 @@ +use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; +use anyhow::{Context, Result}; +use async_recursion::async_recursion; +use db::sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; +use gpui::{ + platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, +}; +use project::Project; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; +use util::ResultExt; +use uuid::Uuid; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceLocation(Arc>); + +impl WorkspaceLocation { + pub fn paths(&self) -> Arc> { + self.0.clone() + } +} + +impl, T: IntoIterator> From for WorkspaceLocation { + fn from(iterator: T) -> Self { + let mut roots = iterator + .into_iter() + .map(|p| p.as_ref().to_path_buf()) + .collect::>(); + roots.sort(); + Self(Arc::new(roots)) + } +} + +impl StaticColumnCount for WorkspaceLocation {} +impl Bind for &WorkspaceLocation { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + bincode::serialize(&self.0) + .expect("Bincode serialization of paths should not fail") + .bind(statement, start_index) + } +} + +impl Column for WorkspaceLocation { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let blob = statement.column_blob(start_index)?; + Ok(( + WorkspaceLocation(bincode::deserialize(blob).context("Bincode failed")?), + start_index + 1, + )) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct SerializedWorkspace { + pub id: WorkspaceId, + pub location: WorkspaceLocation, + pub center_group: SerializedPaneGroup, + pub bounds: Option, + pub display: Option, + pub docks: DockStructure, +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockStructure { + pub(crate) left: DockData, + pub(crate) right: DockData, + pub(crate) bottom: DockData, +} + +impl Column for DockStructure { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (left, next_index) = DockData::column(statement, start_index)?; + let (right, next_index) = DockData::column(statement, next_index)?; + let (bottom, next_index) = DockData::column(statement, next_index)?; + Ok(( + DockStructure { + left, + right, + bottom, + }, + next_index, + )) + } +} + +impl Bind for DockStructure { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.left, start_index)?; + let next_index = statement.bind(&self.right, next_index)?; + statement.bind(&self.bottom, next_index) + } +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct DockData { + pub(crate) visible: bool, + pub(crate) active_panel: Option, + pub(crate) zoom: bool, +} + +impl Column for DockData { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (visible, next_index) = Option::::column(statement, start_index)?; + let (active_panel, next_index) = Option::::column(statement, next_index)?; + let (zoom, next_index) = Option::::column(statement, next_index)?; + Ok(( + DockData { + visible: visible.unwrap_or(false), + active_panel, + zoom: zoom.unwrap_or(false), + }, + next_index, + )) + } +} + +impl Bind for DockData { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.visible, start_index)?; + let next_index = statement.bind(&self.active_panel, next_index)?; + statement.bind(&self.zoom, next_index) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum SerializedPaneGroup { + Group { + axis: Axis, + flexes: Option>, + children: Vec, + }, + Pane(SerializedPane), +} + +#[cfg(test)] +impl Default for SerializedPaneGroup { + fn default() -> Self { + Self::Pane(SerializedPane { + children: vec![SerializedItem::default()], + active: false, + }) + } +} + +impl SerializedPaneGroup { + #[async_recursion(?Send)] + pub(crate) async fn deserialize( + self, + project: &ModelHandle, + workspace_id: WorkspaceId, + workspace: &WeakViewHandle, + cx: &mut AsyncAppContext, + ) -> Option<( + Member, + Option>, + Vec>>, + )> { + match self { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { + let mut current_active_pane = None; + let mut members = Vec::new(); + let mut items = Vec::new(); + for child in children { + if let Some((new_member, active_pane, new_items)) = child + .deserialize(project, workspace_id, workspace, cx) + .await + { + members.push(new_member); + items.extend(new_items); + current_active_pane = current_active_pane.or(active_pane); + } + } + + if members.is_empty() { + return None; + } + + if members.len() == 1 { + return Some((members.remove(0), current_active_pane, items)); + } + + Some(( + Member::Axis(PaneAxis::load(axis, members, flexes)), + current_active_pane, + items, + )) + } + SerializedPaneGroup::Pane(serialized_pane) => { + let pane = workspace + .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade()) + .log_err()?; + let active = serialized_pane.active; + let new_items = serialized_pane + .deserialize_to(project, &pane, workspace_id, workspace, cx) + .await + .log_err()?; + + if pane + .read_with(cx, |pane, _| pane.items_len() != 0) + .log_err()? + { + let pane = pane.upgrade(cx)?; + Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) + } else { + let pane = pane.upgrade(cx)?; + workspace + .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx)) + .log_err()?; + None + } + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Default, Clone)] +pub struct SerializedPane { + pub(crate) active: bool, + pub(crate) children: Vec, +} + +impl SerializedPane { + pub fn new(children: Vec, active: bool) -> Self { + SerializedPane { children, active } + } + + pub async fn deserialize_to( + &self, + project: &ModelHandle, + pane: &WeakViewHandle, + workspace_id: WorkspaceId, + workspace: &WeakViewHandle, + cx: &mut AsyncAppContext, + ) -> Result>>> { + let mut items = Vec::new(); + let mut active_item_index = None; + for (index, item) in self.children.iter().enumerate() { + let project = project.clone(); + let item_handle = pane + .update(cx, |_, cx| { + if let Some(deserializer) = cx.global::().get(&item.kind) { + deserializer(project, workspace.clone(), workspace_id, item.item_id, cx) + } else { + Task::ready(Err(anyhow::anyhow!( + "Deserializer does not exist for item kind: {}", + item.kind + ))) + } + })? + .await + .log_err(); + + items.push(item_handle.clone()); + + if let Some(item_handle) = item_handle { + pane.update(cx, |pane, cx| { + pane.add_item(item_handle.clone(), true, true, None, cx); + })?; + } + + if item.active { + active_item_index = Some(index); + } + } + + if let Some(active_item_index) = active_item_index { + pane.update(cx, |pane, cx| { + pane.activate_item(active_item_index, false, false, cx); + })?; + } + + anyhow::Ok(items) + } +} + +pub type GroupId = i64; +pub type PaneId = i64; +pub type ItemId = usize; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct SerializedItem { + pub kind: Arc, + pub item_id: ItemId, + pub active: bool, +} + +impl SerializedItem { + pub fn new(kind: impl AsRef, item_id: ItemId, active: bool) -> Self { + Self { + kind: Arc::from(kind.as_ref()), + item_id, + active, + } + } +} + +#[cfg(test)] +impl Default for SerializedItem { + fn default() -> Self { + SerializedItem { + kind: Arc::from("Terminal"), + item_id: 100000, + active: false, + } + } +} + +impl StaticColumnCount for SerializedItem { + fn column_count() -> usize { + 3 + } +} +impl Bind for &SerializedItem { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let next_index = statement.bind(&self.kind, start_index)?; + let next_index = statement.bind(&self.item_id, next_index)?; + statement.bind(&self.active, next_index) + } +} + +impl Column for SerializedItem { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (kind, next_index) = Arc::::column(statement, start_index)?; + let (item_id, next_index) = ItemId::column(statement, next_index)?; + let (active, next_index) = bool::column(statement, next_index)?; + Ok(( + SerializedItem { + kind, + item_id, + active, + }, + next_index, + )) + } +} diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs new file mode 100644 index 0000000000..ddde5c3554 --- /dev/null +++ b/crates/workspace2/src/searchable.rs @@ -0,0 +1,282 @@ +use std::{any::Any, sync::Arc}; + +use gpui::{ + AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle, + WeakViewHandle, WindowContext, +}; +use project::search::SearchQuery; + +use crate::{item::WeakItemHandle, Item, ItemHandle}; + +#[derive(Debug)] +pub enum SearchEvent { + MatchesInvalidated, + ActiveMatchChanged, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Direction { + Prev, + Next, +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct SearchOptions { + pub case: bool, + pub word: bool, + pub regex: bool, + /// Specifies whether the item supports search & replace. + pub replacement: bool, +} + +pub trait SearchableItem: Item { + type Match: Any + Sync + Send + Clone; + + fn supported_options() -> SearchOptions { + SearchOptions { + case: true, + word: true, + regex: true, + replacement: true, + } + } + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option; + fn clear_matches(&mut self, cx: &mut ViewContext); + fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext); + fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; + fn activate_match( + &mut self, + index: usize, + matches: Vec, + cx: &mut ViewContext, + ); + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext); + fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext); + fn match_index_for_direction( + &mut self, + matches: &Vec, + current_index: usize, + direction: Direction, + count: usize, + _: &mut ViewContext, + ) -> usize { + match direction { + Direction::Prev => { + let count = count % matches.len(); + if current_index >= count { + current_index - count + } else { + matches.len() - (count - current_index) + } + } + Direction::Next => (current_index + count) % matches.len(), + } + } + fn find_matches( + &mut self, + query: Arc, + cx: &mut ViewContext, + ) -> Task>; + fn active_match_index( + &mut self, + matches: Vec, + cx: &mut ViewContext, + ) -> Option; +} + +pub trait SearchableItemHandle: ItemHandle { + fn downgrade(&self) -> Box; + fn boxed_clone(&self) -> Box; + fn supported_options(&self) -> SearchOptions; + fn subscribe_to_search_events( + &self, + cx: &mut WindowContext, + handler: Box, + ) -> Subscription; + fn clear_matches(&self, cx: &mut WindowContext); + fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext); + fn query_suggestion(&self, cx: &mut WindowContext) -> String; + fn activate_match( + &self, + index: usize, + matches: &Vec>, + cx: &mut WindowContext, + ); + fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext); + fn replace(&self, _: &Box, _: &SearchQuery, _: &mut WindowContext); + fn match_index_for_direction( + &self, + matches: &Vec>, + current_index: usize, + direction: Direction, + count: usize, + cx: &mut WindowContext, + ) -> usize; + fn find_matches( + &self, + query: Arc, + cx: &mut WindowContext, + ) -> Task>>; + fn active_match_index( + &self, + matches: &Vec>, + cx: &mut WindowContext, + ) -> Option; +} + +impl SearchableItemHandle for ViewHandle { + fn downgrade(&self) -> Box { + Box::new(self.downgrade()) + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + + fn supported_options(&self) -> SearchOptions { + T::supported_options() + } + + fn subscribe_to_search_events( + &self, + cx: &mut WindowContext, + handler: Box, + ) -> Subscription { + cx.subscribe(self, move |handle, event, cx| { + let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx)); + if let Some(search_event) = search_event { + handler(search_event, cx) + } + }) + } + + fn clear_matches(&self, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.clear_matches(cx)); + } + fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext) { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.update_matches(matches, cx)); + } + fn query_suggestion(&self, cx: &mut WindowContext) -> String { + self.update(cx, |this, cx| this.query_suggestion(cx)) + } + fn activate_match( + &self, + index: usize, + matches: &Vec>, + cx: &mut WindowContext, + ) { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.activate_match(index, matches, cx)); + } + + fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext) { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.select_matches(matches, cx)); + } + + fn match_index_for_direction( + &self, + matches: &Vec>, + current_index: usize, + direction: Direction, + count: usize, + cx: &mut WindowContext, + ) -> usize { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| { + this.match_index_for_direction(&matches, current_index, direction, count, cx) + }) + } + fn find_matches( + &self, + query: Arc, + cx: &mut WindowContext, + ) -> Task>> { + let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); + cx.foreground().spawn(async { + let matches = matches.await; + matches + .into_iter() + .map::, _>(|range| Box::new(range)) + .collect() + }) + } + fn active_match_index( + &self, + matches: &Vec>, + cx: &mut WindowContext, + ) -> Option { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.active_match_index(matches, cx)) + } + + fn replace(&self, matches: &Box, query: &SearchQuery, cx: &mut WindowContext) { + let matches = matches.downcast_ref().unwrap(); + self.update(cx, |this, cx| this.replace(matches, query, cx)) + } +} + +fn downcast_matches(matches: &Vec>) -> Vec { + matches + .iter() + .map(|range| range.downcast_ref::().cloned()) + .collect::>>() + .expect( + "SearchableItemHandle function called with vec of matches of a different type than expected", + ) +} + +impl From> for AnyViewHandle { + fn from(this: Box) -> Self { + this.as_any().clone() + } +} + +impl From<&Box> for AnyViewHandle { + fn from(this: &Box) -> Self { + this.as_any().clone() + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() && self.window() == other.window() + } +} + +impl Eq for Box {} + +pub trait WeakSearchableItemHandle: WeakItemHandle { + fn upgrade(&self, cx: &AppContext) -> Option>; + + fn into_any(self) -> AnyWeakViewHandle; +} + +impl WeakSearchableItemHandle for WeakViewHandle { + fn upgrade(&self, cx: &AppContext) -> Option> { + Some(Box::new(self.upgrade(cx)?)) + } + + fn into_any(self) -> AnyWeakViewHandle { + self.into_any() + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() && self.window() == other.window() + } +} + +impl Eq for Box {} + +impl std::hash::Hash for Box { + fn hash(&self, state: &mut H) { + (self.id(), self.window().id()).hash(state) + } +} diff --git a/crates/workspace2/src/shared_screen.rs b/crates/workspace2/src/shared_screen.rs new file mode 100644 index 0000000000..b99c5f3ab9 --- /dev/null +++ b/crates/workspace2/src/shared_screen.rs @@ -0,0 +1,151 @@ +use crate::{ + item::{Item, ItemEvent}, + ItemNavHistory, WorkspaceId, +}; +use anyhow::Result; +use call::participant::{Frame, RemoteVideoTrack}; +use client::{proto::PeerId, User}; +use futures::StreamExt; +use gpui::{ + elements::*, + geometry::{rect::RectF, vector::vec2f}, + platform::MouseButton, + AppContext, Entity, Task, View, ViewContext, +}; +use smallvec::SmallVec; +use std::{ + borrow::Cow, + sync::{Arc, Weak}, +}; + +pub enum Event { + Close, +} + +pub struct SharedScreen { + track: Weak, + frame: Option, + pub peer_id: PeerId, + user: Arc, + nav_history: Option, + _maintain_frame: Task>, +} + +impl SharedScreen { + pub fn new( + track: &Arc, + peer_id: PeerId, + user: Arc, + cx: &mut ViewContext, + ) -> Self { + let mut frames = track.frames(); + Self { + track: Arc::downgrade(track), + frame: None, + peer_id, + user, + nav_history: Default::default(), + _maintain_frame: cx.spawn(|this, mut cx| async move { + while let Some(frame) = frames.next().await { + this.update(&mut cx, |this, cx| { + this.frame = Some(frame); + cx.notify(); + })?; + } + this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; + Ok(()) + }), + } + } +} + +impl Entity for SharedScreen { + type Event = Event; +} + +impl View for SharedScreen { + fn ui_name() -> &'static str { + "SharedScreen" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + enum Focus {} + + let frame = self.frame.clone(); + MouseEventHandler::new::(0, cx, |_, cx| { + Canvas::new(move |bounds, _, _, cx| { + if let Some(frame) = frame.clone() { + let size = constrain_size_preserving_aspect_ratio( + bounds.size(), + vec2f(frame.width() as f32, frame.height() as f32), + ); + let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.; + cx.scene().push_surface(gpui::platform::mac::Surface { + bounds: RectF::new(origin, size), + image_buffer: frame.image(), + }); + } + }) + .contained() + .with_style(theme::current(cx).shared_screen) + }) + .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) + .into_any() + } +} + +impl Item for SharedScreen { + fn tab_tooltip_text(&self, _: &AppContext) -> Option> { + Some(format!("{}'s screen", self.user.github_login).into()) + } + fn deactivated(&mut self, cx: &mut ViewContext) { + if let Some(nav_history) = self.nav_history.as_mut() { + nav_history.push::<()>(None, cx); + } + } + + fn tab_content( + &self, + _: Option, + style: &theme::Tab, + _: &AppContext, + ) -> gpui::AnyElement { + Flex::row() + .with_child( + Svg::new("icons/desktop.svg") + .with_color(style.label.text.color) + .constrained() + .with_width(style.type_icon_width) + .aligned() + .contained() + .with_margin_right(style.spacing), + ) + .with_child( + Label::new( + format!("{}'s screen", self.user.github_login), + style.label.clone(), + ) + .aligned(), + ) + .into_any() + } + + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); + } + + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option { + let track = self.track.upgrade()?; + Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) + } + + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + match event { + Event::Close => smallvec::smallvec!(ItemEvent::CloseItem), + } + } +} diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs new file mode 100644 index 0000000000..b62dae2114 --- /dev/null +++ b/crates/workspace2/src/status_bar.rs @@ -0,0 +1,271 @@ +use std::ops::Range; + +use crate::{ItemHandle, Pane}; +use gpui::{ + elements::*, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::{json, ToJson}, + AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle, + WindowContext, +}; + +pub trait StatusItemView: View { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn crate::ItemHandle>, + cx: &mut ViewContext, + ); +} + +trait StatusItemViewHandle { + fn as_any(&self) -> &AnyViewHandle; + fn set_active_pane_item( + &self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut WindowContext, + ); + fn ui_name(&self) -> &'static str; +} + +pub struct StatusBar { + left_items: Vec>, + right_items: Vec>, + active_pane: ViewHandle, + _observe_active_pane: Subscription, +} + +impl Entity for StatusBar { + type Event = (); +} + +impl View for StatusBar { + fn ui_name() -> &'static str { + "StatusBar" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + let theme = &theme::current(cx).workspace.status_bar; + + StatusBarElement { + left: Flex::row() + .with_children(self.left_items.iter().map(|i| { + ChildView::new(i.as_any(), cx) + .aligned() + .contained() + .with_margin_right(theme.item_spacing) + })) + .into_any(), + right: Flex::row() + .with_children(self.right_items.iter().rev().map(|i| { + ChildView::new(i.as_any(), cx) + .aligned() + .contained() + .with_margin_left(theme.item_spacing) + })) + .into_any(), + } + .contained() + .with_style(theme.container) + .constrained() + .with_height(theme.height) + .into_any() + } +} + +impl StatusBar { + pub fn new(active_pane: &ViewHandle, cx: &mut ViewContext) -> Self { + let mut this = Self { + left_items: Default::default(), + right_items: Default::default(), + active_pane: active_pane.clone(), + _observe_active_pane: cx + .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)), + }; + this.update_active_pane_item(cx); + this + } + + pub fn add_left_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + where + T: 'static + StatusItemView, + { + self.left_items.push(Box::new(item)); + cx.notify(); + } + + pub fn item_of_type(&self) -> Option> { + self.left_items + .iter() + .chain(self.right_items.iter()) + .find_map(|item| item.as_any().clone().downcast()) + } + + pub fn position_of_item(&self) -> Option + where + T: StatusItemView, + { + for (index, item) in self.left_items.iter().enumerate() { + if item.as_ref().ui_name() == T::ui_name() { + return Some(index); + } + } + for (index, item) in self.right_items.iter().enumerate() { + if item.as_ref().ui_name() == T::ui_name() { + return Some(index + self.left_items.len()); + } + } + return None; + } + + pub fn insert_item_after( + &mut self, + position: usize, + item: ViewHandle, + cx: &mut ViewContext, + ) where + T: 'static + StatusItemView, + { + if position < self.left_items.len() { + self.left_items.insert(position + 1, Box::new(item)) + } else { + self.right_items + .insert(position + 1 - self.left_items.len(), Box::new(item)) + } + cx.notify() + } + + pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext) { + if position < self.left_items.len() { + self.left_items.remove(position); + } else { + self.right_items.remove(position - self.left_items.len()); + } + cx.notify(); + } + + pub fn add_right_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + where + T: 'static + StatusItemView, + { + self.right_items.push(Box::new(item)); + cx.notify(); + } + + pub fn set_active_pane(&mut self, active_pane: &ViewHandle, cx: &mut ViewContext) { + self.active_pane = active_pane.clone(); + self._observe_active_pane = + cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)); + self.update_active_pane_item(cx); + } + + fn update_active_pane_item(&mut self, cx: &mut ViewContext) { + let active_pane_item = self.active_pane.read(cx).active_item(); + for item in self.left_items.iter().chain(&self.right_items) { + item.set_active_pane_item(active_pane_item.as_deref(), cx); + } + } +} + +impl StatusItemViewHandle for ViewHandle { + fn as_any(&self) -> &AnyViewHandle { + self + } + + fn set_active_pane_item( + &self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut WindowContext, + ) { + self.update(cx, |this, cx| { + this.set_active_pane_item(active_pane_item, cx) + }); + } + + fn ui_name(&self) -> &'static str { + T::ui_name() + } +} + +impl From<&dyn StatusItemViewHandle> for AnyViewHandle { + fn from(val: &dyn StatusItemViewHandle) -> Self { + val.as_any().clone() + } +} + +struct StatusBarElement { + left: AnyElement, + right: AnyElement, +} + +impl Element for StatusBarElement { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + mut constraint: SizeConstraint, + view: &mut StatusBar, + cx: &mut ViewContext, + ) -> (Vector2F, Self::LayoutState) { + let max_width = constraint.max.x(); + constraint.min = vec2f(0., constraint.min.y()); + + let right_size = self.right.layout(constraint, view, cx); + let constraint = SizeConstraint::new( + vec2f(0., constraint.min.y()), + vec2f(max_width - right_size.x(), constraint.max.y()), + ); + + self.left.layout(constraint, view, cx); + + (vec2f(max_width, right_size.y()), ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _: &mut Self::LayoutState, + view: &mut StatusBar, + cx: &mut ViewContext, + ) -> Self::PaintState { + let origin_y = bounds.upper_right().y(); + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + let left_origin = vec2f(bounds.lower_left().x(), origin_y); + self.left.paint(left_origin, visible_bounds, view, cx); + + let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y); + self.right.paint(right_origin, visible_bounds, view, cx); + } + + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &StatusBar, + _: &ViewContext, + ) -> Option { + None + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &StatusBar, + _: &ViewContext, + ) -> serde_json::Value { + json!({ + "type": "StatusBarElement", + "bounds": bounds.to_json() + }) + } +} diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs new file mode 100644 index 0000000000..c3f4bb9723 --- /dev/null +++ b/crates/workspace2/src/toolbar.rs @@ -0,0 +1,301 @@ +use crate::ItemHandle; +use gpui::{ + elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle, + WindowContext, +}; + +pub trait ToolbarItemView: View { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn crate::ItemHandle>, + cx: &mut ViewContext, + ) -> ToolbarItemLocation; + + fn location_for_event( + &self, + _event: &Self::Event, + current_location: ToolbarItemLocation, + _cx: &AppContext, + ) -> ToolbarItemLocation { + current_location + } + + fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext) {} + + /// Number of times toolbar's height will be repeated to get the effective height. + /// Useful when multiple rows one under each other are needed. + /// The rows have the same width and act as a whole when reacting to resizes and similar events. + fn row_count(&self, _cx: &ViewContext) -> usize { + 1 + } +} + +trait ToolbarItemViewHandle { + fn id(&self) -> usize; + fn as_any(&self) -> &AnyViewHandle; + fn set_active_pane_item( + &self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut WindowContext, + ) -> ToolbarItemLocation; + fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext); + fn row_count(&self, cx: &WindowContext) -> usize; +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ToolbarItemLocation { + Hidden, + PrimaryLeft { flex: Option<(f32, bool)> }, + PrimaryRight { flex: Option<(f32, bool)> }, + Secondary, +} + +pub struct Toolbar { + active_item: Option>, + hidden: bool, + can_navigate: bool, + items: Vec<(Box, ToolbarItemLocation)>, +} + +impl Entity for Toolbar { + type Event = (); +} + +impl View for Toolbar { + fn ui_name() -> &'static str { + "Toolbar" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + let theme = &theme::current(cx).workspace.toolbar; + + let mut primary_left_items = Vec::new(); + let mut primary_right_items = Vec::new(); + let mut secondary_item = None; + let spacing = theme.item_spacing; + let mut primary_items_row_count = 1; + + for (item, position) in &self.items { + match *position { + ToolbarItemLocation::Hidden => {} + + ToolbarItemLocation::PrimaryLeft { flex } => { + primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); + let left_item = ChildView::new(item.as_any(), cx).aligned(); + if let Some((flex, expanded)) = flex { + primary_left_items.push(left_item.flex(flex, expanded).into_any()); + } else { + primary_left_items.push(left_item.into_any()); + } + } + + ToolbarItemLocation::PrimaryRight { flex } => { + primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); + let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float(); + if let Some((flex, expanded)) = flex { + primary_right_items.push(right_item.flex(flex, expanded).into_any()); + } else { + primary_right_items.push(right_item.into_any()); + } + } + + ToolbarItemLocation::Secondary => { + secondary_item = Some( + ChildView::new(item.as_any(), cx) + .constrained() + .with_height(theme.height * item.row_count(cx) as f32) + .into_any(), + ); + } + } + } + + let container_style = theme.container; + let height = theme.height * primary_items_row_count as f32; + + let mut primary_items = Flex::row().with_spacing(spacing); + primary_items.extend(primary_left_items); + primary_items.extend(primary_right_items); + + let mut toolbar = Flex::column(); + if !primary_items.is_empty() { + toolbar.add_child(primary_items.constrained().with_height(height)); + } + if let Some(secondary_item) = secondary_item { + toolbar.add_child(secondary_item); + } + + if toolbar.is_empty() { + toolbar.into_any_named("toolbar") + } else { + toolbar + .contained() + .with_style(container_style) + .into_any_named("toolbar") + } + } +} + +// <<<<<<< HEAD +// ======= +// #[allow(clippy::too_many_arguments)] +// fn nav_button)>( +// svg_path: &'static str, +// style: theme::Interactive, +// nav_button_height: f32, +// tooltip_style: TooltipStyle, +// enabled: bool, +// spacing: f32, +// on_click: F, +// tooltip_action: A, +// action_name: &'static str, +// cx: &mut ViewContext, +// ) -> AnyElement { +// MouseEventHandler::new::(0, cx, |state, _| { +// let style = if enabled { +// style.style_for(state) +// } else { +// style.disabled_style() +// }; +// Svg::new(svg_path) +// .with_color(style.color) +// .constrained() +// .with_width(style.icon_width) +// .aligned() +// .contained() +// .with_style(style.container) +// .constrained() +// .with_width(style.button_width) +// .with_height(nav_button_height) +// .aligned() +// .top() +// }) +// .with_cursor_style(if enabled { +// CursorStyle::PointingHand +// } else { +// CursorStyle::default() +// }) +// .on_click(MouseButton::Left, move |_, toolbar, cx| { +// on_click(toolbar, cx) +// }) +// .with_tooltip::( +// 0, +// action_name, +// Some(Box::new(tooltip_action)), +// tooltip_style, +// cx, +// ) +// .contained() +// .with_margin_right(spacing) +// .into_any_named("nav button") +// } + +// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e +impl Toolbar { + pub fn new() -> Self { + Self { + active_item: None, + items: Default::default(), + hidden: false, + can_navigate: true, + } + } + + pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { + self.can_navigate = can_navigate; + cx.notify(); + } + + pub fn add_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + where + T: 'static + ToolbarItemView, + { + let location = item.set_active_pane_item(self.active_item.as_deref(), cx); + cx.subscribe(&item, |this, item, event, cx| { + if let Some((_, current_location)) = + this.items.iter_mut().find(|(i, _)| i.id() == item.id()) + { + let new_location = item + .read(cx) + .location_for_event(event, *current_location, cx); + if new_location != *current_location { + *current_location = new_location; + cx.notify(); + } + } + }) + .detach(); + self.items.push((Box::new(item), location)); + cx.notify(); + } + + pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext) { + self.active_item = item.map(|item| item.boxed_clone()); + self.hidden = self + .active_item + .as_ref() + .map(|item| !item.show_toolbar(cx)) + .unwrap_or(false); + + for (toolbar_item, current_location) in self.items.iter_mut() { + let new_location = toolbar_item.set_active_pane_item(item, cx); + if new_location != *current_location { + *current_location = new_location; + cx.notify(); + } + } + } + + pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext) { + for (toolbar_item, _) in self.items.iter_mut() { + toolbar_item.focus_changed(focused, cx); + } + } + + pub fn item_of_type(&self) -> Option> { + self.items + .iter() + .find_map(|(item, _)| item.as_any().clone().downcast()) + } + + pub fn hidden(&self) -> bool { + self.hidden + } +} + +impl ToolbarItemViewHandle for ViewHandle { + fn id(&self) -> usize { + self.id() + } + + fn as_any(&self) -> &AnyViewHandle { + self + } + + fn set_active_pane_item( + &self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut WindowContext, + ) -> ToolbarItemLocation { + self.update(cx, |this, cx| { + this.set_active_pane_item(active_pane_item, cx) + }) + } + + fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) { + self.update(cx, |this, cx| { + this.pane_focus_update(pane_focused, cx); + cx.notify(); + }); + } + + fn row_count(&self, cx: &WindowContext) -> usize { + self.read_with(cx, |this, cx| this.row_count(cx)) + } +} + +impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { + fn from(val: &dyn ToolbarItemViewHandle) -> Self { + val.as_any().clone() + } +} diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs new file mode 100644 index 0000000000..607bc5b61c --- /dev/null +++ b/crates/workspace2/src/workspace2.rs @@ -0,0 +1,5520 @@ +// pub mod dock; +// pub mod item; +// pub mod notifications; +// pub mod pane; +// pub mod pane_group; +// mod persistence; +// pub mod searchable; +// pub mod shared_screen; +// mod status_bar; +// mod toolbar; +// mod workspace_settings; + +// use anyhow::{anyhow, Context, Result}; +// use call::ActiveCall; +// use client::{ +// 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::{ +// actions, +// elements::*, +// geometry::{ +// rect::RectF, +// vector::{vec2f, Vector2F}, +// }, +// impl_actions, +// platform::{ +// CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel, +// WindowBounds, WindowOptions, +// }, +// AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext, +// Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, +// ViewHandle, WeakViewHandle, WindowContext, WindowHandle, +// }; +// 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 crate::{ +// notifications::{simple_message_notification::MessageNotification, NotificationTracker}, +// persistence::model::{ +// DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, +// }, +// }; +// use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; +// use lazy_static::lazy_static; +// use notifications::{NotificationHandle, NotifyResultExt}; +// pub use pane::*; +// pub use pane_group::*; +// use persistence::{model::SerializedItem, DB}; +// pub use persistence::{ +// model::{ItemId, WorkspaceLocation}, +// WorkspaceDb, DB as WORKSPACE_DB, +// }; +// use postage::prelude::Stream; +// use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; +// use serde::Deserialize; +// use shared_screen::SharedScreen; +// use status_bar::StatusBar; +// pub use status_bar::StatusItemView; +// use theme::{Theme, ThemeSettings}; +// pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; +// use util::ResultExt; +// pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings}; + +// lazy_static! { +// static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") +// .ok() +// .as_deref() +// .and_then(parse_pixel_position_env_var); +// static ref ZED_WINDOW_POSITION: Option = env::var("ZED_WINDOW_POSITION") +// .ok() +// .as_deref() +// .and_then(parse_pixel_position_env_var); +// } + +// pub trait Modal: View { +// fn has_focus(&self) -> bool; +// fn dismiss_on_event(event: &Self::Event) -> bool; +// } + +// trait ModalHandle { +// fn as_any(&self) -> &AnyViewHandle; +// fn has_focus(&self, cx: &WindowContext) -> bool; +// } + +// impl ModalHandle for ViewHandle { +// fn as_any(&self) -> &AnyViewHandle { +// self +// } + +// fn has_focus(&self, cx: &WindowContext) -> bool { +// self.read(cx).has_focus() +// } +// } + +// #[derive(Clone, PartialEq)] +// pub struct RemoveWorktreeFromProject(pub WorktreeId); + +// actions!( +// workspace, +// [ +// Open, +// NewFile, +// NewWindow, +// CloseWindow, +// CloseInactiveTabsAndPanes, +// AddFolderToProject, +// Unfollow, +// SaveAs, +// ReloadActiveItem, +// ActivatePreviousPane, +// ActivateNextPane, +// FollowNextCollaborator, +// NewTerminal, +// NewCenterTerminal, +// ToggleTerminalFocus, +// NewSearch, +// Feedback, +// Restart, +// Welcome, +// ToggleZoom, +// ToggleLeftDock, +// ToggleRightDock, +// ToggleBottomDock, +// CloseAllDocks, +// ] +// ); + +// #[derive(Clone, PartialEq)] +// pub struct OpenPaths { +// pub paths: Vec, +// } + +// #[derive(Clone, Deserialize, PartialEq)] +// pub struct ActivatePane(pub usize); + +// #[derive(Clone, Deserialize, PartialEq)] +// pub struct ActivatePaneInDirection(pub SplitDirection); + +// #[derive(Clone, Deserialize, PartialEq)] +// pub struct SwapPaneInDirection(pub SplitDirection); + +// #[derive(Clone, Deserialize, PartialEq)] +// pub struct NewFileInDirection(pub SplitDirection); + +// #[derive(Clone, PartialEq, Debug, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct SaveAll { +// pub save_intent: Option, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct Save { +// pub save_intent: Option, +// } + +// #[derive(Clone, PartialEq, Debug, Deserialize, Default)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseAllItemsAndPanes { +// pub save_intent: Option, +// } + +// #[derive(Deserialize)] +// pub struct Toast { +// id: usize, +// msg: Cow<'static, str>, +// #[serde(skip)] +// on_click: Option<(Cow<'static, str>, Arc)>, +// } + +// impl Toast { +// pub fn new>>(id: usize, msg: I) -> Self { +// Toast { +// id, +// msg: msg.into(), +// on_click: None, +// } +// } + +// pub fn on_click(mut self, message: M, on_click: F) -> Self +// where +// M: Into>, +// F: Fn(&mut WindowContext) + 'static, +// { +// self.on_click = Some((message.into(), Arc::new(on_click))); +// self +// } +// } + +// impl PartialEq for Toast { +// fn eq(&self, other: &Self) -> bool { +// self.id == other.id +// && self.msg == other.msg +// && self.on_click.is_some() == other.on_click.is_some() +// } +// } + +// impl Clone for Toast { +// fn clone(&self) -> Self { +// Toast { +// id: self.id, +// msg: self.msg.to_owned(), +// on_click: self.on_click.clone(), +// } +// } +// } + +// #[derive(Clone, Deserialize, PartialEq)] +// pub struct OpenTerminal { +// pub working_directory: PathBuf, +// } + +// impl_actions!( +// workspace, +// [ +// ActivatePane, +// ActivatePaneInDirection, +// SwapPaneInDirection, +// NewFileInDirection, +// Toast, +// OpenTerminal, +// SaveAll, +// Save, +// CloseAllItemsAndPanes, +// ] +// ); + +// pub type WorkspaceId = i64; + +// pub fn init_settings(cx: &mut AppContext) { +// settings::register::(cx); +// settings::register::(cx); +// } + +// pub fn init(app_state: Arc, cx: &mut AppContext) { +// init_settings(cx); +// pane::init(cx); +// notifications::init(cx); + +// cx.add_global_action({ +// let app_state = Arc::downgrade(&app_state); +// move |_: &Open, cx: &mut AppContext| { +// let mut paths = cx.prompt_for_paths(PathPromptOptions { +// files: true, +// directories: true, +// multiple: true, +// }); + +// if let Some(app_state) = app_state.upgrade() { +// cx.spawn(move |mut cx| async move { +// if let Some(paths) = paths.recv().await.flatten() { +// cx.update(|cx| { +// open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx) +// }); +// } +// }) +// .detach(); +// } +// } +// }); +// cx.add_async_action(Workspace::open); + +// cx.add_async_action(Workspace::follow_next_collaborator); +// cx.add_async_action(Workspace::close); +// cx.add_async_action(Workspace::close_inactive_items_and_panes); +// cx.add_async_action(Workspace::close_all_items_and_panes); +// cx.add_global_action(Workspace::close_global); +// cx.add_global_action(restart); +// cx.add_async_action(Workspace::save_all); +// cx.add_action(Workspace::add_folder_to_project); +// cx.add_action( +// |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext| { +// let pane = workspace.active_pane().clone(); +// workspace.unfollow(&pane, cx); +// }, +// ); +// cx.add_action( +// |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext| { +// workspace +// .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx) +// .detach_and_log_err(cx); +// }, +// ); +// cx.add_action( +// |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext| { +// workspace +// .save_active_item(SaveIntent::SaveAs, cx) +// .detach_and_log_err(cx); +// }, +// ); +// cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { +// workspace.activate_previous_pane(cx) +// }); +// cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { +// workspace.activate_next_pane(cx) +// }); + +// cx.add_action( +// |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| { +// workspace.activate_pane_in_direction(action.0, cx) +// }, +// ); + +// cx.add_action( +// |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| { +// workspace.swap_pane_in_direction(action.0, cx) +// }, +// ); + +// cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { +// workspace.toggle_dock(DockPosition::Left, cx); +// }); +// cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { +// workspace.toggle_dock(DockPosition::Right, cx); +// }); +// cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { +// workspace.toggle_dock(DockPosition::Bottom, cx); +// }); +// cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| { +// workspace.close_all_docks(cx); +// }); +// cx.add_action(Workspace::activate_pane_at_index); +// cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { +// workspace.reopen_closed_item(cx).detach(); +// }); +// cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { +// workspace +// .go_back(workspace.active_pane().downgrade(), cx) +// .detach(); +// }); +// cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { +// workspace +// .go_forward(workspace.active_pane().downgrade(), cx) +// .detach(); +// }); + +// cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { +// cx.spawn(|workspace, mut cx| async move { +// let err = install_cli::install_cli(&cx) +// .await +// .context("Failed to create CLI symlink"); + +// workspace.update(&mut cx, |workspace, cx| { +// if matches!(err, Err(_)) { +// err.notify_err(workspace, cx); +// } else { +// workspace.show_notification(1, cx, |cx| { +// cx.add_view(|_| { +// MessageNotification::new("Successfully installed the `zed` binary") +// }) +// }); +// } +// }) +// }) +// .detach(); +// }); +// } + +// type ProjectItemBuilders = HashMap< +// TypeId, +// fn(ModelHandle, AnyModelHandle, &mut ViewContext) -> Box, +// >; +// pub fn register_project_item(cx: &mut AppContext) { +// cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { +// builders.insert(TypeId::of::(), |project, model, cx| { +// let item = model.downcast::().unwrap(); +// Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) +// }); +// }); +// } + +// type FollowableItemBuilder = fn( +// ViewHandle, +// ViewHandle, +// ViewId, +// &mut Option, +// &mut AppContext, +// ) -> Option>>>; +// type FollowableItemBuilders = HashMap< +// TypeId, +// ( +// FollowableItemBuilder, +// fn(&AnyViewHandle) -> Box, +// ), +// >; +// pub fn register_followable_item(cx: &mut AppContext) { +// cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { +// builders.insert( +// TypeId::of::(), +// ( +// |pane, workspace, id, state, cx| { +// I::from_state_proto(pane, workspace, id, state, cx).map(|task| { +// cx.foreground() +// .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) +// }) +// }, +// |this| Box::new(this.clone().downcast::().unwrap()), +// ), +// ); +// }); +// } + +// type ItemDeserializers = HashMap< +// Arc, +// fn( +// ModelHandle, +// WeakViewHandle, +// WorkspaceId, +// ItemId, +// &mut ViewContext, +// ) -> Task>>, +// >; +// pub fn register_deserializable_item(cx: &mut AppContext) { +// cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| { +// if let Some(serialized_item_kind) = I::serialized_item_kind() { +// deserializers.insert( +// Arc::from(serialized_item_kind), +// |project, workspace, workspace_id, item_id, cx| { +// let task = I::deserialize(project, workspace, workspace_id, item_id, cx); +// cx.foreground() +// .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) +// }, +// ); +// } +// }); +// } + +// pub struct AppState { +// pub languages: Arc, +// pub client: Arc, +// pub user_store: 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, +// } + +// impl AppState { +// #[cfg(any(test, feature = "test-support"))] +// pub fn test(cx: &mut AppContext) -> Arc { +// use node_runtime::FakeNodeRuntime; +// use settings::SettingsStore; + +// if !cx.has_global::() { +// cx.set_global(SettingsStore::test(cx)); +// } + +// let fs = fs::FakeFs::new(cx.background().clone()); +// let languages = Arc::new(LanguageRegistry::test()); +// let http_client = util::http::FakeHttpClient::with_404_response(); +// let client = Client::new(http_client.clone(), cx); +// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); +// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); + +// theme::init((), cx); +// client::init(&client, cx); +// crate::init_settings(cx); + +// Arc::new(Self { +// client, +// fs, +// languages, +// user_store, +// // channel_store, +// workspace_store, +// node_runtime: FakeNodeRuntime::new(), +// initialize_workspace: |_, _, _, _| Task::ready(Ok(())), +// build_window_options: |_, _, _| Default::default(), +// background_actions: || &[], +// }) +// } +// } + +// struct DelayedDebouncedEditAction { +// task: Option>, +// cancel_channel: Option>, +// } + +// impl DelayedDebouncedEditAction { +// fn new() -> DelayedDebouncedEditAction { +// DelayedDebouncedEditAction { +// task: None, +// cancel_channel: None, +// } +// } + +// fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) +// where +// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, +// { +// if let Some(channel) = self.cancel_channel.take() { +// _ = channel.send(()); +// } + +// let (sender, mut receiver) = oneshot::channel::<()>(); +// self.cancel_channel = Some(sender); + +// let previous_task = self.task.take(); +// self.task = Some(cx.spawn(|workspace, mut cx| async move { +// let mut timer = cx.background().timer(delay).fuse(); +// if let Some(previous_task) = previous_task { +// previous_task.await; +// } + +// futures::select_biased! { +// _ = receiver => return, +// _ = timer => {} +// } + +// if let Some(result) = workspace +// .update(&mut cx, |workspace, cx| (func)(workspace, cx)) +// .log_err() +// { +// result.await.log_err(); +// } +// })); +// } +// } + +// pub enum Event { +// PaneAdded(ViewHandle), +// ContactRequestedJoin(u64), +// } + +// pub struct Workspace { +// weak_self: 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, +// } + +// struct ActiveModal { +// view: Box, +// previously_focused_view_id: Option, +// } + +// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +// pub struct ViewId { +// pub creator: PeerId, +// pub id: u64, +// } + +// #[derive(Default)] +// struct FollowerState { +// leader_id: PeerId, +// active_view_id: Option, +// items_by_leader_view_id: HashMap>, +// } + +// enum WorkspaceBounds {} + +// impl Workspace { +// pub fn new( +// workspace_id: WorkspaceId, +// project: ModelHandle, +// app_state: Arc, +// cx: &mut ViewContext, +// ) -> Self { +// cx.observe(&project, |_, _, cx| cx.notify()).detach(); +// cx.subscribe(&project, move |this, _, event, cx| { +// match event { +// project::Event::RemoteIdChanged(_) => { +// this.update_window_title(cx); +// } + +// project::Event::CollaboratorLeft(peer_id) => { +// this.collaborator_left(*peer_id, cx); +// } + +// project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { +// this.update_window_title(cx); +// this.serialize_workspace(cx); +// } + +// project::Event::DisconnectedFromHost => { +// this.update_window_edited(cx); +// cx.blur(); +// } + +// project::Event::Closed => { +// cx.remove_window(); +// } + +// project::Event::DeletedEntry(entry_id) => { +// for pane in this.panes.iter() { +// pane.update(cx, |pane, cx| { +// pane.handle_deleted_project_item(*entry_id, cx) +// }); +// } +// } + +// project::Event::Notification(message) => this.show_notification(0, cx, |cx| { +// cx.add_view(|_| MessageNotification::new(message.clone())) +// }), + +// _ => {} +// } +// cx.notify() +// }) +// .detach(); + +// let weak_handle = cx.weak_handle(); +// let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); + +// let center_pane = cx.add_view(|cx| { +// Pane::new( +// weak_handle.clone(), +// project.clone(), +// app_state.background_actions, +// pane_history_timestamp.clone(), +// cx, +// ) +// }); +// cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); +// cx.focus(¢er_pane); +// cx.emit(Event::PaneAdded(center_pane.clone())); + +// app_state.workspace_store.update(cx, |store, _| { +// store.workspaces.insert(weak_handle.clone()); +// }); + +// let mut current_user = app_state.user_store.read(cx).watch_current_user(); +// let mut connection_status = app_state.client.status(); +// let _observe_current_user = cx.spawn(|this, mut cx| async move { +// current_user.recv().await; +// connection_status.recv().await; +// let mut stream = +// Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); + +// while stream.recv().await.is_some() { +// this.update(&mut cx, |_, cx| cx.notify())?; +// } +// anyhow::Ok(()) +// }); + +// // All leader updates are enqueued and then processed in a single task, so +// // that each asynchronous operation can be run in order. +// let (leader_updates_tx, mut leader_updates_rx) = +// mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); +// let _apply_leader_updates = cx.spawn(|this, mut cx| async move { +// while let Some((leader_id, update)) = leader_updates_rx.next().await { +// Self::process_leader_update(&this, leader_id, update, &mut cx) +// .await +// .log_err(); +// } + +// Ok(()) +// }); + +// cx.emit_global(WorkspaceCreated(weak_handle.clone())); + +// let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); +// let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); +// let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); +// let left_dock_buttons = +// cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); +// let bottom_dock_buttons = +// cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); +// let right_dock_buttons = +// cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); +// let status_bar = cx.add_view(|cx| { +// let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); +// status_bar.add_left_item(left_dock_buttons, cx); +// status_bar.add_right_item(right_dock_buttons, cx); +// status_bar.add_right_item(bottom_dock_buttons, cx); +// status_bar +// }); + +// cx.update_default_global::, _, _>(|drag_and_drop, _| { +// drag_and_drop.register_container(weak_handle.clone()); +// }); + +// let mut active_call = None; +// if cx.has_global::>() { +// let call = cx.global::>().clone(); +// let mut subscriptions = Vec::new(); +// subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); +// active_call = Some((call, subscriptions)); +// } + +// let subscriptions = vec![ +// cx.observe_fullscreen(|_, _, cx| cx.notify()), +// cx.observe_window_activation(Self::on_window_activation_changed), +// cx.observe_window_bounds(move |_, mut bounds, display, cx| { +// // Transform fixed bounds to be stored in terms of the containing display +// if let WindowBounds::Fixed(mut window_bounds) = bounds { +// if let Some(screen) = cx.platform().screen_by_id(display) { +// let screen_bounds = screen.bounds(); +// window_bounds +// .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); +// window_bounds +// .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); +// bounds = WindowBounds::Fixed(window_bounds); +// } +// } + +// cx.background() +// .spawn(DB.set_window_bounds(workspace_id, bounds, display)) +// .detach_and_log_err(cx); +// }), +// cx.observe(&left_dock, |this, _, cx| { +// this.serialize_workspace(cx); +// cx.notify(); +// }), +// cx.observe(&bottom_dock, |this, _, cx| { +// this.serialize_workspace(cx); +// cx.notify(); +// }), +// cx.observe(&right_dock, |this, _, cx| { +// this.serialize_workspace(cx); +// cx.notify(); +// }), +// ]; + +// cx.defer(|this, cx| this.update_window_title(cx)); +// Workspace { +// weak_self: weak_handle.clone(), +// modal: None, +// zoomed: None, +// zoomed_position: None, +// center: PaneGroup::new(center_pane.clone()), +// panes: vec![center_pane.clone()], +// panes_by_item: Default::default(), +// active_pane: center_pane.clone(), +// last_active_center_pane: Some(center_pane.downgrade()), +// last_active_view_id: None, +// status_bar, +// titlebar_item: None, +// notifications: Default::default(), +// left_dock, +// bottom_dock, +// right_dock, +// project: project.clone(), +// follower_states: Default::default(), +// last_leaders_by_pane: Default::default(), +// window_edited: false, +// active_call, +// database_id: workspace_id, +// app_state, +// _observe_current_user, +// _apply_leader_updates, +// _schedule_serialize: None, +// leader_updates_tx, +// subscriptions, +// pane_history_timestamp, +// } +// } + +// fn new_local( +// abs_paths: Vec, +// app_state: Arc, +// requesting_window: Option>, +// cx: &mut AppContext, +// ) -> Task<( +// WeakViewHandle, +// Vec, anyhow::Error>>>, +// )> { +// let project_handle = Project::local( +// app_state.client.clone(), +// app_state.node_runtime.clone(), +// app_state.user_store.clone(), +// app_state.languages.clone(), +// app_state.fs.clone(), +// cx, +// ); + +// cx.spawn(|mut cx| async move { +// let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + +// let paths_to_open = Arc::new(abs_paths); + +// // Get project paths for all of the abs_paths +// let mut worktree_roots: HashSet> = Default::default(); +// let mut project_paths: Vec<(PathBuf, Option)> = +// Vec::with_capacity(paths_to_open.len()); +// for path in paths_to_open.iter().cloned() { +// if let Some((worktree, project_entry)) = cx +// .update(|cx| { +// Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) +// }) +// .await +// .log_err() +// { +// worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); +// project_paths.push((path, Some(project_entry))); +// } else { +// project_paths.push((path, None)); +// } +// } + +// let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { +// serialized_workspace.id +// } else { +// DB.next_id().await.unwrap_or(0) +// }; + +// let window = if let Some(window) = requesting_window { +// window.replace_root(&mut cx, |cx| { +// Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) +// }); +// window +// } else { +// { +// let window_bounds_override = window_bounds_env_override(&cx); +// let (bounds, display) = if let Some(bounds) = window_bounds_override { +// (Some(bounds), None) +// } else { +// serialized_workspace +// .as_ref() +// .and_then(|serialized_workspace| { +// let display = serialized_workspace.display?; +// let mut bounds = serialized_workspace.bounds?; + +// // Stored bounds are relative to the containing display. +// // So convert back to global coordinates if that screen still exists +// if let WindowBounds::Fixed(mut window_bounds) = bounds { +// if let Some(screen) = cx.platform().screen_by_id(display) { +// let screen_bounds = screen.bounds(); +// window_bounds.set_origin_x( +// window_bounds.origin_x() + screen_bounds.origin_x(), +// ); +// window_bounds.set_origin_y( +// window_bounds.origin_y() + screen_bounds.origin_y(), +// ); +// bounds = WindowBounds::Fixed(window_bounds); +// } else { +// // Screen no longer exists. Return none here. +// return None; +// } +// } + +// Some((bounds, display)) +// }) +// .unzip() +// }; + +// // Use the serialized workspace to construct the new window +// cx.add_window( +// (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), +// |cx| { +// Workspace::new( +// workspace_id, +// project_handle.clone(), +// app_state.clone(), +// cx, +// ) +// }, +// ) +// } +// }; + +// // We haven't yielded the main thread since obtaining the window handle, +// // so the window exists. +// let workspace = window.root(&cx).unwrap(); + +// (app_state.initialize_workspace)( +// workspace.downgrade(), +// serialized_workspace.is_some(), +// app_state.clone(), +// cx.clone(), +// ) +// .await +// .log_err(); + +// window.update(&mut cx, |cx| cx.activate_window()); + +// let workspace = workspace.downgrade(); +// notify_if_database_failed(&workspace, &mut cx); +// let opened_items = open_items( +// serialized_workspace, +// &workspace, +// project_paths, +// app_state, +// cx, +// ) +// .await +// .unwrap_or_default(); + +// (workspace, opened_items) +// }) +// } + +// pub fn weak_handle(&self) -> WeakViewHandle { +// self.weak_self.clone() +// } + +// pub fn left_dock(&self) -> &ViewHandle { +// &self.left_dock +// } + +// pub fn bottom_dock(&self) -> &ViewHandle { +// &self.bottom_dock +// } + +// pub fn right_dock(&self) -> &ViewHandle { +// &self.right_dock +// } + +// pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) +// where +// T::Event: std::fmt::Debug, +// { +// self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) +// } + +// pub fn add_panel_with_extra_event_handler( +// &mut self, +// panel: ViewHandle, +// cx: &mut ViewContext, +// handler: F, +// ) where +// T::Event: std::fmt::Debug, +// F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, +// { +// let dock = match panel.position(cx) { +// DockPosition::Left => &self.left_dock, +// DockPosition::Bottom => &self.bottom_dock, +// DockPosition::Right => &self.right_dock, +// }; + +// self.subscriptions.push(cx.subscribe(&panel, { +// let mut dock = dock.clone(); +// let mut prev_position = panel.position(cx); +// move |this, panel, event, cx| { +// if T::should_change_position_on_event(event) { +// let new_position = panel.read(cx).position(cx); +// let mut was_visible = false; +// dock.update(cx, |dock, cx| { +// prev_position = new_position; + +// was_visible = dock.is_open() +// && dock +// .visible_panel() +// .map_or(false, |active_panel| active_panel.id() == panel.id()); +// dock.remove_panel(&panel, cx); +// }); + +// if panel.is_zoomed(cx) { +// this.zoomed_position = Some(new_position); +// } + +// dock = match panel.read(cx).position(cx) { +// DockPosition::Left => &this.left_dock, +// DockPosition::Bottom => &this.bottom_dock, +// DockPosition::Right => &this.right_dock, +// } +// .clone(); +// dock.update(cx, |dock, cx| { +// dock.add_panel(panel.clone(), cx); +// if was_visible { +// dock.set_open(true, cx); +// dock.activate_panel(dock.panels_len() - 1, cx); +// } +// }); +// } else if T::should_zoom_in_on_event(event) { +// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); +// if !panel.has_focus(cx) { +// cx.focus(&panel); +// } +// this.zoomed = Some(panel.downgrade().into_any()); +// this.zoomed_position = Some(panel.read(cx).position(cx)); +// } else if T::should_zoom_out_on_event(event) { +// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); +// if this.zoomed_position == Some(prev_position) { +// this.zoomed = None; +// this.zoomed_position = None; +// } +// cx.notify(); +// } else if T::is_focus_event(event) { +// let position = panel.read(cx).position(cx); +// this.dismiss_zoomed_items_to_reveal(Some(position), cx); +// if panel.is_zoomed(cx) { +// this.zoomed = Some(panel.downgrade().into_any()); +// this.zoomed_position = Some(position); +// } else { +// this.zoomed = None; +// this.zoomed_position = None; +// } +// this.update_active_view_for_followers(cx); +// cx.notify(); +// } else { +// handler(this, &panel, event, cx) +// } +// } +// })); + +// dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); +// } + +// pub fn status_bar(&self) -> &ViewHandle { +// &self.status_bar +// } + +// pub fn app_state(&self) -> &Arc { +// &self.app_state +// } + +// pub fn user_store(&self) -> &ModelHandle { +// &self.app_state.user_store +// } + +// pub fn project(&self) -> &ModelHandle { +// &self.project +// } + +// pub fn recent_navigation_history( +// &self, +// limit: Option, +// cx: &AppContext, +// ) -> Vec<(ProjectPath, Option)> { +// let mut abs_paths_opened: HashMap> = HashMap::default(); +// let mut history: HashMap, usize)> = HashMap::default(); +// for pane in &self.panes { +// let pane = pane.read(cx); +// pane.nav_history() +// .for_each_entry(cx, |entry, (project_path, fs_path)| { +// if let Some(fs_path) = &fs_path { +// abs_paths_opened +// .entry(fs_path.clone()) +// .or_default() +// .insert(project_path.clone()); +// } +// let timestamp = entry.timestamp; +// match history.entry(project_path) { +// hash_map::Entry::Occupied(mut entry) => { +// let (_, old_timestamp) = entry.get(); +// if ×tamp > old_timestamp { +// entry.insert((fs_path, timestamp)); +// } +// } +// hash_map::Entry::Vacant(entry) => { +// entry.insert((fs_path, timestamp)); +// } +// } +// }); +// } + +// history +// .into_iter() +// .sorted_by_key(|(_, (_, timestamp))| *timestamp) +// .map(|(project_path, (fs_path, _))| (project_path, fs_path)) +// .rev() +// .filter(|(history_path, abs_path)| { +// let latest_project_path_opened = abs_path +// .as_ref() +// .and_then(|abs_path| abs_paths_opened.get(abs_path)) +// .and_then(|project_paths| { +// project_paths +// .iter() +// .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) +// }); + +// match latest_project_path_opened { +// Some(latest_project_path_opened) => latest_project_path_opened == history_path, +// None => true, +// } +// }) +// .take(limit.unwrap_or(usize::MAX)) +// .collect() +// } + +// fn navigate_history( +// &mut self, +// pane: WeakViewHandle, +// mode: NavigationMode, +// cx: &mut ViewContext, +// ) -> Task> { +// let to_load = if let Some(pane) = pane.upgrade(cx) { +// cx.focus(&pane); + +// pane.update(cx, |pane, cx| { +// loop { +// // Retrieve the weak item handle from the history. +// let entry = pane.nav_history_mut().pop(mode, cx)?; + +// // If the item is still present in this pane, then activate it. +// if let Some(index) = entry +// .item +// .upgrade(cx) +// .and_then(|v| pane.index_for_item(v.as_ref())) +// { +// let prev_active_item_index = pane.active_item_index(); +// pane.nav_history_mut().set_mode(mode); +// pane.activate_item(index, true, true, cx); +// pane.nav_history_mut().set_mode(NavigationMode::Normal); + +// let mut navigated = prev_active_item_index != pane.active_item_index(); +// if let Some(data) = entry.data { +// navigated |= pane.active_item()?.navigate(data, cx); +// } + +// if navigated { +// break None; +// } +// } +// // If the item is no longer present in this pane, then retrieve its +// // project path in order to reopen it. +// else { +// break pane +// .nav_history() +// .path_for_item(entry.item.id()) +// .map(|(project_path, _)| (project_path, entry)); +// } +// } +// }) +// } else { +// None +// }; + +// if let Some((project_path, entry)) = to_load { +// // If the item was no longer present, then load it again from its previous path. +// let task = self.load_path(project_path, cx); +// cx.spawn(|workspace, mut cx| async move { +// let task = task.await; +// let mut navigated = false; +// if let Some((project_entry_id, build_item)) = task.log_err() { +// let prev_active_item_id = pane.update(&mut cx, |pane, _| { +// pane.nav_history_mut().set_mode(mode); +// pane.active_item().map(|p| p.id()) +// })?; + +// pane.update(&mut cx, |pane, cx| { +// let item = pane.open_item(project_entry_id, true, cx, build_item); +// navigated |= Some(item.id()) != prev_active_item_id; +// pane.nav_history_mut().set_mode(NavigationMode::Normal); +// if let Some(data) = entry.data { +// navigated |= item.navigate(data, cx); +// } +// })?; +// } + +// if !navigated { +// workspace +// .update(&mut cx, |workspace, cx| { +// Self::navigate_history(workspace, pane, mode, cx) +// })? +// .await?; +// } + +// Ok(()) +// }) +// } else { +// Task::ready(Ok(())) +// } +// } + +// pub fn go_back( +// &mut self, +// pane: WeakViewHandle, +// cx: &mut ViewContext, +// ) -> Task> { +// self.navigate_history(pane, NavigationMode::GoingBack, cx) +// } + +// pub fn go_forward( +// &mut self, +// pane: WeakViewHandle, +// cx: &mut ViewContext, +// ) -> Task> { +// self.navigate_history(pane, NavigationMode::GoingForward, cx) +// } + +// pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { +// self.navigate_history( +// self.active_pane().downgrade(), +// NavigationMode::ReopeningClosedItem, +// cx, +// ) +// } + +// pub fn client(&self) -> &Client { +// &self.app_state.client +// } + +// pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext) { +// self.titlebar_item = Some(item); +// cx.notify(); +// } + +// pub fn titlebar_item(&self) -> Option { +// self.titlebar_item.clone() +// } + +// /// Call the given callback with a workspace whose project is local. +// /// +// /// If the given workspace has a local project, then it will be passed +// /// to the callback. Otherwise, a new empty window will be created. +// pub fn with_local_workspace( +// &mut self, +// cx: &mut ViewContext, +// callback: F, +// ) -> Task> +// where +// T: 'static, +// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, +// { +// if self.project.read(cx).is_local() { +// Task::Ready(Some(Ok(callback(self, cx)))) +// } else { +// let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); +// cx.spawn(|_vh, mut cx| async move { +// let (workspace, _) = task.await; +// workspace.update(&mut cx, callback) +// }) +// } +// } + +// pub fn worktrees<'a>( +// &self, +// cx: &'a AppContext, +// ) -> impl 'a + Iterator> { +// self.project.read(cx).worktrees(cx) +// } + +// pub fn visible_worktrees<'a>( +// &self, +// cx: &'a AppContext, +// ) -> impl 'a + Iterator> { +// self.project.read(cx).visible_worktrees(cx) +// } + +// pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { +// let futures = self +// .worktrees(cx) +// .filter_map(|worktree| worktree.read(cx).as_local()) +// .map(|worktree| worktree.scan_complete()) +// .collect::>(); +// async move { +// for future in futures { +// future.await; +// } +// } +// } + +// pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { +// cx.spawn(|mut cx| async move { +// let window = cx +// .windows() +// .into_iter() +// .find(|window| window.is_active(&cx).unwrap_or(false)); +// if let Some(window) = window { +// //This can only get called when the window's project connection has been lost +// //so we don't need to prompt the user for anything and instead just close the window +// window.remove(&mut cx); +// } +// }) +// .detach(); +// } + +// pub fn close( +// &mut self, +// _: &CloseWindow, +// cx: &mut ViewContext, +// ) -> Option>> { +// let window = cx.window(); +// let prepare = self.prepare_to_close(false, cx); +// Some(cx.spawn(|_, mut cx| async move { +// if prepare.await? { +// window.remove(&mut cx); +// } +// Ok(()) +// })) +// } + +// pub fn prepare_to_close( +// &mut self, +// quitting: bool, +// cx: &mut ViewContext, +// ) -> Task> { +// let active_call = self.active_call().cloned(); +// let window = cx.window(); + +// cx.spawn(|this, mut cx| async move { +// let workspace_count = cx +// .windows() +// .into_iter() +// .filter(|window| window.root_is::()) +// .count(); + +// if let Some(active_call) = active_call { +// if !quitting +// && workspace_count == 1 +// && active_call.read_with(&cx, |call, _| call.room().is_some()) +// { +// let answer = window.prompt( +// PromptLevel::Warning, +// "Do you want to leave the current call?", +// &["Close window and hang up", "Cancel"], +// &mut cx, +// ); + +// if let Some(mut answer) = answer { +// if answer.next().await == Some(1) { +// return anyhow::Ok(false); +// } else { +// active_call +// .update(&mut cx, |call, cx| call.hang_up(cx)) +// .await +// .log_err(); +// } +// } +// } +// } + +// Ok(this +// .update(&mut cx, |this, cx| { +// this.save_all_internal(SaveIntent::Close, cx) +// })? +// .await?) +// }) +// } + +// fn save_all( +// &mut self, +// action: &SaveAll, +// cx: &mut ViewContext, +// ) -> Option>> { +// let save_all = +// self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx); +// Some(cx.foreground().spawn(async move { +// save_all.await?; +// Ok(()) +// })) +// } + +// fn save_all_internal( +// &mut self, +// mut save_intent: SaveIntent, +// cx: &mut ViewContext, +// ) -> Task> { +// if self.project.read(cx).is_read_only() { +// return Task::ready(Ok(true)); +// } +// let dirty_items = self +// .panes +// .iter() +// .flat_map(|pane| { +// pane.read(cx).items().filter_map(|item| { +// if item.is_dirty(cx) { +// Some((pane.downgrade(), item.boxed_clone())) +// } else { +// None +// } +// }) +// }) +// .collect::>(); + +// let project = self.project.clone(); +// cx.spawn(|workspace, mut cx| async move { +// // Override save mode and display "Save all files" prompt +// if save_intent == SaveIntent::Close && dirty_items.len() > 1 { +// let mut answer = workspace.update(&mut cx, |_, cx| { +// let prompt = Pane::file_names_for_prompt( +// &mut dirty_items.iter().map(|(_, handle)| handle), +// dirty_items.len(), +// cx, +// ); +// cx.prompt( +// PromptLevel::Warning, +// &prompt, +// &["Save all", "Discard all", "Cancel"], +// ) +// })?; +// match answer.next().await { +// Some(0) => save_intent = SaveIntent::SaveAll, +// Some(1) => save_intent = SaveIntent::Skip, +// _ => {} +// } +// } +// for (pane, item) in dirty_items { +// let (singleton, project_entry_ids) = +// cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); +// if singleton || !project_entry_ids.is_empty() { +// if let Some(ix) = +// pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))? +// { +// if !Pane::save_item( +// project.clone(), +// &pane, +// ix, +// &*item, +// save_intent, +// &mut cx, +// ) +// .await? +// { +// return Ok(false); +// } +// } +// } +// } +// Ok(true) +// }) +// } + +// pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { +// let mut paths = cx.prompt_for_paths(PathPromptOptions { +// files: true, +// directories: true, +// multiple: true, +// }); + +// Some(cx.spawn(|this, mut cx| async move { +// if let Some(paths) = paths.recv().await.flatten() { +// if let Some(task) = this +// .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) +// .log_err() +// { +// task.await? +// } +// } +// Ok(()) +// })) +// } + +// pub fn open_workspace_for_paths( +// &mut self, +// paths: Vec, +// cx: &mut ViewContext, +// ) -> Task> { +// let window = cx.window().downcast::(); +// let is_remote = self.project.read(cx).is_remote(); +// let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); +// let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); +// let close_task = if is_remote || has_worktree || has_dirty_items { +// None +// } else { +// Some(self.prepare_to_close(false, cx)) +// }; +// let app_state = self.app_state.clone(); + +// cx.spawn(|_, mut cx| async move { +// let window_to_replace = if let Some(close_task) = close_task { +// if !close_task.await? { +// return Ok(()); +// } +// window +// } else { +// None +// }; +// cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx)) +// .await?; +// Ok(()) +// }) +// } + +// #[allow(clippy::type_complexity)] +// pub fn open_paths( +// &mut self, +// mut abs_paths: Vec, +// visible: bool, +// cx: &mut ViewContext, +// ) -> Task, anyhow::Error>>>> { +// log::info!("open paths {:?}", abs_paths); + +// let fs = self.app_state.fs.clone(); + +// // Sort the paths to ensure we add worktrees for parents before their children. +// abs_paths.sort_unstable(); +// cx.spawn(|this, mut cx| async move { +// let mut tasks = Vec::with_capacity(abs_paths.len()); +// for abs_path in &abs_paths { +// let project_path = match this +// .update(&mut cx, |this, cx| { +// Workspace::project_path_for_path( +// this.project.clone(), +// abs_path, +// visible, +// cx, +// ) +// }) +// .log_err() +// { +// Some(project_path) => project_path.await.log_err(), +// None => None, +// }; + +// let this = this.clone(); +// let task = cx.spawn(|mut cx| { +// let fs = fs.clone(); +// let abs_path = abs_path.clone(); +// async move { +// let (worktree, project_path) = project_path?; +// if fs.is_file(&abs_path).await { +// Some( +// this.update(&mut cx, |this, cx| { +// this.open_path(project_path, None, true, cx) +// }) +// .log_err()? +// .await, +// ) +// } else { +// this.update(&mut cx, |workspace, cx| { +// let worktree = worktree.read(cx); +// let worktree_abs_path = worktree.abs_path(); +// let entry_id = if abs_path == worktree_abs_path.as_ref() { +// worktree.root_entry() +// } else { +// abs_path +// .strip_prefix(worktree_abs_path.as_ref()) +// .ok() +// .and_then(|relative_path| { +// worktree.entry_for_path(relative_path) +// }) +// } +// .map(|entry| entry.id); +// if let Some(entry_id) = entry_id { +// workspace.project().update(cx, |_, cx| { +// cx.emit(project::Event::ActiveEntryChanged(Some(entry_id))); +// }) +// } +// }) +// .log_err()?; +// None +// } +// } +// }); +// tasks.push(task); +// } + +// futures::future::join_all(tasks).await +// }) +// } + +// fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { +// let mut paths = cx.prompt_for_paths(PathPromptOptions { +// files: false, +// directories: true, +// multiple: true, +// }); +// cx.spawn(|this, mut cx| async move { +// if let Some(paths) = paths.recv().await.flatten() { +// let results = this +// .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? +// .await; +// for result in results.into_iter().flatten() { +// result.log_err(); +// } +// } +// anyhow::Ok(()) +// }) +// .detach_and_log_err(cx); +// } + +// fn project_path_for_path( +// project: ModelHandle, +// abs_path: &Path, +// visible: bool, +// cx: &mut AppContext, +// ) -> Task, ProjectPath)>> { +// let entry = project.update(cx, |project, cx| { +// project.find_or_create_local_worktree(abs_path, visible, cx) +// }); +// cx.spawn(|cx| async move { +// let (worktree, path) = entry.await?; +// let worktree_id = worktree.read_with(&cx, |t, _| t.id()); +// Ok(( +// worktree, +// ProjectPath { +// worktree_id, +// path: path.into(), +// }, +// )) +// }) +// } + +// /// Returns the modal that was toggled closed if it was open. +// pub fn toggle_modal( +// &mut self, +// cx: &mut ViewContext, +// add_view: F, +// ) -> Option> +// where +// V: 'static + Modal, +// F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, +// { +// cx.notify(); +// // Whatever modal was visible is getting clobbered. If its the same type as V, then return +// // it. Otherwise, create a new modal and set it as active. +// if let Some(already_open_modal) = self +// .dismiss_modal(cx) +// .and_then(|modal| modal.downcast::()) +// { +// cx.focus_self(); +// Some(already_open_modal) +// } else { +// let modal = add_view(self, cx); +// cx.subscribe(&modal, |this, _, event, cx| { +// if V::dismiss_on_event(event) { +// this.dismiss_modal(cx); +// } +// }) +// .detach(); +// let previously_focused_view_id = cx.focused_view_id(); +// cx.focus(&modal); +// self.modal = Some(ActiveModal { +// view: Box::new(modal), +// previously_focused_view_id, +// }); +// None +// } +// } + +// pub fn modal(&self) -> Option> { +// self.modal +// .as_ref() +// .and_then(|modal| modal.view.as_any().clone().downcast::()) +// } + +// pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { +// if let Some(modal) = self.modal.take() { +// if let Some(previously_focused_view_id) = modal.previously_focused_view_id { +// if modal.view.has_focus(cx) { +// cx.window_context().focus(Some(previously_focused_view_id)); +// } +// } +// cx.notify(); +// Some(modal.view.as_any().clone()) +// } else { +// None +// } +// } + +// pub fn items<'a>( +// &'a self, +// cx: &'a AppContext, +// ) -> impl 'a + Iterator> { +// self.panes.iter().flat_map(|pane| pane.read(cx).items()) +// } + +// pub fn item_of_type(&self, cx: &AppContext) -> Option> { +// self.items_of_type(cx).max_by_key(|item| item.id()) +// } + +// pub fn items_of_type<'a, T: Item>( +// &'a self, +// cx: &'a AppContext, +// ) -> impl 'a + Iterator> { +// self.panes +// .iter() +// .flat_map(|pane| pane.read(cx).items_of_type()) +// } + +// pub fn active_item(&self, cx: &AppContext) -> Option> { +// self.active_pane().read(cx).active_item() +// } + +// fn active_project_path(&self, cx: &ViewContext) -> Option { +// self.active_item(cx).and_then(|item| item.project_path(cx)) +// } + +// pub fn save_active_item( +// &mut self, +// save_intent: SaveIntent, +// cx: &mut ViewContext, +// ) -> Task> { +// let project = self.project.clone(); +// let pane = self.active_pane(); +// let item_ix = pane.read(cx).active_item_index(); +// let item = pane.read(cx).active_item(); +// let pane = pane.downgrade(); + +// cx.spawn(|_, mut cx| async move { +// if let Some(item) = item { +// Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx) +// .await +// .map(|_| ()) +// } else { +// Ok(()) +// } +// }) +// } + +// pub fn close_inactive_items_and_panes( +// &mut self, +// _: &CloseInactiveTabsAndPanes, +// cx: &mut ViewContext, +// ) -> Option>> { +// self.close_all_internal(true, SaveIntent::Close, cx) +// } + +// pub fn close_all_items_and_panes( +// &mut self, +// action: &CloseAllItemsAndPanes, +// cx: &mut ViewContext, +// ) -> Option>> { +// self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) +// } + +// fn close_all_internal( +// &mut self, +// retain_active_pane: bool, +// save_intent: SaveIntent, +// cx: &mut ViewContext, +// ) -> Option>> { +// let current_pane = self.active_pane(); + +// let mut tasks = Vec::new(); + +// if retain_active_pane { +// if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| { +// pane.close_inactive_items(&CloseInactiveItems, cx) +// }) { +// tasks.push(current_pane_close); +// }; +// } + +// for pane in self.panes() { +// if retain_active_pane && pane.id() == current_pane.id() { +// continue; +// } + +// if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| { +// pane.close_all_items( +// &CloseAllItems { +// save_intent: Some(save_intent), +// }, +// cx, +// ) +// }) { +// tasks.push(close_pane_items) +// } +// } + +// if tasks.is_empty() { +// None +// } else { +// Some(cx.spawn(|_, _| async move { +// for task in tasks { +// task.await? +// } +// Ok(()) +// })) +// } +// } + +// pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { +// let dock = match dock_side { +// DockPosition::Left => &self.left_dock, +// DockPosition::Bottom => &self.bottom_dock, +// DockPosition::Right => &self.right_dock, +// }; +// let mut focus_center = false; +// let mut reveal_dock = false; +// dock.update(cx, |dock, cx| { +// let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); +// let was_visible = dock.is_open() && !other_is_zoomed; +// dock.set_open(!was_visible, cx); + +// if let Some(active_panel) = dock.active_panel() { +// if was_visible { +// if active_panel.has_focus(cx) { +// focus_center = true; +// } +// } else { +// cx.focus(active_panel.as_any()); +// reveal_dock = true; +// } +// } +// }); + +// if reveal_dock { +// self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); +// } + +// if focus_center { +// cx.focus_self(); +// } + +// cx.notify(); +// self.serialize_workspace(cx); +// } + +// pub fn close_all_docks(&mut self, cx: &mut ViewContext) { +// let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + +// for dock in docks { +// dock.update(cx, |dock, cx| { +// dock.set_open(false, cx); +// }); +// } + +// cx.focus_self(); +// cx.notify(); +// self.serialize_workspace(cx); +// } + +// /// Transfer focus to the panel of the given type. +// pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { +// self.focus_or_unfocus_panel::(cx, |_, _| true)? +// .as_any() +// .clone() +// .downcast() +// } + +// /// Focus the panel of the given type if it isn't already focused. If it is +// /// already focused, then transfer focus back to the workspace center. +// pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { +// self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); +// } + +// /// Focus or unfocus the given panel type, depending on the given callback. +// fn focus_or_unfocus_panel( +// &mut self, +// cx: &mut ViewContext, +// should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, +// ) -> Option> { +// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { +// if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { +// let mut focus_center = false; +// let mut reveal_dock = false; +// let panel = dock.update(cx, |dock, cx| { +// dock.activate_panel(panel_index, cx); + +// let panel = dock.active_panel().cloned(); +// if let Some(panel) = panel.as_ref() { +// if should_focus(&**panel, cx) { +// dock.set_open(true, cx); +// cx.focus(panel.as_any()); +// reveal_dock = true; +// } else { +// // if panel.is_zoomed(cx) { +// // dock.set_open(false, cx); +// // } +// focus_center = true; +// } +// } +// panel +// }); + +// if focus_center { +// cx.focus_self(); +// } + +// self.serialize_workspace(cx); +// cx.notify(); +// return panel; +// } +// } +// None +// } + +// pub fn panel(&self, cx: &WindowContext) -> Option> { +// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { +// let dock = dock.read(cx); +// if let Some(panel) = dock.panel::() { +// return Some(panel); +// } +// } +// None +// } + +// fn zoom_out(&mut self, cx: &mut ViewContext) { +// for pane in &self.panes { +// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); +// } + +// self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); +// self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); +// self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); +// self.zoomed = None; +// self.zoomed_position = None; + +// cx.notify(); +// } + +// #[cfg(any(test, feature = "test-support"))] +// pub fn zoomed_view(&self, cx: &AppContext) -> Option { +// self.zoomed.and_then(|view| view.upgrade(cx)) +// } + +// fn dismiss_zoomed_items_to_reveal( +// &mut self, +// dock_to_reveal: Option, +// cx: &mut ViewContext, +// ) { +// // If a center pane is zoomed, unzoom it. +// for pane in &self.panes { +// if pane != &self.active_pane || dock_to_reveal.is_some() { +// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); +// } +// } + +// // If another dock is zoomed, hide it. +// let mut focus_center = false; +// for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { +// dock.update(cx, |dock, cx| { +// if Some(dock.position()) != dock_to_reveal { +// if let Some(panel) = dock.active_panel() { +// if panel.is_zoomed(cx) { +// focus_center |= panel.has_focus(cx); +// dock.set_open(false, cx); +// } +// } +// } +// }); +// } + +// if focus_center { +// cx.focus_self(); +// } + +// if self.zoomed_position != dock_to_reveal { +// self.zoomed = None; +// self.zoomed_position = None; +// } + +// cx.notify(); +// } + +// fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { +// let pane = cx.add_view(|cx| { +// Pane::new( +// self.weak_handle(), +// self.project.clone(), +// self.app_state.background_actions, +// self.pane_history_timestamp.clone(), +// cx, +// ) +// }); +// cx.subscribe(&pane, Self::handle_pane_event).detach(); +// self.panes.push(pane.clone()); +// cx.focus(&pane); +// cx.emit(Event::PaneAdded(pane.clone())); +// pane +// } + +// pub fn add_item_to_center( +// &mut self, +// item: Box, +// cx: &mut ViewContext, +// ) -> bool { +// if let Some(center_pane) = self.last_active_center_pane.clone() { +// if let Some(center_pane) = center_pane.upgrade(cx) { +// center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); +// true +// } else { +// false +// } +// } else { +// false +// } +// } + +// pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { +// self.active_pane +// .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); +// } + +// pub fn split_item( +// &mut self, +// split_direction: SplitDirection, +// item: Box, +// cx: &mut ViewContext, +// ) { +// let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); +// new_pane.update(cx, move |new_pane, cx| { +// new_pane.add_item(item, true, true, None, cx) +// }) +// } + +// pub fn open_abs_path( +// &mut self, +// abs_path: PathBuf, +// visible: bool, +// cx: &mut ViewContext, +// ) -> Task>> { +// cx.spawn(|workspace, mut cx| async move { +// let open_paths_task_result = workspace +// .update(&mut cx, |workspace, cx| { +// workspace.open_paths(vec![abs_path.clone()], visible, cx) +// }) +// .with_context(|| format!("open abs path {abs_path:?} task spawn"))? +// .await; +// anyhow::ensure!( +// open_paths_task_result.len() == 1, +// "open abs path {abs_path:?} task returned incorrect number of results" +// ); +// match open_paths_task_result +// .into_iter() +// .next() +// .expect("ensured single task result") +// { +// Some(open_result) => { +// open_result.with_context(|| format!("open abs path {abs_path:?} task join")) +// } +// None => anyhow::bail!("open abs path {abs_path:?} task returned None"), +// } +// }) +// } + +// pub fn split_abs_path( +// &mut self, +// abs_path: PathBuf, +// visible: bool, +// cx: &mut ViewContext, +// ) -> Task>> { +// let project_path_task = +// Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); +// cx.spawn(|this, mut cx| async move { +// let (_, path) = project_path_task.await?; +// this.update(&mut cx, |this, cx| this.split_path(path, cx))? +// .await +// }) +// } + +// pub fn open_path( +// &mut self, +// path: impl Into, +// pane: Option>, +// focus_item: bool, +// cx: &mut ViewContext, +// ) -> Task, anyhow::Error>> { +// let pane = pane.unwrap_or_else(|| { +// self.last_active_center_pane.clone().unwrap_or_else(|| { +// self.panes +// .first() +// .expect("There must be an active pane") +// .downgrade() +// }) +// }); + +// let task = self.load_path(path.into(), cx); +// cx.spawn(|_, mut cx| async move { +// let (project_entry_id, build_item) = task.await?; +// pane.update(&mut cx, |pane, cx| { +// pane.open_item(project_entry_id, focus_item, cx, build_item) +// }) +// }) +// } + +// pub fn split_path( +// &mut self, +// path: impl Into, +// cx: &mut ViewContext, +// ) -> Task, anyhow::Error>> { +// let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { +// self.panes +// .first() +// .expect("There must be an active pane") +// .downgrade() +// }); + +// if let Member::Pane(center_pane) = &self.center.root { +// if center_pane.read(cx).items_len() == 0 { +// return self.open_path(path, Some(pane), true, cx); +// } +// } + +// let task = self.load_path(path.into(), cx); +// cx.spawn(|this, mut cx| async move { +// let (project_entry_id, build_item) = task.await?; +// this.update(&mut cx, move |this, cx| -> Option<_> { +// let pane = pane.upgrade(cx)?; +// let new_pane = this.split_pane(pane, SplitDirection::Right, cx); +// new_pane.update(cx, |new_pane, cx| { +// Some(new_pane.open_item(project_entry_id, true, cx, build_item)) +// }) +// }) +// .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? +// }) +// } + +// pub(crate) fn load_path( +// &mut self, +// path: ProjectPath, +// cx: &mut ViewContext, +// ) -> Task< +// Result<( +// ProjectEntryId, +// impl 'static + FnOnce(&mut ViewContext) -> Box, +// )>, +// > { +// let project = self.project().clone(); +// let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); +// cx.spawn(|_, mut cx| async move { +// let (project_entry_id, project_item) = project_item.await?; +// let build_item = cx.update(|cx| { +// cx.default_global::() +// .get(&project_item.model_type()) +// .ok_or_else(|| anyhow!("no item builder for project item")) +// .cloned() +// })?; +// let build_item = +// move |cx: &mut ViewContext| build_item(project, project_item, cx); +// Ok((project_entry_id, build_item)) +// }) +// } + +// pub fn open_project_item( +// &mut self, +// project_item: ModelHandle, +// cx: &mut ViewContext, +// ) -> ViewHandle +// where +// T: ProjectItem, +// { +// use project::Item as _; + +// let entry_id = project_item.read(cx).entry_id(cx); +// if let Some(item) = entry_id +// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) +// .and_then(|item| item.downcast()) +// { +// self.activate_item(&item, cx); +// return item; +// } + +// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); +// self.add_item(Box::new(item.clone()), cx); +// item +// } + +// pub fn split_project_item( +// &mut self, +// project_item: ModelHandle, +// cx: &mut ViewContext, +// ) -> ViewHandle +// where +// T: ProjectItem, +// { +// use project::Item as _; + +// let entry_id = project_item.read(cx).entry_id(cx); +// if let Some(item) = entry_id +// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) +// .and_then(|item| item.downcast()) +// { +// self.activate_item(&item, cx); +// return item; +// } + +// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); +// self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); +// item +// } + +// pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { +// if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { +// self.active_pane.update(cx, |pane, cx| { +// pane.add_item(Box::new(shared_screen), false, true, None, cx) +// }); +// } +// } + +// pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { +// let result = self.panes.iter().find_map(|pane| { +// pane.read(cx) +// .index_for_item(item) +// .map(|ix| (pane.clone(), ix)) +// }); +// if let Some((pane, ix)) = result { +// pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); +// true +// } else { +// false +// } +// } + +// fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { +// let panes = self.center.panes(); +// if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { +// cx.focus(&pane); +// } else { +// self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); +// } +// } + +// pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { +// let panes = self.center.panes(); +// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { +// let next_ix = (ix + 1) % panes.len(); +// let next_pane = panes[next_ix].clone(); +// cx.focus(&next_pane); +// } +// } + +// pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { +// let panes = self.center.panes(); +// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { +// let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); +// let prev_pane = panes[prev_ix].clone(); +// cx.focus(&prev_pane); +// } +// } + +// pub fn activate_pane_in_direction( +// &mut self, +// direction: SplitDirection, +// cx: &mut ViewContext, +// ) { +// if let Some(pane) = self.find_pane_in_direction(direction, cx) { +// cx.focus(pane); +// } +// } + +// pub fn swap_pane_in_direction( +// &mut self, +// direction: SplitDirection, +// cx: &mut ViewContext, +// ) { +// if let Some(to) = self +// .find_pane_in_direction(direction, cx) +// .map(|pane| pane.clone()) +// { +// self.center.swap(&self.active_pane.clone(), &to); +// cx.notify(); +// } +// } + +// fn find_pane_in_direction( +// &mut self, +// direction: SplitDirection, +// cx: &mut ViewContext, +// ) -> Option<&ViewHandle> { +// let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { +// return None; +// }; +// let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); +// let center = match cursor { +// Some(cursor) if bounding_box.contains_point(cursor) => cursor, +// _ => bounding_box.center(), +// }; + +// let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; + +// let target = match direction { +// SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), +// SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), +// SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), +// SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), +// }; +// self.center.pane_at_pixel_position(target) +// } + +// fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { +// if self.active_pane != pane { +// self.active_pane = pane.clone(); +// self.status_bar.update(cx, |status_bar, cx| { +// status_bar.set_active_pane(&self.active_pane, cx); +// }); +// self.active_item_path_changed(cx); +// self.last_active_center_pane = Some(pane.downgrade()); +// } + +// self.dismiss_zoomed_items_to_reveal(None, cx); +// if pane.read(cx).is_zoomed() { +// self.zoomed = Some(pane.downgrade().into_any()); +// } else { +// self.zoomed = None; +// } +// self.zoomed_position = None; +// self.update_active_view_for_followers(cx); + +// cx.notify(); +// } + +// fn handle_pane_event( +// &mut self, +// pane: ViewHandle, +// event: &pane::Event, +// cx: &mut ViewContext, +// ) { +// match event { +// pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), +// pane::Event::Split(direction) => { +// self.split_and_clone(pane, *direction, cx); +// } +// pane::Event::Remove => self.remove_pane(pane, cx), +// pane::Event::ActivateItem { local } => { +// if *local { +// self.unfollow(&pane, cx); +// } +// if &pane == self.active_pane() { +// self.active_item_path_changed(cx); +// } +// } +// pane::Event::ChangeItemTitle => { +// if pane == self.active_pane { +// self.active_item_path_changed(cx); +// } +// self.update_window_edited(cx); +// } +// pane::Event::RemoveItem { item_id } => { +// self.update_window_edited(cx); +// if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { +// if entry.get().id() == pane.id() { +// entry.remove(); +// } +// } +// } +// pane::Event::Focus => { +// self.handle_pane_focused(pane.clone(), cx); +// } +// pane::Event::ZoomIn => { +// if pane == self.active_pane { +// pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); +// if pane.read(cx).has_focus() { +// self.zoomed = Some(pane.downgrade().into_any()); +// self.zoomed_position = None; +// } +// cx.notify(); +// } +// } +// pane::Event::ZoomOut => { +// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); +// if self.zoomed_position.is_none() { +// self.zoomed = None; +// } +// cx.notify(); +// } +// } + +// self.serialize_workspace(cx); +// } + +// pub fn split_pane( +// &mut self, +// pane_to_split: ViewHandle, +// split_direction: SplitDirection, +// cx: &mut ViewContext, +// ) -> ViewHandle { +// let new_pane = self.add_pane(cx); +// self.center +// .split(&pane_to_split, &new_pane, split_direction) +// .unwrap(); +// cx.notify(); +// new_pane +// } + +// pub fn split_and_clone( +// &mut self, +// pane: ViewHandle, +// direction: SplitDirection, +// cx: &mut ViewContext, +// ) -> Option> { +// let item = pane.read(cx).active_item()?; +// let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { +// let new_pane = self.add_pane(cx); +// new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); +// self.center.split(&pane, &new_pane, direction).unwrap(); +// Some(new_pane) +// } else { +// None +// }; +// cx.notify(); +// maybe_pane_handle +// } + +// pub fn split_pane_with_item( +// &mut self, +// pane_to_split: WeakViewHandle, +// split_direction: SplitDirection, +// from: WeakViewHandle, +// item_id_to_move: usize, +// cx: &mut ViewContext, +// ) { +// let Some(pane_to_split) = pane_to_split.upgrade(cx) else { +// return; +// }; +// let Some(from) = from.upgrade(cx) else { +// return; +// }; + +// let new_pane = self.add_pane(cx); +// self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); +// self.center +// .split(&pane_to_split, &new_pane, split_direction) +// .unwrap(); +// cx.notify(); +// } + +// pub fn split_pane_with_project_entry( +// &mut self, +// pane_to_split: WeakViewHandle, +// split_direction: SplitDirection, +// project_entry: ProjectEntryId, +// cx: &mut ViewContext, +// ) -> Option>> { +// let pane_to_split = pane_to_split.upgrade(cx)?; +// let new_pane = self.add_pane(cx); +// self.center +// .split(&pane_to_split, &new_pane, split_direction) +// .unwrap(); + +// let path = self.project.read(cx).path_for_entry(project_entry, cx)?; +// let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); +// Some(cx.foreground().spawn(async move { +// task.await?; +// Ok(()) +// })) +// } + +// pub fn move_item( +// &mut self, +// source: ViewHandle, +// destination: ViewHandle, +// item_id_to_move: usize, +// destination_index: usize, +// cx: &mut ViewContext, +// ) { +// let item_to_move = source +// .read(cx) +// .items() +// .enumerate() +// .find(|(_, item_handle)| item_handle.id() == item_id_to_move); + +// if item_to_move.is_none() { +// log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); +// return; +// } +// let (item_ix, item_handle) = item_to_move.unwrap(); +// let item_handle = item_handle.clone(); + +// if source != destination { +// // Close item from previous pane +// source.update(cx, |source, cx| { +// source.remove_item(item_ix, false, cx); +// }); +// } + +// // This automatically removes duplicate items in the pane +// destination.update(cx, |destination, cx| { +// destination.add_item(item_handle, true, true, Some(destination_index), cx); +// cx.focus_self(); +// }); +// } + +// fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { +// if self.center.remove(&pane).unwrap() { +// self.force_remove_pane(&pane, cx); +// self.unfollow(&pane, cx); +// self.last_leaders_by_pane.remove(&pane.downgrade()); +// for removed_item in pane.read(cx).items() { +// self.panes_by_item.remove(&removed_item.id()); +// } + +// cx.notify(); +// } else { +// self.active_item_path_changed(cx); +// } +// } + +// pub fn panes(&self) -> &[ViewHandle] { +// &self.panes +// } + +// pub fn active_pane(&self) -> &ViewHandle { +// &self.active_pane +// } + +// fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { +// self.follower_states.retain(|_, state| { +// if state.leader_id == peer_id { +// for item in state.items_by_leader_view_id.values() { +// item.set_leader_peer_id(None, cx); +// } +// false +// } else { +// true +// } +// }); +// cx.notify(); +// } + +// fn start_following( +// &mut self, +// leader_id: PeerId, +// cx: &mut ViewContext, +// ) -> Option>> { +// let pane = self.active_pane().clone(); + +// self.last_leaders_by_pane +// .insert(pane.downgrade(), leader_id); +// self.unfollow(&pane, cx); +// self.follower_states.insert( +// pane.clone(), +// FollowerState { +// leader_id, +// active_view_id: None, +// items_by_leader_view_id: Default::default(), +// }, +// ); +// cx.notify(); + +// let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); +// let project_id = self.project.read(cx).remote_id(); +// let request = self.app_state.client.request(proto::Follow { +// room_id, +// project_id, +// leader_id: Some(leader_id), +// }); + +// Some(cx.spawn(|this, mut cx| async move { +// let response = request.await?; +// this.update(&mut cx, |this, _| { +// let state = this +// .follower_states +// .get_mut(&pane) +// .ok_or_else(|| anyhow!("following interrupted"))?; +// state.active_view_id = if let Some(active_view_id) = response.active_view_id { +// Some(ViewId::from_proto(active_view_id)?) +// } else { +// None +// }; +// Ok::<_, anyhow::Error>(()) +// })??; +// Self::add_views_from_leader( +// this.clone(), +// leader_id, +// vec![pane], +// response.views, +// &mut cx, +// ) +// .await?; +// this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?; +// Ok(()) +// })) +// } + +// pub fn follow_next_collaborator( +// &mut self, +// _: &FollowNextCollaborator, +// cx: &mut ViewContext, +// ) -> Option>> { +// let collaborators = self.project.read(cx).collaborators(); +// let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) { +// let mut collaborators = collaborators.keys().copied(); +// for peer_id in collaborators.by_ref() { +// if peer_id == leader_id { +// break; +// } +// } +// collaborators.next() +// } else if let Some(last_leader_id) = +// self.last_leaders_by_pane.get(&self.active_pane.downgrade()) +// { +// if collaborators.contains_key(last_leader_id) { +// Some(*last_leader_id) +// } else { +// None +// } +// } else { +// None +// }; + +// let pane = self.active_pane.clone(); +// let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next()) +// else { +// return None; +// }; +// if Some(leader_id) == self.unfollow(&pane, cx) { +// return None; +// } +// self.follow(leader_id, cx) +// } + +// pub fn follow( +// &mut self, +// leader_id: PeerId, +// cx: &mut ViewContext, +// ) -> Option>> { +// let room = ActiveCall::global(cx).read(cx).room()?.read(cx); +// let project = self.project.read(cx); + +// let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else { +// return None; +// }; + +// let other_project_id = match remote_participant.location { +// call::ParticipantLocation::External => None, +// call::ParticipantLocation::UnsharedProject => None, +// call::ParticipantLocation::SharedProject { project_id } => { +// if Some(project_id) == project.remote_id() { +// None +// } else { +// Some(project_id) +// } +// } +// }; + +// // if they are active in another project, follow there. +// if let Some(project_id) = other_project_id { +// let app_state = self.app_state.clone(); +// return Some(crate::join_remote_project( +// project_id, +// remote_participant.user.id, +// app_state, +// cx, +// )); +// } + +// // if you're already following, find the right pane and focus it. +// for (pane, state) in &self.follower_states { +// if leader_id == state.leader_id { +// cx.focus(pane); +// return None; +// } +// } + +// // Otherwise, follow. +// self.start_following(leader_id, cx) +// } + +// pub fn unfollow( +// &mut self, +// pane: &ViewHandle, +// cx: &mut ViewContext, +// ) -> Option { +// let state = self.follower_states.remove(pane)?; +// let leader_id = state.leader_id; +// for (_, item) in state.items_by_leader_view_id { +// item.set_leader_peer_id(None, cx); +// } + +// if self +// .follower_states +// .values() +// .all(|state| state.leader_id != state.leader_id) +// { +// let project_id = self.project.read(cx).remote_id(); +// let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); +// self.app_state +// .client +// .send(proto::Unfollow { +// room_id, +// project_id, +// leader_id: Some(leader_id), +// }) +// .log_err(); +// } + +// cx.notify(); +// Some(leader_id) +// } + +// pub fn is_being_followed(&self, peer_id: PeerId) -> bool { +// self.follower_states +// .values() +// .any(|state| state.leader_id == peer_id) +// } + +// fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { +// // TODO: There should be a better system in place for this +// // (https://github.com/zed-industries/zed/issues/1290) +// let is_fullscreen = cx.window_is_fullscreen(); +// let container_theme = if is_fullscreen { +// let mut container_theme = theme.titlebar.container; +// container_theme.padding.left = container_theme.padding.right; +// container_theme +// } else { +// theme.titlebar.container +// }; + +// enum TitleBar {} +// MouseEventHandler::new::(0, cx, |_, cx| { +// Stack::new() +// .with_children( +// self.titlebar_item +// .as_ref() +// .map(|item| ChildView::new(item, cx)), +// ) +// .contained() +// .with_style(container_theme) +// }) +// .on_click(MouseButton::Left, |event, _, cx| { +// if event.click_count == 2 { +// cx.zoom_window(); +// } +// }) +// .constrained() +// .with_height(theme.titlebar.height) +// .into_any_named("titlebar") +// } + +// fn active_item_path_changed(&mut self, cx: &mut ViewContext) { +// let active_entry = self.active_project_path(cx); +// self.project +// .update(cx, |project, cx| project.set_active_path(active_entry, cx)); +// self.update_window_title(cx); +// } + +// fn update_window_title(&mut self, cx: &mut ViewContext) { +// let project = self.project().read(cx); +// let mut title = String::new(); + +// if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { +// let filename = path +// .path +// .file_name() +// .map(|s| s.to_string_lossy()) +// .or_else(|| { +// Some(Cow::Borrowed( +// project +// .worktree_for_id(path.worktree_id, cx)? +// .read(cx) +// .root_name(), +// )) +// }); + +// if let Some(filename) = filename { +// title.push_str(filename.as_ref()); +// title.push_str(" — "); +// } +// } + +// for (i, name) in project.worktree_root_names(cx).enumerate() { +// if i > 0 { +// title.push_str(", "); +// } +// title.push_str(name); +// } + +// if title.is_empty() { +// title = "empty project".to_string(); +// } + +// if project.is_remote() { +// title.push_str(" ↙"); +// } else if project.is_shared() { +// title.push_str(" ↗"); +// } + +// cx.set_window_title(&title); +// } + +// fn update_window_edited(&mut self, cx: &mut ViewContext) { +// let is_edited = !self.project.read(cx).is_read_only() +// && self +// .items(cx) +// .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); +// if is_edited != self.window_edited { +// self.window_edited = is_edited; +// cx.set_window_edited(self.window_edited) +// } +// } + +// fn render_disconnected_overlay( +// &self, +// cx: &mut ViewContext, +// ) -> Option> { +// if self.project.read(cx).is_read_only() { +// enum DisconnectedOverlay {} +// Some( +// MouseEventHandler::new::(0, cx, |_, cx| { +// let theme = &theme::current(cx); +// Label::new( +// "Your connection to the remote project has been lost.", +// theme.workspace.disconnected_overlay.text.clone(), +// ) +// .aligned() +// .contained() +// .with_style(theme.workspace.disconnected_overlay.container) +// }) +// .with_cursor_style(CursorStyle::Arrow) +// .capture_all() +// .into_any_named("disconnected overlay"), +// ) +// } else { +// None +// } +// } + +// fn render_notifications( +// &self, +// theme: &theme::Workspace, +// cx: &AppContext, +// ) -> Option> { +// if self.notifications.is_empty() { +// None +// } else { +// Some( +// Flex::column() +// .with_children(self.notifications.iter().map(|(_, _, notification)| { +// ChildView::new(notification.as_any(), cx) +// .contained() +// .with_style(theme.notification) +// })) +// .constrained() +// .with_width(theme.notifications.width) +// .contained() +// .with_style(theme.notifications.container) +// .aligned() +// .bottom() +// .right() +// .into_any(), +// ) +// } +// } + +// // RPC handlers + +// fn handle_follow( +// &mut self, +// follower_project_id: Option, +// cx: &mut ViewContext, +// ) -> proto::FollowResponse { +// let client = &self.app_state.client; +// let project_id = self.project.read(cx).remote_id(); + +// let active_view_id = self.active_item(cx).and_then(|i| { +// Some( +// i.to_followable_item_handle(cx)? +// .remote_id(client, cx)? +// .to_proto(), +// ) +// }); + +// cx.notify(); + +// self.last_active_view_id = active_view_id.clone(); +// proto::FollowResponse { +// active_view_id, +// views: self +// .panes() +// .iter() +// .flat_map(|pane| { +// let leader_id = self.leader_for_pane(pane); +// pane.read(cx).items().filter_map({ +// let cx = &cx; +// move |item| { +// let item = item.to_followable_item_handle(cx)?; +// if (project_id.is_none() || project_id != follower_project_id) +// && item.is_project_item(cx) +// { +// return None; +// } +// let id = item.remote_id(client, cx)?.to_proto(); +// let variant = item.to_state_proto(cx)?; +// Some(proto::View { +// id: Some(id), +// leader_id, +// variant: Some(variant), +// }) +// } +// }) +// }) +// .collect(), +// } +// } + +// fn handle_update_followers( +// &mut self, +// leader_id: PeerId, +// message: proto::UpdateFollowers, +// _cx: &mut ViewContext, +// ) { +// self.leader_updates_tx +// .unbounded_send((leader_id, message)) +// .ok(); +// } + +// async fn process_leader_update( +// this: &WeakViewHandle, +// leader_id: PeerId, +// update: proto::UpdateFollowers, +// cx: &mut AsyncAppContext, +// ) -> Result<()> { +// match update.variant.ok_or_else(|| anyhow!("invalid update"))? { +// proto::update_followers::Variant::UpdateActiveView(update_active_view) => { +// this.update(cx, |this, _| { +// for (_, state) in &mut this.follower_states { +// if state.leader_id == leader_id { +// state.active_view_id = +// if let Some(active_view_id) = update_active_view.id.clone() { +// Some(ViewId::from_proto(active_view_id)?) +// } else { +// None +// }; +// } +// } +// anyhow::Ok(()) +// })??; +// } +// proto::update_followers::Variant::UpdateView(update_view) => { +// let variant = update_view +// .variant +// .ok_or_else(|| anyhow!("missing update view variant"))?; +// let id = update_view +// .id +// .ok_or_else(|| anyhow!("missing update view id"))?; +// let mut tasks = Vec::new(); +// this.update(cx, |this, cx| { +// let project = this.project.clone(); +// for (_, state) in &mut this.follower_states { +// if state.leader_id == leader_id { +// let view_id = ViewId::from_proto(id.clone())?; +// if let Some(item) = state.items_by_leader_view_id.get(&view_id) { +// tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); +// } +// } +// } +// anyhow::Ok(()) +// })??; +// try_join_all(tasks).await.log_err(); +// } +// proto::update_followers::Variant::CreateView(view) => { +// let panes = this.read_with(cx, |this, _| { +// this.follower_states +// .iter() +// .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) +// .cloned() +// .collect() +// })?; +// Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?; +// } +// } +// this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?; +// Ok(()) +// } + +// async fn add_views_from_leader( +// this: WeakViewHandle, +// leader_id: PeerId, +// panes: Vec>, +// views: Vec, +// cx: &mut AsyncAppContext, +// ) -> Result<()> { +// let this = this +// .upgrade(cx) +// .ok_or_else(|| anyhow!("workspace dropped"))?; + +// let item_builders = cx.update(|cx| { +// cx.default_global::() +// .values() +// .map(|b| b.0) +// .collect::>() +// }); + +// let mut item_tasks_by_pane = HashMap::default(); +// for pane in panes { +// let mut item_tasks = Vec::new(); +// let mut leader_view_ids = Vec::new(); +// for view in &views { +// let Some(id) = &view.id else { continue }; +// let id = ViewId::from_proto(id.clone())?; +// let mut variant = view.variant.clone(); +// if variant.is_none() { +// Err(anyhow!("missing view variant"))?; +// } +// for build_item in &item_builders { +// let task = cx +// .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); +// if let Some(task) = task { +// item_tasks.push(task); +// leader_view_ids.push(id); +// break; +// } else { +// assert!(variant.is_some()); +// } +// } +// } + +// item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids)); +// } + +// for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { +// let items = futures::future::try_join_all(item_tasks).await?; +// this.update(cx, |this, cx| { +// let state = this.follower_states.get_mut(&pane)?; +// for (id, item) in leader_view_ids.into_iter().zip(items) { +// item.set_leader_peer_id(Some(leader_id), cx); +// state.items_by_leader_view_id.insert(id, item); +// } + +// Some(()) +// }); +// } +// Ok(()) +// } + +// fn update_active_view_for_followers(&mut self, cx: &AppContext) { +// let mut is_project_item = true; +// let mut update = proto::UpdateActiveView::default(); +// if self.active_pane.read(cx).has_focus() { +// let item = self +// .active_item(cx) +// .and_then(|item| item.to_followable_item_handle(cx)); +// if let Some(item) = item { +// is_project_item = item.is_project_item(cx); +// update = proto::UpdateActiveView { +// id: item +// .remote_id(&self.app_state.client, cx) +// .map(|id| id.to_proto()), +// leader_id: self.leader_for_pane(&self.active_pane), +// }; +// } +// } + +// if update.id != self.last_active_view_id { +// self.last_active_view_id = update.id.clone(); +// self.update_followers( +// is_project_item, +// proto::update_followers::Variant::UpdateActiveView(update), +// cx, +// ); +// } +// } + +// fn update_followers( +// &self, +// project_only: bool, +// update: proto::update_followers::Variant, +// cx: &AppContext, +// ) -> Option<()> { +// let project_id = if project_only { +// self.project.read(cx).remote_id() +// } else { +// None +// }; +// self.app_state().workspace_store.read_with(cx, |store, cx| { +// store.update_followers(project_id, update, cx) +// }) +// } + +// pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { +// self.follower_states.get(pane).map(|state| state.leader_id) +// } + +// fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { +// cx.notify(); + +// let call = self.active_call()?; +// let room = call.read(cx).room()?.read(cx); +// let participant = room.remote_participant_for_peer_id(leader_id)?; +// let mut items_to_activate = Vec::new(); + +// let leader_in_this_app; +// let leader_in_this_project; +// match participant.location { +// call::ParticipantLocation::SharedProject { project_id } => { +// leader_in_this_app = true; +// leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); +// } +// call::ParticipantLocation::UnsharedProject => { +// leader_in_this_app = true; +// leader_in_this_project = false; +// } +// call::ParticipantLocation::External => { +// leader_in_this_app = false; +// leader_in_this_project = false; +// } +// }; + +// for (pane, state) in &self.follower_states { +// if state.leader_id != leader_id { +// continue; +// } +// if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { +// if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { +// if leader_in_this_project || !item.is_project_item(cx) { +// items_to_activate.push((pane.clone(), item.boxed_clone())); +// } +// } else { +// log::warn!( +// "unknown view id {:?} for leader {:?}", +// active_view_id, +// leader_id +// ); +// } +// continue; +// } +// if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { +// items_to_activate.push((pane.clone(), Box::new(shared_screen))); +// } +// } + +// for (pane, item) in items_to_activate { +// let pane_was_focused = pane.read(cx).has_focus(); +// if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { +// pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); +// } else { +// pane.update(cx, |pane, cx| { +// pane.add_item(item.boxed_clone(), false, false, None, cx) +// }); +// } + +// if pane_was_focused { +// pane.update(cx, |pane, cx| pane.focus_active_item(cx)); +// } +// } + +// None +// } + +// fn shared_screen_for_peer( +// &self, +// peer_id: PeerId, +// pane: &ViewHandle, +// cx: &mut ViewContext, +// ) -> Option> { +// let call = self.active_call()?; +// let room = call.read(cx).room()?.read(cx); +// let participant = room.remote_participant_for_peer_id(peer_id)?; +// let track = participant.video_tracks.values().next()?.clone(); +// let user = participant.user.clone(); + +// for item in pane.read(cx).items_of_type::() { +// if item.read(cx).peer_id == peer_id { +// return Some(item); +// } +// } + +// Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) +// } + +// pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { +// if active { +// self.update_active_view_for_followers(cx); +// cx.background() +// .spawn(persistence::DB.update_timestamp(self.database_id())) +// .detach(); +// } else { +// for pane in &self.panes { +// pane.update(cx, |pane, cx| { +// if let Some(item) = pane.active_item() { +// item.workspace_deactivated(cx); +// } +// if matches!( +// settings::get::(cx).autosave, +// AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange +// ) { +// for item in pane.items() { +// Pane::autosave_item(item.as_ref(), self.project.clone(), cx) +// .detach_and_log_err(cx); +// } +// } +// }); +// } +// } +// } + +// fn active_call(&self) -> Option<&ModelHandle> { +// self.active_call.as_ref().map(|(call, _)| call) +// } + +// fn on_active_call_event( +// &mut self, +// _: ModelHandle, +// event: &call::room::Event, +// cx: &mut ViewContext, +// ) { +// match event { +// call::room::Event::ParticipantLocationChanged { participant_id } +// | call::room::Event::RemoteVideoTracksChanged { participant_id } => { +// self.leader_updated(*participant_id, cx); +// } +// _ => {} +// } +// } + +// pub fn database_id(&self) -> WorkspaceId { +// self.database_id +// } + +// fn location(&self, cx: &AppContext) -> Option { +// let project = self.project().read(cx); + +// if project.is_local() { +// Some( +// project +// .visible_worktrees(cx) +// .map(|worktree| worktree.read(cx).abs_path()) +// .collect::>() +// .into(), +// ) +// } else { +// None +// } +// } + +// fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { +// match member { +// Member::Axis(PaneAxis { members, .. }) => { +// for child in members.iter() { +// self.remove_panes(child.clone(), cx) +// } +// } +// Member::Pane(pane) => { +// self.force_remove_pane(&pane, cx); +// } +// } +// } + +// fn force_remove_pane(&mut self, pane: &ViewHandle, cx: &mut ViewContext) { +// self.panes.retain(|p| p != pane); +// cx.focus(self.panes.last().unwrap()); +// if self.last_active_center_pane == Some(pane.downgrade()) { +// self.last_active_center_pane = None; +// } +// cx.notify(); +// } + +// fn schedule_serialize(&mut self, cx: &mut ViewContext) { +// self._schedule_serialize = Some(cx.spawn(|this, cx| async move { +// cx.background().timer(Duration::from_millis(100)).await; +// this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) +// .ok(); +// })); +// } + +// fn serialize_workspace(&self, cx: &ViewContext) { +// fn serialize_pane_handle( +// pane_handle: &ViewHandle, +// cx: &AppContext, +// ) -> SerializedPane { +// let (items, active) = { +// let pane = pane_handle.read(cx); +// let active_item_id = pane.active_item().map(|item| item.id()); +// ( +// pane.items() +// .filter_map(|item_handle| { +// Some(SerializedItem { +// kind: Arc::from(item_handle.serialized_item_kind()?), +// item_id: item_handle.id(), +// active: Some(item_handle.id()) == active_item_id, +// }) +// }) +// .collect::>(), +// pane.has_focus(), +// ) +// }; + +// SerializedPane::new(items, active) +// } + +// fn build_serialized_pane_group( +// pane_group: &Member, +// cx: &AppContext, +// ) -> SerializedPaneGroup { +// match pane_group { +// Member::Axis(PaneAxis { +// axis, +// members, +// flexes, +// bounding_boxes: _, +// }) => SerializedPaneGroup::Group { +// axis: *axis, +// children: members +// .iter() +// .map(|member| build_serialized_pane_group(member, cx)) +// .collect::>(), +// flexes: Some(flexes.borrow().clone()), +// }, +// Member::Pane(pane_handle) => { +// SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) +// } +// } +// } + +// fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { +// let left_dock = this.left_dock.read(cx); +// let left_visible = left_dock.is_open(); +// let left_active_panel = left_dock.visible_panel().and_then(|panel| { +// Some( +// cx.view_ui_name(panel.as_any().window(), panel.id())? +// .to_string(), +// ) +// }); +// let left_dock_zoom = left_dock +// .visible_panel() +// .map(|panel| panel.is_zoomed(cx)) +// .unwrap_or(false); + +// let right_dock = this.right_dock.read(cx); +// let right_visible = right_dock.is_open(); +// let right_active_panel = right_dock.visible_panel().and_then(|panel| { +// Some( +// cx.view_ui_name(panel.as_any().window(), panel.id())? +// .to_string(), +// ) +// }); +// let right_dock_zoom = right_dock +// .visible_panel() +// .map(|panel| panel.is_zoomed(cx)) +// .unwrap_or(false); + +// let bottom_dock = this.bottom_dock.read(cx); +// let bottom_visible = bottom_dock.is_open(); +// let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { +// Some( +// cx.view_ui_name(panel.as_any().window(), panel.id())? +// .to_string(), +// ) +// }); +// let bottom_dock_zoom = bottom_dock +// .visible_panel() +// .map(|panel| panel.is_zoomed(cx)) +// .unwrap_or(false); + +// DockStructure { +// left: DockData { +// visible: left_visible, +// active_panel: left_active_panel, +// zoom: left_dock_zoom, +// }, +// right: DockData { +// visible: right_visible, +// active_panel: right_active_panel, +// zoom: right_dock_zoom, +// }, +// bottom: DockData { +// visible: bottom_visible, +// active_panel: bottom_active_panel, +// zoom: bottom_dock_zoom, +// }, +// } +// } + +// if let Some(location) = self.location(cx) { +// // Load bearing special case: +// // - with_local_workspace() relies on this to not have other stuff open +// // when you open your log +// if !location.paths().is_empty() { +// let center_group = build_serialized_pane_group(&self.center.root, cx); +// let docks = build_serialized_docks(self, cx); + +// let serialized_workspace = SerializedWorkspace { +// id: self.database_id, +// location, +// center_group, +// bounds: Default::default(), +// display: Default::default(), +// docks, +// }; + +// cx.background() +// .spawn(persistence::DB.save_workspace(serialized_workspace)) +// .detach(); +// } +// } +// } + +// pub(crate) fn load_workspace( +// workspace: WeakViewHandle, +// serialized_workspace: SerializedWorkspace, +// paths_to_open: Vec>, +// cx: &mut AppContext, +// ) -> Task>>>> { +// cx.spawn(|mut cx| async move { +// let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { +// ( +// workspace.project().clone(), +// workspace.last_active_center_pane.clone(), +// ) +// })?; + +// let mut center_group = None; +// let mut center_items = None; +// // Traverse the splits tree and add to things +// if let Some((group, active_pane, items)) = serialized_workspace +// .center_group +// .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) +// .await +// { +// center_items = Some(items); +// center_group = Some((group, active_pane)) +// } + +// let mut items_by_project_path = cx.read(|cx| { +// center_items +// .unwrap_or_default() +// .into_iter() +// .filter_map(|item| { +// let item = item?; +// let project_path = item.project_path(cx)?; +// Some((project_path, item)) +// }) +// .collect::>() +// }); + +// let opened_items = paths_to_open +// .into_iter() +// .map(|path_to_open| { +// path_to_open +// .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) +// }) +// .collect::>(); + +// // Remove old panes from workspace panes list +// workspace.update(&mut cx, |workspace, cx| { +// if let Some((center_group, active_pane)) = center_group { +// workspace.remove_panes(workspace.center.root.clone(), cx); + +// // Swap workspace center group +// workspace.center = PaneGroup::with_root(center_group); + +// // Change the focus to the workspace first so that we retrigger focus in on the pane. +// cx.focus_self(); + +// if let Some(active_pane) = active_pane { +// cx.focus(&active_pane); +// } else { +// cx.focus(workspace.panes.last().unwrap()); +// } +// } else { +// let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); +// if let Some(old_center_handle) = old_center_handle { +// cx.focus(&old_center_handle) +// } else { +// cx.focus_self() +// } +// } + +// let docks = serialized_workspace.docks; +// workspace.left_dock.update(cx, |dock, cx| { +// dock.set_open(docks.left.visible, cx); +// if let Some(active_panel) = docks.left.active_panel { +// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { +// dock.activate_panel(ix, cx); +// } +// } +// dock.active_panel() +// .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); +// if docks.left.visible && docks.left.zoom { +// cx.focus_self() +// } +// }); +// // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something +// workspace.right_dock.update(cx, |dock, cx| { +// dock.set_open(docks.right.visible, cx); +// if let Some(active_panel) = docks.right.active_panel { +// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { +// dock.activate_panel(ix, cx); +// } +// } +// dock.active_panel() +// .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); + +// if docks.right.visible && docks.right.zoom { +// cx.focus_self() +// } +// }); +// workspace.bottom_dock.update(cx, |dock, cx| { +// dock.set_open(docks.bottom.visible, cx); +// if let Some(active_panel) = docks.bottom.active_panel { +// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { +// dock.activate_panel(ix, cx); +// } +// } + +// dock.active_panel() +// .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); + +// if docks.bottom.visible && docks.bottom.zoom { +// cx.focus_self() +// } +// }); + +// cx.notify(); +// })?; + +// // Serialize ourself to make sure our timestamps and any pane / item changes are replicated +// workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; + +// Ok(opened_items) +// }) +// } + +// #[cfg(any(test, feature = "test-support"))] +// pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { +// use node_runtime::FakeNodeRuntime; + +// let client = project.read(cx).client(); +// let user_store = project.read(cx).user_store(); + +// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); +// let app_state = Arc::new(AppState { +// languages: project.read(cx).languages().clone(), +// workspace_store, +// client, +// user_store, +// fs: project.read(cx).fs().clone(), +// build_window_options: |_, _, _| Default::default(), +// initialize_workspace: |_, _, _, _| Task::ready(Ok(())), +// background_actions: || &[], +// node_runtime: FakeNodeRuntime::new(), +// }); +// Self::new(0, project, app_state, cx) +// } + +// fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { +// let dock = match position { +// DockPosition::Left => &self.left_dock, +// DockPosition::Right => &self.right_dock, +// DockPosition::Bottom => &self.bottom_dock, +// }; +// let active_panel = dock.read(cx).visible_panel()?; +// let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { +// dock.read(cx).render_placeholder(cx) +// } else { +// ChildView::new(dock, cx).into_any() +// }; + +// Some( +// element +// .constrained() +// .dynamically(move |constraint, _, cx| match position { +// DockPosition::Left | DockPosition::Right => SizeConstraint::new( +// Vector2F::new(20., constraint.min.y()), +// Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), +// ), +// DockPosition::Bottom => SizeConstraint::new( +// Vector2F::new(constraint.min.x(), 20.), +// Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), +// ), +// }) +// .into_any(), +// ) +// } +// } + +// fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { +// ZED_WINDOW_POSITION +// .zip(*ZED_WINDOW_SIZE) +// .map(|(position, size)| { +// WindowBounds::Fixed(RectF::new( +// cx.platform().screens()[0].bounds().origin() + position, +// size, +// )) +// }) +// } + +// async fn open_items( +// serialized_workspace: Option, +// workspace: &WeakViewHandle, +// mut project_paths_to_open: Vec<(PathBuf, Option)>, +// app_state: Arc, +// mut cx: AsyncAppContext, +// ) -> Result>>>> { +// let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); + +// if let Some(serialized_workspace) = serialized_workspace { +// let workspace = workspace.clone(); +// let restored_items = cx +// .update(|cx| { +// Workspace::load_workspace( +// workspace, +// serialized_workspace, +// project_paths_to_open +// .iter() +// .map(|(_, project_path)| project_path) +// .cloned() +// .collect(), +// cx, +// ) +// }) +// .await?; + +// let restored_project_paths = cx.read(|cx| { +// restored_items +// .iter() +// .filter_map(|item| item.as_ref()?.project_path(cx)) +// .collect::>() +// }); + +// for restored_item in restored_items { +// opened_items.push(restored_item.map(Ok)); +// } + +// project_paths_to_open +// .iter_mut() +// .for_each(|(_, project_path)| { +// if let Some(project_path_to_open) = project_path { +// if restored_project_paths.contains(project_path_to_open) { +// *project_path = None; +// } +// } +// }); +// } else { +// for _ in 0..project_paths_to_open.len() { +// opened_items.push(None); +// } +// } +// assert!(opened_items.len() == project_paths_to_open.len()); + +// let tasks = +// project_paths_to_open +// .into_iter() +// .enumerate() +// .map(|(i, (abs_path, project_path))| { +// let workspace = workspace.clone(); +// cx.spawn(|mut cx| { +// let fs = app_state.fs.clone(); +// async move { +// let file_project_path = project_path?; +// if fs.is_file(&abs_path).await { +// Some(( +// i, +// workspace +// .update(&mut cx, |workspace, cx| { +// workspace.open_path(file_project_path, None, true, cx) +// }) +// .log_err()? +// .await, +// )) +// } else { +// None +// } +// } +// }) +// }); + +// for maybe_opened_path in futures::future::join_all(tasks.into_iter()) +// .await +// .into_iter() +// { +// if let Some((i, path_open_result)) = maybe_opened_path { +// opened_items[i] = Some(path_open_result); +// } +// } + +// Ok(opened_items) +// } + +// fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { +// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; +// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; +// const MESSAGE_ID: usize = 2; + +// if workspace +// .read_with(cx, |workspace, cx| { +// workspace.has_shown_notification_once::(MESSAGE_ID, cx) +// }) +// .unwrap_or(false) +// { +// return; +// } + +// if db::kvp::KEY_VALUE_STORE +// .read_kvp(NEW_DOCK_HINT_KEY) +// .ok() +// .flatten() +// .is_some() +// { +// if !workspace +// .read_with(cx, |workspace, cx| { +// workspace.has_shown_notification_once::(MESSAGE_ID, cx) +// }) +// .unwrap_or(false) +// { +// cx.update(|cx| { +// cx.update_global::(|tracker, _| { +// let entry = tracker +// .entry(TypeId::of::()) +// .or_default(); +// if !entry.contains(&MESSAGE_ID) { +// entry.push(MESSAGE_ID); +// } +// }); +// }); +// } + +// return; +// } + +// cx.spawn(|_| async move { +// db::kvp::KEY_VALUE_STORE +// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) +// .await +// .ok(); +// }) +// .detach(); + +// workspace +// .update(cx, |workspace, cx| { +// workspace.show_notification_once(2, cx, |cx| { +// cx.add_view(|_| { +// MessageNotification::new_element(|text, _| { +// Text::new( +// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", +// text, +// ) +// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { +// let code_span_background_color = settings::get::(cx) +// .theme +// .editor +// .document_highlight_read_background; + +// cx.scene().push_quad(gpui::Quad { +// bounds, +// background: Some(code_span_background_color), +// border: Default::default(), +// corner_radii: (2.0).into(), +// }) +// }) +// .into_any() +// }) +// .with_click_message("Read more about the new panel system") +// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) +// }) +// }) +// }) +// .ok(); +// } + +// fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { +// const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; + +// workspace +// .update(cx, |workspace, cx| { +// if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { +// workspace.show_notification_once(0, cx, |cx| { +// cx.add_view(|_| { +// MessageNotification::new("Failed to load the database file.") +// .with_click_message("Click to let us know about this error") +// .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) +// }) +// }); +// } +// }) +// .log_err(); +// } + +// impl Entity for Workspace { +// type Event = Event; + +// fn release(&mut self, cx: &mut AppContext) { +// self.app_state.workspace_store.update(cx, |store, _| { +// store.workspaces.remove(&self.weak_self); +// }) +// } +// } + +// impl View for Workspace { +// fn ui_name() -> &'static str { +// "Workspace" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = theme::current(cx).clone(); +// Stack::new() +// .with_child( +// Flex::column() +// .with_child(self.render_titlebar(&theme, cx)) +// .with_child( +// Stack::new() +// .with_child({ +// let project = self.project.clone(); +// Flex::row() +// .with_children(self.render_dock(DockPosition::Left, cx)) +// .with_child( +// Flex::column() +// .with_child( +// FlexItem::new( +// self.center.render( +// &project, +// &theme, +// &self.follower_states, +// self.active_call(), +// self.active_pane(), +// self.zoomed +// .as_ref() +// .and_then(|zoomed| zoomed.upgrade(cx)) +// .as_ref(), +// &self.app_state, +// cx, +// ), +// ) +// .flex(1., true), +// ) +// .with_children( +// self.render_dock(DockPosition::Bottom, cx), +// ) +// .flex(1., true), +// ) +// .with_children(self.render_dock(DockPosition::Right, cx)) +// }) +// .with_child(Overlay::new( +// Stack::new() +// .with_children(self.zoomed.as_ref().and_then(|zoomed| { +// enum ZoomBackground {} +// let zoomed = zoomed.upgrade(cx)?; + +// let mut foreground_style = +// theme.workspace.zoomed_pane_foreground; +// if let Some(zoomed_dock_position) = self.zoomed_position { +// foreground_style = +// theme.workspace.zoomed_panel_foreground; +// let margin = foreground_style.margin.top; +// let border = foreground_style.border.top; + +// // Only include a margin and border on the opposite side. +// foreground_style.margin.top = 0.; +// foreground_style.margin.left = 0.; +// foreground_style.margin.bottom = 0.; +// foreground_style.margin.right = 0.; +// foreground_style.border.top = false; +// foreground_style.border.left = false; +// foreground_style.border.bottom = false; +// foreground_style.border.right = false; +// match zoomed_dock_position { +// DockPosition::Left => { +// foreground_style.margin.right = margin; +// foreground_style.border.right = border; +// } +// DockPosition::Right => { +// foreground_style.margin.left = margin; +// foreground_style.border.left = border; +// } +// DockPosition::Bottom => { +// foreground_style.margin.top = margin; +// foreground_style.border.top = border; +// } +// } +// } + +// Some( +// ChildView::new(&zoomed, cx) +// .contained() +// .with_style(foreground_style) +// .aligned() +// .contained() +// .with_style(theme.workspace.zoomed_background) +// .mouse::(0) +// .capture_all() +// .on_down( +// MouseButton::Left, +// |_, this: &mut Self, cx| { +// this.zoom_out(cx); +// }, +// ), +// ) +// })) +// .with_children(self.modal.as_ref().map(|modal| { +// // Prevent clicks within the modal from falling +// // through to the rest of the workspace. +// enum ModalBackground {} +// MouseEventHandler::new::( +// 0, +// cx, +// |_, cx| ChildView::new(modal.view.as_any(), cx), +// ) +// .on_click(MouseButton::Left, |_, _, _| {}) +// .contained() +// .with_style(theme.workspace.modal) +// .aligned() +// .top() +// })) +// .with_children(self.render_notifications(&theme.workspace, cx)), +// )) +// .provide_resize_bounds::() +// .flex(1.0, true), +// ) +// .with_child(ChildView::new(&self.status_bar, cx)) +// .contained() +// .with_background_color(theme.workspace.background), +// ) +// .with_children(DragAndDrop::render(cx)) +// .with_children(self.render_disconnected_overlay(cx)) +// .into_any_named("workspace") +// } + +// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { +// if cx.is_self_focused() { +// cx.focus(&self.active_pane); +// } +// } + +// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext) -> bool { +// DragAndDrop::::update_modifiers(e.modifiers, cx) +// } +// } + +// impl WorkspaceStore { +// pub fn new(client: Arc, cx: &mut ModelContext) -> Self { +// Self { +// workspaces: Default::default(), +// followers: Default::default(), +// _subscriptions: vec![ +// client.add_request_handler(cx.handle(), Self::handle_follow), +// client.add_message_handler(cx.handle(), Self::handle_unfollow), +// client.add_message_handler(cx.handle(), Self::handle_update_followers), +// ], +// client, +// } +// } + +// pub fn update_followers( +// &self, +// project_id: Option, +// update: proto::update_followers::Variant, +// cx: &AppContext, +// ) -> Option<()> { +// if !cx.has_global::>() { +// return None; +// } + +// let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); +// let follower_ids: Vec<_> = self +// .followers +// .iter() +// .filter_map(|follower| { +// if follower.project_id == project_id || project_id.is_none() { +// Some(follower.peer_id.into()) +// } else { +// None +// } +// }) +// .collect(); +// if follower_ids.is_empty() { +// return None; +// } +// self.client +// .send(proto::UpdateFollowers { +// room_id, +// project_id, +// follower_ids, +// variant: Some(update), +// }) +// .log_err() +// } + +// async fn handle_follow( +// this: ModelHandle, +// envelope: TypedEnvelope, +// _: Arc, +// mut cx: AsyncAppContext, +// ) -> Result { +// this.update(&mut cx, |this, cx| { +// let follower = Follower { +// project_id: envelope.payload.project_id, +// peer_id: envelope.original_sender_id()?, +// }; +// let active_project = ActiveCall::global(cx) +// .read(cx) +// .location() +// .map(|project| project.id()); + +// let mut response = proto::FollowResponse::default(); +// for workspace in &this.workspaces { +// let Some(workspace) = workspace.upgrade(cx) else { +// continue; +// }; + +// workspace.update(cx.as_mut(), |workspace, cx| { +// let handler_response = workspace.handle_follow(follower.project_id, cx); +// if response.views.is_empty() { +// response.views = handler_response.views; +// } else { +// response.views.extend_from_slice(&handler_response.views); +// } + +// if let Some(active_view_id) = handler_response.active_view_id.clone() { +// if response.active_view_id.is_none() +// || Some(workspace.project.id()) == active_project +// { +// response.active_view_id = Some(active_view_id); +// } +// } +// }); +// } + +// if let Err(ix) = this.followers.binary_search(&follower) { +// this.followers.insert(ix, follower); +// } + +// Ok(response) +// }) +// } + +// async fn handle_unfollow( +// this: ModelHandle, +// envelope: TypedEnvelope, +// _: Arc, +// mut cx: AsyncAppContext, +// ) -> Result<()> { +// this.update(&mut cx, |this, _| { +// let follower = Follower { +// project_id: envelope.payload.project_id, +// peer_id: envelope.original_sender_id()?, +// }; +// if let Ok(ix) = this.followers.binary_search(&follower) { +// this.followers.remove(ix); +// } +// Ok(()) +// }) +// } + +// async fn handle_update_followers( +// this: ModelHandle, +// envelope: TypedEnvelope, +// _: Arc, +// mut cx: AsyncAppContext, +// ) -> Result<()> { +// let leader_id = envelope.original_sender_id()?; +// let update = envelope.payload; +// this.update(&mut cx, |this, cx| { +// for workspace in &this.workspaces { +// let Some(workspace) = workspace.upgrade(cx) else { +// continue; +// }; +// workspace.update(cx.as_mut(), |workspace, cx| { +// let project_id = workspace.project.read(cx).remote_id(); +// if update.project_id != project_id && update.project_id.is_some() { +// return; +// } +// workspace.handle_update_followers(leader_id, update.clone(), cx); +// }); +// } +// Ok(()) +// }) +// } +// } + +// impl Entity for WorkspaceStore { +// type Event = (); +// } + +// impl ViewId { +// pub(crate) fn from_proto(message: proto::ViewId) -> Result { +// Ok(Self { +// creator: message +// .creator +// .ok_or_else(|| anyhow!("creator is missing"))?, +// id: message.id, +// }) +// } + +// pub(crate) fn to_proto(&self) -> proto::ViewId { +// proto::ViewId { +// creator: Some(self.creator), +// id: self.id, +// } +// } +// } + +// pub trait WorkspaceHandle { +// fn file_project_paths(&self, cx: &AppContext) -> Vec; +// } + +// impl WorkspaceHandle for ViewHandle { +// fn file_project_paths(&self, cx: &AppContext) -> Vec { +// self.read(cx) +// .worktrees(cx) +// .flat_map(|worktree| { +// let worktree_id = worktree.read(cx).id(); +// worktree.read(cx).files(true, 0).map(move |f| ProjectPath { +// worktree_id, +// path: f.path.clone(), +// }) +// }) +// .collect::>() +// } +// } + +// impl std::fmt::Debug for OpenPaths { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// f.debug_struct("OpenPaths") +// .field("paths", &self.paths) +// .finish() +// } +// } + +// 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 last_opened_workspace_paths() -> Option { +// DB.last_workspace().await.log_err().flatten() +// } + +// async fn join_channel_internal( +// channel_id: u64, +// app_state: &Arc, +// requesting_window: Option>, +// active_call: &ModelHandle, +// cx: &mut AsyncAppContext, +// ) -> Result { +// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { +// let Some(room) = active_call.room().map(|room| room.read(cx)) else { +// return (false, None); +// }; + +// let already_in_channel = room.channel_id() == Some(channel_id); +// let should_prompt = room.is_sharing_project() +// && room.remote_participants().len() > 0 +// && !already_in_channel; +// let open_room = if already_in_channel { +// active_call.room().cloned() +// } else { +// None +// }; +// (should_prompt, open_room) +// }); + +// if let Some(room) = open_room { +// let task = room.update(cx, |room, cx| { +// if let Some((project, host)) = room.most_active_project(cx) { +// return Some(join_remote_project(project, host, app_state.clone(), cx)); +// } + +// None +// }); +// if let Some(task) = task { +// task.await?; +// } +// return anyhow::Ok(true); +// } + +// if should_prompt { +// if let Some(workspace) = requesting_window { +// if let Some(window) = workspace.update(cx, |cx| cx.window()) { +// let answer = window.prompt( +// PromptLevel::Warning, +// "Leaving this call will unshare your current project.\nDo you want to switch channels?", +// &["Yes, Join Channel", "Cancel"], +// cx, +// ); + +// if let Some(mut answer) = answer { +// if answer.next().await == Some(1) { +// return Ok(false); +// } +// } +// } else { +// return Ok(false); // unreachable!() hopefully +// } +// } else { +// return Ok(false); // unreachable!() hopefully +// } +// } + +// let client = cx.read(|cx| active_call.read(cx).client()); + +// let mut client_status = client.status(); + +// // this loop will terminate within client::CONNECTION_TIMEOUT seconds. +// 'outer: loop { +// let Some(status) = client_status.recv().await else { +// return Err(anyhow!("error connecting")); +// }; + +// match status { +// Status::Connecting +// | Status::Authenticating +// | Status::Reconnecting +// | Status::Reauthenticating => continue, +// Status::Connected { .. } => break 'outer, +// Status::SignedOut => return Err(anyhow!("not signed in")), +// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), +// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { +// return Err(anyhow!("zed is offline")) +// } +// } +// } + +// let room = active_call +// .update(cx, |active_call, cx| { +// active_call.join_channel(channel_id, cx) +// }) +// .await?; + +// room.update(cx, |room, _| room.room_update_completed()) +// .await; + +// let task = room.update(cx, |room, cx| { +// if let Some((project, host)) = room.most_active_project(cx) { +// return Some(join_remote_project(project, host, app_state.clone(), cx)); +// } + +// None +// }); +// if let Some(task) = task { +// task.await?; +// return anyhow::Ok(true); +// } +// anyhow::Ok(false) +// } + +// pub fn join_channel( +// channel_id: u64, +// app_state: Arc, +// requesting_window: Option>, +// cx: &mut AppContext, +// ) -> Task> { +// let active_call = ActiveCall::global(cx); +// cx.spawn(|mut cx| async move { +// let result = join_channel_internal( +// channel_id, +// &app_state, +// requesting_window, +// &active_call, +// &mut cx, +// ) +// .await; + +// // join channel succeeded, and opened a window +// if matches!(result, Ok(true)) { +// return anyhow::Ok(()); +// } + +// if requesting_window.is_some() { +// return anyhow::Ok(()); +// } + +// // find an existing workspace to focus and show call controls +// let mut active_window = activate_any_workspace_window(&mut cx); +// if active_window.is_none() { +// // no open workspaces, make one to show the error in (blergh) +// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) +// .await; +// } + +// active_window = activate_any_workspace_window(&mut cx); +// if active_window.is_none() { +// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window +// } + +// if let Err(err) = result { +// let prompt = active_window.unwrap().prompt( +// PromptLevel::Critical, +// &format!("Failed to join channel: {}", err), +// &["Ok"], +// &mut cx, +// ); +// if let Some(mut prompt) = prompt { +// prompt.next().await; +// } else { +// return Err(err); +// } +// } + +// // return ok, we showed the error to the user. +// return anyhow::Ok(()); +// }) +// } + +// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { +// for window in cx.windows() { +// let found = window.update(cx, |cx| { +// let is_workspace = cx.root_view().clone().downcast::().is_some(); +// if is_workspace { +// cx.activate_window(); +// } +// is_workspace +// }); +// if found == Some(true) { +// return Some(window); +// } +// } +// 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) +// } +// }) +// } + +// pub fn open_new( +// app_state: &Arc, +// cx: &mut AppContext, +// init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, +// ) -> Task<()> { +// let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); +// cx.spawn(|mut cx| async move { +// let (workspace, opened_paths) = task.await; + +// workspace +// .update(&mut cx, |workspace, cx| { +// if opened_paths.is_empty() { +// init(workspace, cx) +// } +// }) +// .log_err(); +// }) +// } + +// pub fn create_and_open_local_file( +// path: &'static Path, +// cx: &mut ViewContext, +// default_content: impl 'static + Send + FnOnce() -> Rope, +// ) -> Task>> { +// cx.spawn(|workspace, mut cx| async move { +// let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?; +// if !fs.is_file(path).await { +// fs.create_file(path, Default::default()).await?; +// fs.save(path, &default_content(), Default::default()) +// .await?; +// } + +// let mut items = workspace +// .update(&mut cx, |workspace, cx| { +// workspace.with_local_workspace(cx, |workspace, cx| { +// workspace.open_paths(vec![path.to_path_buf()], false, cx) +// }) +// })? +// .await? +// .await; + +// let item = items.pop().flatten(); +// item.ok_or_else(|| anyhow!("path {path:?} is not a file"))? +// }) +// } + +// pub fn join_remote_project( +// project_id: u64, +// follow_user_id: u64, +// app_state: Arc, +// cx: &mut AppContext, +// ) -> Task> { +// cx.spawn(|mut cx| async move { +// let windows = cx.windows(); +// let existing_workspace = windows.into_iter().find_map(|window| { +// window.downcast::().and_then(|window| { +// window +// .read_root_with(&cx, |workspace, cx| { +// if workspace.project().read(cx).remote_id() == Some(project_id) { +// Some(cx.handle().downgrade()) +// } else { +// None +// } +// }) +// .unwrap_or(None) +// }) +// }); + +// let workspace = if let Some(existing_workspace) = existing_workspace { +// existing_workspace +// } else { +// let active_call = cx.read(ActiveCall::global); +// let room = active_call +// .read_with(&cx, |call, _| call.room().cloned()) +// .ok_or_else(|| anyhow!("not in a call"))?; +// let project = room +// .update(&mut cx, |room, cx| { +// room.join_project( +// project_id, +// app_state.languages.clone(), +// app_state.fs.clone(), +// cx, +// ) +// }) +// .await?; + +// let window_bounds_override = window_bounds_env_override(&cx); +// let window = cx.add_window( +// (app_state.build_window_options)( +// window_bounds_override, +// None, +// cx.platform().as_ref(), +// ), +// |cx| Workspace::new(0, project, app_state.clone(), cx), +// ); +// let workspace = window.root(&cx).unwrap(); +// (app_state.initialize_workspace)( +// workspace.downgrade(), +// false, +// app_state.clone(), +// cx.clone(), +// ) +// .await +// .log_err(); + +// workspace.downgrade() +// }; + +// workspace.window().activate(&mut cx); +// cx.platform().activate(true); + +// workspace.update(&mut cx, |workspace, cx| { +// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { +// let follow_peer_id = room +// .read(cx) +// .remote_participants() +// .iter() +// .find(|(_, participant)| participant.user.id == follow_user_id) +// .map(|(_, p)| p.peer_id) +// .or_else(|| { +// // If we couldn't follow the given user, follow the host instead. +// let collaborator = workspace +// .project() +// .read(cx) +// .collaborators() +// .values() +// .find(|collaborator| collaborator.replica_id == 0)?; +// Some(collaborator.peer_id) +// }); + +// if let Some(follow_peer_id) = follow_peer_id { +// workspace +// .follow(follow_peer_id, cx) +// .map(|follow| follow.detach_and_log_err(cx)); +// } +// } +// })?; + +// anyhow::Ok(()) +// }) +// } + +// pub fn restart(_: &Restart, cx: &mut AppContext) { +// let should_confirm = settings::get::(cx).confirm_quit; +// cx.spawn(|mut cx| async move { +// let mut workspace_windows = cx +// .windows() +// .into_iter() +// .filter_map(|window| window.downcast::()) +// .collect::>(); + +// // If multiple windows have unsaved changes, and need a save prompt, +// // prompt in the active window before switching to a different window. +// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false)); + +// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) { +// let answer = window.prompt( +// PromptLevel::Info, +// "Are you sure you want to restart?", +// &["Restart", "Cancel"], +// &mut cx, +// ); + +// if let Some(mut answer) = answer { +// let answer = answer.next().await; +// if answer != Some(0) { +// return Ok(()); +// } +// } +// } + +// // If the user cancels any save prompt, then keep the app open. +// for window in workspace_windows { +// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { +// workspace.prepare_to_close(true, cx) +// }) { +// if !should_close.await? { +// return Ok(()); +// } +// } +// } +// cx.platform().restart(); +// anyhow::Ok(()) +// }) +// .detach_and_log_err(cx); +// } + +// fn parse_pixel_position_env_var(value: &str) -> Option { +// let mut parts = value.split(','); +// let width: usize = parts.next()?.parse().ok()?; +// let height: usize = parts.next()?.parse().ok()?; +// Some(vec2f(width as f32, height as f32)) +// } + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// dock::test::{TestPanel, TestPanelEvent}, +// item::test::{TestItem, TestItemEvent, TestProjectItem}, +// }; +// use fs::FakeFs; +// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext}; +// use project::{Project, ProjectEntryId}; +// use serde_json::json; +// use settings::SettingsStore; +// use std::{cell::RefCell, rc::Rc}; + +// #[gpui::test] +// async fn test_tab_disambiguation(cx: &mut TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); + +// // Adding an item with no ambiguity renders the tab without detail. +// let item1 = window.add_view(cx, |_| { +// let mut item = TestItem::new(); +// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]); +// item +// }); +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item1.clone()), cx); +// }); +// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None)); + +// // Adding an item that creates ambiguity increases the level of detail on +// // both tabs. +// let item2 = window.add_view(cx, |_| { +// let mut item = TestItem::new(); +// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); +// item +// }); +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item2.clone()), cx); +// }); +// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); +// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); + +// // Adding an item that creates ambiguity increases the level of detail only +// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so +// // we stop at the highest detail available. +// let item3 = window.add_view(cx, |_| { +// let mut item = TestItem::new(); +// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); +// item +// }); +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item3.clone()), cx); +// }); +// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); +// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); +// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); +// } + +// #[gpui::test] +// async fn test_tracking_active_path(cx: &mut TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/root1", +// json!({ +// "one.txt": "", +// "two.txt": "", +// }), +// ) +// .await; +// fs.insert_tree( +// "/root2", +// json!({ +// "three.txt": "", +// }), +// ) +// .await; + +// let project = Project::test(fs, ["root1".as_ref()], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); +// let worktree_id = project.read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }); + +// let item1 = window.add_view(cx, |cx| { +// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) +// }); +// let item2 = window.add_view(cx, |cx| { +// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) +// }); + +// // Add an item to an empty pane +// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx)); +// project.read_with(cx, |project, cx| { +// assert_eq!( +// project.active_entry(), +// project +// .entry_for_path(&(worktree_id, "one.txt").into(), cx) +// .map(|e| e.id) +// ); +// }); +// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1")); + +// // Add a second item to a non-empty pane +// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); +// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt — root1")); +// project.read_with(cx, |project, cx| { +// assert_eq!( +// project.active_entry(), +// project +// .entry_for_path(&(worktree_id, "two.txt").into(), cx) +// .map(|e| e.id) +// ); +// }); + +// // Close the active item +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&Default::default(), cx).unwrap() +// }) +// .await +// .unwrap(); +// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1")); +// project.read_with(cx, |project, cx| { +// assert_eq!( +// project.active_entry(), +// project +// .entry_for_path(&(worktree_id, "one.txt").into(), cx) +// .map(|e| e.id) +// ); +// }); + +// // Add a project folder +// project +// .update(cx, |project, cx| { +// project.find_or_create_local_worktree("/root2", true, cx) +// }) +// .await +// .unwrap(); +// assert_eq!( +// window.current_title(cx).as_deref(), +// Some("one.txt — root1, root2") +// ); + +// // Remove a project folder +// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); +// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root2")); +// } + +// #[gpui::test] +// async fn test_close_window(cx: &mut TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree("/root", json!({ "one": "" })).await; + +// let project = Project::test(fs, ["root".as_ref()], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); + +// // When there are no dirty items, there's nothing to do. +// let item1 = window.add_view(cx, |_| TestItem::new()); +// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); +// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); +// assert!(task.await.unwrap()); + +// // When there are dirty untitled items, prompt to save each one. If the user +// // cancels any prompt, then abort. +// let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true)); +// let item3 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) +// }); +// workspace.update(cx, |w, cx| { +// w.add_item(Box::new(item2.clone()), cx); +// w.add_item(Box::new(item3.clone()), cx); +// }); +// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); +// cx.foreground().run_until_parked(); +// window.simulate_prompt_answer(2, cx); // cancel save all +// cx.foreground().run_until_parked(); +// window.simulate_prompt_answer(2, cx); // cancel save all +// cx.foreground().run_until_parked(); +// assert!(!window.has_pending_prompt(cx)); +// assert!(!task.await.unwrap()); +// } + +// #[gpui::test] +// async fn test_close_pane_items(cx: &mut TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); + +// let item1 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) +// }); +// let item2 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_conflict(true) +// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) +// }); +// let item3 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_conflict(true) +// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) +// }); +// let item4 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_project_items(&[TestProjectItem::new_untitled(cx)]) +// }); +// let pane = workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item1.clone()), cx); +// workspace.add_item(Box::new(item2.clone()), cx); +// workspace.add_item(Box::new(item3.clone()), cx); +// workspace.add_item(Box::new(item4.clone()), cx); +// workspace.active_pane().clone() +// }); + +// let close_items = pane.update(cx, |pane, cx| { +// pane.activate_item(1, true, true, cx); +// assert_eq!(pane.active_item().unwrap().id(), item2.id()); +// let item1_id = item1.id(); +// let item3_id = item3.id(); +// let item4_id = item4.id(); +// pane.close_items(cx, SaveIntent::Close, move |id| { +// [item1_id, item3_id, item4_id].contains(&id) +// }) +// }); +// cx.foreground().run_until_parked(); + +// assert!(window.has_pending_prompt(cx)); +// // Ignore "Save all" prompt +// window.simulate_prompt_answer(2, cx); +// cx.foreground().run_until_parked(); +// // There's a prompt to save item 1. +// pane.read_with(cx, |pane, _| { +// assert_eq!(pane.items_len(), 4); +// assert_eq!(pane.active_item().unwrap().id(), item1.id()); +// }); +// // Confirm saving item 1. +// window.simulate_prompt_answer(0, cx); +// cx.foreground().run_until_parked(); + +// // Item 1 is saved. There's a prompt to save item 3. +// pane.read_with(cx, |pane, cx| { +// assert_eq!(item1.read(cx).save_count, 1); +// assert_eq!(item1.read(cx).save_as_count, 0); +// assert_eq!(item1.read(cx).reload_count, 0); +// assert_eq!(pane.items_len(), 3); +// assert_eq!(pane.active_item().unwrap().id(), item3.id()); +// }); +// assert!(window.has_pending_prompt(cx)); + +// // Cancel saving item 3. +// window.simulate_prompt_answer(1, cx); +// cx.foreground().run_until_parked(); + +// // Item 3 is reloaded. There's a prompt to save item 4. +// pane.read_with(cx, |pane, cx| { +// assert_eq!(item3.read(cx).save_count, 0); +// assert_eq!(item3.read(cx).save_as_count, 0); +// assert_eq!(item3.read(cx).reload_count, 1); +// assert_eq!(pane.items_len(), 2); +// assert_eq!(pane.active_item().unwrap().id(), item4.id()); +// }); +// assert!(window.has_pending_prompt(cx)); + +// // Confirm saving item 4. +// window.simulate_prompt_answer(0, cx); +// cx.foreground().run_until_parked(); + +// // There's a prompt for a path for item 4. +// cx.simulate_new_path_selection(|_| Some(Default::default())); +// close_items.await.unwrap(); + +// // The requested items are closed. +// pane.read_with(cx, |pane, cx| { +// assert_eq!(item4.read(cx).save_count, 0); +// assert_eq!(item4.read(cx).save_as_count, 1); +// assert_eq!(item4.read(cx).reload_count, 0); +// assert_eq!(pane.items_len(), 1); +// assert_eq!(pane.active_item().unwrap().id(), item2.id()); +// }); +// } + +// #[gpui::test] +// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); + +// // Create several workspace items with single project entries, and two +// // workspace items with multiple project entries. +// let single_entry_items = (0..=4) +// .map(|project_entry_id| { +// window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_project_items(&[TestProjectItem::new( +// project_entry_id, +// &format!("{project_entry_id}.txt"), +// cx, +// )]) +// }) +// }) +// .collect::>(); +// let item_2_3 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_singleton(false) +// .with_project_items(&[ +// single_entry_items[2].read(cx).project_items[0].clone(), +// single_entry_items[3].read(cx).project_items[0].clone(), +// ]) +// }); +// let item_3_4 = window.add_view(cx, |cx| { +// TestItem::new() +// .with_dirty(true) +// .with_singleton(false) +// .with_project_items(&[ +// single_entry_items[3].read(cx).project_items[0].clone(), +// single_entry_items[4].read(cx).project_items[0].clone(), +// ]) +// }); + +// // Create two panes that contain the following project entries: +// // left pane: +// // multi-entry items: (2, 3) +// // single-entry items: 0, 1, 2, 3, 4 +// // right pane: +// // single-entry items: 1 +// // multi-entry items: (3, 4) +// let left_pane = workspace.update(cx, |workspace, cx| { +// let left_pane = workspace.active_pane().clone(); +// workspace.add_item(Box::new(item_2_3.clone()), cx); +// for item in single_entry_items { +// workspace.add_item(Box::new(item), cx); +// } +// left_pane.update(cx, |pane, cx| { +// pane.activate_item(2, true, true, cx); +// }); + +// workspace +// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx) +// .unwrap(); + +// left_pane +// }); + +// //Need to cause an effect flush in order to respect new focus +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item_3_4.clone()), cx); +// cx.focus(&left_pane); +// }); + +// // When closing all of the items in the left pane, we should be prompted twice: +// // once for project entry 0, and once for project entry 2. After those two +// // prompts, the task should complete. + +// let close = left_pane.update(cx, |pane, cx| { +// pane.close_items(cx, SaveIntent::Close, move |_| true) +// }); +// cx.foreground().run_until_parked(); +// // Discard "Save all" prompt +// window.simulate_prompt_answer(2, cx); + +// cx.foreground().run_until_parked(); +// left_pane.read_with(cx, |pane, cx| { +// assert_eq!( +// pane.active_item().unwrap().project_entry_ids(cx).as_slice(), +// &[ProjectEntryId::from_proto(0)] +// ); +// }); +// window.simulate_prompt_answer(0, cx); + +// cx.foreground().run_until_parked(); +// left_pane.read_with(cx, |pane, cx| { +// assert_eq!( +// pane.active_item().unwrap().project_entry_ids(cx).as_slice(), +// &[ProjectEntryId::from_proto(2)] +// ); +// }); +// window.simulate_prompt_answer(0, cx); + +// cx.foreground().run_until_parked(); +// close.await.unwrap(); +// left_pane.read_with(cx, |pane, _| { +// assert_eq!(pane.items_len(), 0); +// }); +// } + +// #[gpui::test] +// async fn test_autosave(deterministic: Arc, cx: &mut gpui::TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// let item = window.add_view(cx, |cx| { +// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) +// }); +// let item_id = item.id(); +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item.clone()), cx); +// }); + +// // Autosave on window change. +// item.update(cx, |item, cx| { +// cx.update_global(|settings: &mut SettingsStore, cx| { +// settings.update_user_settings::(cx, |settings| { +// settings.autosave = Some(AutosaveSetting::OnWindowChange); +// }) +// }); +// item.is_dirty = true; +// }); + +// // Deactivating the window saves the file. +// window.simulate_deactivation(cx); +// deterministic.run_until_parked(); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1)); + +// // Autosave on focus change. +// item.update(cx, |item, cx| { +// cx.focus_self(); +// cx.update_global(|settings: &mut SettingsStore, cx| { +// settings.update_user_settings::(cx, |settings| { +// settings.autosave = Some(AutosaveSetting::OnFocusChange); +// }) +// }); +// item.is_dirty = true; +// }); + +// // Blurring the item saves the file. +// item.update(cx, |_, cx| cx.blur()); +// deterministic.run_until_parked(); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); + +// // Deactivating the window still saves the file. +// window.simulate_activation(cx); +// item.update(cx, |item, cx| { +// cx.focus_self(); +// item.is_dirty = true; +// }); +// window.simulate_deactivation(cx); + +// deterministic.run_until_parked(); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); + +// // Autosave after delay. +// item.update(cx, |item, cx| { +// cx.update_global(|settings: &mut SettingsStore, cx| { +// settings.update_user_settings::(cx, |settings| { +// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); +// }) +// }); +// item.is_dirty = true; +// cx.emit(TestItemEvent::Edit); +// }); + +// // Delay hasn't fully expired, so the file is still dirty and unsaved. +// deterministic.advance_clock(Duration::from_millis(250)); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); + +// // After delay expires, the file is saved. +// deterministic.advance_clock(Duration::from_millis(250)); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + +// // Autosave on focus change, ensuring closing the tab counts as such. +// item.update(cx, |item, cx| { +// cx.update_global(|settings: &mut SettingsStore, cx| { +// settings.update_user_settings::(cx, |settings| { +// settings.autosave = Some(AutosaveSetting::OnFocusChange); +// }) +// }); +// item.is_dirty = true; +// }); + +// pane.update(cx, |pane, cx| { +// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id) +// }) +// .await +// .unwrap(); +// assert!(!window.has_pending_prompt(cx)); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + +// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item.clone()), cx); +// }); +// item.update(cx, |item, cx| { +// item.project_items[0].update(cx, |item, _| { +// item.entry_id = None; +// }); +// item.is_dirty = true; +// cx.blur(); +// }); +// deterministic.run_until_parked(); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + +// // Ensure autosave is prevented for deleted files also when closing the buffer. +// let _close_items = pane.update(cx, |pane, cx| { +// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id) +// }); +// deterministic.run_until_parked(); +// assert!(window.has_pending_prompt(cx)); +// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); +// } + +// #[gpui::test] +// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) { +// init_test(cx); + +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); + +// let item = window.add_view(cx, |cx| { +// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) +// }); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); +// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); +// let toolbar_notify_count = Rc::new(RefCell::new(0)); + +// workspace.update(cx, |workspace, cx| { +// workspace.add_item(Box::new(item.clone()), cx); +// let toolbar_notification_count = toolbar_notify_count.clone(); +// cx.observe(&toolbar, move |_, _, _| { +// *toolbar_notification_count.borrow_mut() += 1 +// }) +// .detach(); +// }); + +// pane.read_with(cx, |pane, _| { +// assert!(!pane.can_navigate_backward()); +// assert!(!pane.can_navigate_forward()); +// }); + +// item.update(cx, |item, cx| { +// item.set_state("one".to_string(), cx); +// }); + +// // Toolbar must be notified to re-render the navigation buttons +// assert_eq!(*toolbar_notify_count.borrow(), 1); + +// pane.read_with(cx, |pane, _| { +// assert!(pane.can_navigate_backward()); +// assert!(!pane.can_navigate_forward()); +// }); + +// workspace +// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx)) +// .await +// .unwrap(); + +// assert_eq!(*toolbar_notify_count.borrow(), 3); +// pane.read_with(cx, |pane, _| { +// assert!(!pane.can_navigate_backward()); +// assert!(pane.can_navigate_forward()); +// }); +// } + +// #[gpui::test] +// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); + +// let panel = workspace.update(cx, |workspace, cx| { +// let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right)); +// workspace.add_panel(panel.clone(), cx); + +// workspace +// .right_dock() +// .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + +// panel +// }); + +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); +// pane.update(cx, |pane, cx| { +// let item = cx.add_view(|_| TestItem::new()); +// pane.add_item(Box::new(item), true, true, None, cx); +// }); + +// // Transfer focus from center to panel +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_panel_focus::(cx); +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(!panel.is_zoomed(cx)); +// assert!(panel.has_focus(cx)); +// }); + +// // Transfer focus from panel to center +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_panel_focus::(cx); +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(!panel.is_zoomed(cx)); +// assert!(!panel.has_focus(cx)); +// }); + +// // Close the dock +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_dock(DockPosition::Right, cx); +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(!workspace.right_dock().read(cx).is_open()); +// assert!(!panel.is_zoomed(cx)); +// assert!(!panel.has_focus(cx)); +// }); + +// // Open the dock +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_dock(DockPosition::Right, cx); +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(!panel.is_zoomed(cx)); +// assert!(panel.has_focus(cx)); +// }); + +// // Focus and zoom panel +// panel.update(cx, |panel, cx| { +// cx.focus_self(); +// panel.set_zoomed(true, cx) +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(panel.is_zoomed(cx)); +// assert!(panel.has_focus(cx)); +// }); + +// // Transfer focus to the center closes the dock +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_panel_focus::(cx); +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(!workspace.right_dock().read(cx).is_open()); +// assert!(panel.is_zoomed(cx)); +// assert!(!panel.has_focus(cx)); +// }); + +// // Transferring focus back to the panel keeps it zoomed +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_panel_focus::(cx); +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(panel.is_zoomed(cx)); +// assert!(panel.has_focus(cx)); +// }); + +// // Close the dock while it is zoomed +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_dock(DockPosition::Right, cx) +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(!workspace.right_dock().read(cx).is_open()); +// assert!(panel.is_zoomed(cx)); +// assert!(workspace.zoomed.is_none()); +// assert!(!panel.has_focus(cx)); +// }); + +// // Opening the dock, when it's zoomed, retains focus +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_dock(DockPosition::Right, cx) +// }); + +// workspace.read_with(cx, |workspace, cx| { +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(panel.is_zoomed(cx)); +// assert!(workspace.zoomed.is_some()); +// assert!(panel.has_focus(cx)); +// }); + +// // Unzoom and close the panel, zoom the active pane. +// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_dock(DockPosition::Right, cx) +// }); +// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + +// // Opening a dock unzooms the pane. +// workspace.update(cx, |workspace, cx| { +// workspace.toggle_dock(DockPosition::Right, cx) +// }); +// workspace.read_with(cx, |workspace, cx| { +// let pane = pane.read(cx); +// assert!(!pane.is_zoomed()); +// assert!(!pane.has_focus()); +// assert!(workspace.right_dock().read(cx).is_open()); +// assert!(workspace.zoomed.is_none()); +// }); +// } + +// #[gpui::test] +// async fn test_panels(cx: &mut gpui::TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); + +// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { +// // Add panel_1 on the left, panel_2 on the right. +// let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left)); +// workspace.add_panel(panel_1.clone(), cx); +// workspace +// .left_dock() +// .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); +// let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right)); +// workspace.add_panel(panel_2.clone(), cx); +// workspace +// .right_dock() +// .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + +// let left_dock = workspace.left_dock(); +// assert_eq!( +// left_dock.read(cx).visible_panel().unwrap().id(), +// panel_1.id() +// ); +// assert_eq!( +// left_dock.read(cx).active_panel_size(cx).unwrap(), +// panel_1.size(cx) +// ); + +// left_dock.update(cx, |left_dock, cx| { +// left_dock.resize_active_panel(Some(1337.), cx) +// }); +// assert_eq!( +// workspace +// .right_dock() +// .read(cx) +// .visible_panel() +// .unwrap() +// .id(), +// panel_2.id() +// ); + +// (panel_1, panel_2) +// }); + +// // Move panel_1 to the right +// panel_1.update(cx, |panel_1, cx| { +// panel_1.set_position(DockPosition::Right, cx) +// }); + +// workspace.update(cx, |workspace, cx| { +// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. +// // Since it was the only panel on the left, the left dock should now be closed. +// assert!(!workspace.left_dock().read(cx).is_open()); +// assert!(workspace.left_dock().read(cx).visible_panel().is_none()); +// let right_dock = workspace.right_dock(); +// assert_eq!( +// right_dock.read(cx).visible_panel().unwrap().id(), +// panel_1.id() +// ); +// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); + +// // Now we move panel_2 to the left +// panel_2.set_position(DockPosition::Left, cx); +// }); + +// workspace.update(cx, |workspace, cx| { +// // Since panel_2 was not visible on the right, we don't open the left dock. +// assert!(!workspace.left_dock().read(cx).is_open()); +// // And the right dock is unaffected in it's displaying of panel_1 +// assert!(workspace.right_dock().read(cx).is_open()); +// assert_eq!( +// workspace +// .right_dock() +// .read(cx) +// .visible_panel() +// .unwrap() +// .id(), +// panel_1.id() +// ); +// }); + +// // Move panel_1 back to the left +// panel_1.update(cx, |panel_1, cx| { +// panel_1.set_position(DockPosition::Left, cx) +// }); + +// workspace.update(cx, |workspace, cx| { +// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. +// let left_dock = workspace.left_dock(); +// assert!(left_dock.read(cx).is_open()); +// assert_eq!( +// left_dock.read(cx).visible_panel().unwrap().id(), +// panel_1.id() +// ); +// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); +// // And right the dock should be closed as it no longer has any panels. +// assert!(!workspace.right_dock().read(cx).is_open()); + +// // Now we move panel_1 to the bottom +// panel_1.set_position(DockPosition::Bottom, cx); +// }); + +// workspace.update(cx, |workspace, cx| { +// // Since panel_1 was visible on the left, we close the left dock. +// assert!(!workspace.left_dock().read(cx).is_open()); +// // The bottom dock is sized based on the panel's default size, +// // since the panel orientation changed from vertical to horizontal. +// let bottom_dock = workspace.bottom_dock(); +// assert_eq!( +// bottom_dock.read(cx).active_panel_size(cx).unwrap(), +// panel_1.size(cx), +// ); +// // Close bottom dock and move panel_1 back to the left. +// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); +// panel_1.set_position(DockPosition::Left, cx); +// }); + +// // Emit activated event on panel 1 +// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); + +// // Now the left dock is open and panel_1 is active and focused. +// workspace.read_with(cx, |workspace, cx| { +// let left_dock = workspace.left_dock(); +// assert!(left_dock.read(cx).is_open()); +// assert_eq!( +// left_dock.read(cx).visible_panel().unwrap().id(), +// panel_1.id() +// ); +// assert!(panel_1.is_focused(cx)); +// }); + +// // Emit closed event on panel 2, which is not active +// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + +// // Wo don't close the left dock, because panel_2 wasn't the active panel +// workspace.read_with(cx, |workspace, cx| { +// let left_dock = workspace.left_dock(); +// assert!(left_dock.read(cx).is_open()); +// assert_eq!( +// left_dock.read(cx).visible_panel().unwrap().id(), +// panel_1.id() +// ); +// }); + +// // Emitting a ZoomIn event shows the panel as zoomed. +// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); +// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); +// }); + +// // Move panel to another dock while it is zoomed +// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); +// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); +// }); + +// // If focus is transferred to another view that's not a panel or another pane, we still show +// // the panel as zoomed. +// let focus_receiver = window.add_view(cx, |_| EmptyView); +// focus_receiver.update(cx, |_, cx| cx.focus_self()); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); +// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); +// }); + +// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. +// workspace.update(cx, |_, cx| cx.focus_self()); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, None); +// assert_eq!(workspace.zoomed_position, None); +// }); + +// // If focus is transferred again to another view that's not a panel or a pane, we won't +// // show the panel as zoomed because it wasn't zoomed before. +// focus_receiver.update(cx, |_, cx| cx.focus_self()); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, None); +// assert_eq!(workspace.zoomed_position, None); +// }); + +// // When focus is transferred back to the panel, it is zoomed again. +// panel_1.update(cx, |_, cx| cx.focus_self()); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); +// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); +// }); + +// // Emitting a ZoomOut event unzooms the panel. +// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); +// workspace.read_with(cx, |workspace, _| { +// assert_eq!(workspace.zoomed, None); +// assert_eq!(workspace.zoomed_position, None); +// }); + +// // Emit closed event on panel 1, which is active +// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); + +// // Now the left dock is closed, because panel_1 was the active panel +// workspace.read_with(cx, |workspace, cx| { +// let right_dock = workspace.right_dock(); +// assert!(!right_dock.read(cx).is_open()); +// }); +// } + +// pub fn init_test(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// cx.update(|cx| { +// cx.set_global(SettingsStore::test(cx)); +// theme::init((), cx); +// language::init(cx); +// crate::init_settings(cx); +// Project::init_settings(cx); +// }); +// } +// } diff --git a/crates/workspace2/src/workspace_settings.rs b/crates/workspace2/src/workspace_settings.rs new file mode 100644 index 0000000000..6483167018 --- /dev/null +++ b/crates/workspace2/src/workspace_settings.rs @@ -0,0 +1,56 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize)] +pub struct WorkspaceSettings { + pub active_pane_magnification: f32, + pub confirm_quit: bool, + pub show_call_status_icon: bool, + pub autosave: AutosaveSetting, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct WorkspaceSettingsContent { + pub active_pane_magnification: Option, + pub confirm_quit: Option, + pub show_call_status_icon: Option, + pub autosave: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AutosaveSetting { + Off, + AfterDelay { milliseconds: u64 }, + OnFocusChange, + OnWindowChange, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct GitSettings { + pub git_gutter: Option, + pub gutter_debounce: Option, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitGutterSetting { + #[default] + TrackedFiles, + Hide, +} + +impl Setting for WorkspaceSettings { + const KEY: Option<&'static str> = None; + + type FileContent = WorkspaceSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index bc56e31457..08aedce714 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -69,7 +69,7 @@ theme2 = { path = "../theme2" } util = { path = "../util" } # semantic_index = { path = "../semantic_index" } # vim = { path = "../vim" } -# workspace = { path = "../workspace" } +workspace2 = { path = "../workspace2" } # welcome = { path = "../welcome" } # zed-actions = {path = "../zed-actions"} anyhow.workspace = true diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index d78908dfb5..9782c6dd05 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -4,7 +4,8 @@ mod open_listener; pub use assets::*; use client2::{Client, UserStore}; -use gpui2::{AsyncAppContext, Handle}; +use collections::HashMap; +use gpui2::{AsyncAppContext, Handle, Point}; pub use only_instance::*; pub use open_listener::*; @@ -13,8 +14,12 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, }; -use futures::{channel::mpsc, SinkExt, StreamExt}; -use std::{sync::Arc, thread}; +use futures::{ + channel::{mpsc, oneshot}, + FutureExt, SinkExt, StreamExt, +}; +use std::{path::Path, sync::Arc, thread, time::Duration}; +use util::{paths::PathLikeWithPosition, ResultExt}; pub fn connect_to_cli( server_name: &str, @@ -51,156 +56,157 @@ pub struct AppState { } pub async fn handle_cli_connection( - (mut requests, _responses): (mpsc::Receiver, IpcSender), - _app_state: Arc, - mut _cx: AsyncAppContext, + (mut requests, responses): (mpsc::Receiver, IpcSender), + app_state: Arc, + mut cx: AsyncAppContext, ) { if let Some(request) = requests.next().await { match request { - CliRequest::Open { paths: _, wait: _ } => { - // let mut caret_positions = HashMap::new(); + CliRequest::Open { paths, wait } => { + let mut caret_positions = HashMap::default(); - // let paths = if paths.is_empty() { - // todo!() - // workspace::last_opened_workspace_paths() - // .await - // .map(|location| location.paths().to_vec()) - // .unwrap_or_default() - // } else { - // paths - // .into_iter() - // .filter_map(|path_with_position_string| { - // let path_with_position = PathLikeWithPosition::parse_str( - // &path_with_position_string, - // |path_str| { - // Ok::<_, std::convert::Infallible>( - // Path::new(path_str).to_path_buf(), - // ) - // }, - // ) - // .expect("Infallible"); - // let path = path_with_position.path_like; - // if let Some(row) = path_with_position.row { - // if path.is_file() { - // let row = row.saturating_sub(1); - // let col = - // path_with_position.column.unwrap_or(0).saturating_sub(1); - // caret_positions.insert(path.clone(), Point::new(row, col)); - // } - // } - // Some(path) - // }) - // .collect() - // }; + let paths = if paths.is_empty() { + todo!() + // workspace::last_opened_workspace_paths() + // .await + // .map(|location| location.paths().to_vec()) + // .unwrap_or_default() + } else { + paths + .into_iter() + .filter_map(|path_with_position_string| { + let path_with_position = PathLikeWithPosition::parse_str( + &path_with_position_string, + |path_str| { + Ok::<_, std::convert::Infallible>( + Path::new(path_str).to_path_buf(), + ) + }, + ) + .expect("Infallible"); + let path = path_with_position.path_like; + if let Some(row) = path_with_position.row { + if path.is_file() { + let row = row.saturating_sub(1); + let col = + path_with_position.column.unwrap_or(0).saturating_sub(1); + caret_positions.insert(path.clone(), Point::new(row, col)); + } + } + Some(path) + }) + .collect() + }; - // let mut errored = false; - // todo!("workspace") - // match cx - // .update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - // .await - // { - // Ok((workspace, items)) => { - // let mut item_release_futures = Vec::new(); + let mut errored = false; - // for (item, path) in items.into_iter().zip(&paths) { - // match item { - // Some(Ok(item)) => { - // if let Some(point) = caret_positions.remove(path) { - // if let Some(active_editor) = item.downcast::() { - // active_editor - // .downgrade() - // .update(&mut cx, |editor, cx| { - // let snapshot = - // editor.snapshot(cx).display_snapshot; - // let point = snapshot - // .buffer_snapshot - // .clip_point(point, Bias::Left); - // editor.change_selections( - // Some(Autoscroll::center()), - // cx, - // |s| s.select_ranges([point..point]), - // ); - // }) - // .log_err(); - // } - // } + match cx + .update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) + .await + { + Ok((workspace, items)) => { + let mut item_release_futures = Vec::new(); - // let released = oneshot::channel(); - // cx.update(|cx| { - // item.on_release( - // cx, - // Box::new(move |_| { - // let _ = released.0.send(()); - // }), - // ) - // .detach(); - // }); - // item_release_futures.push(released.1); - // } - // Some(Err(err)) => { - // responses - // .send(CliResponse::Stderr { - // message: format!("error opening {:?}: {}", path, err), - // }) - // .log_err(); - // errored = true; - // } - // None => {} - // } - // } + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(item)) => { + if let Some(point) = caret_positions.remove(path) { + todo!() + // if let Some(active_editor) = item.downcast::() { + // active_editor + // .downgrade() + // .update(&mut cx, |editor, cx| { + // let snapshot = + // editor.snapshot(cx).display_snapshot; + // let point = snapshot + // .buffer_snapshot + // .clip_point(point, Bias::Left); + // editor.change_selections( + // Some(Autoscroll::center()), + // cx, + // |s| s.select_ranges([point..point]), + // ); + // }) + // .log_err(); + // } + } - // if wait { - // let background = cx.background(); - // let wait = async move { - // if paths.is_empty() { - // let (done_tx, done_rx) = oneshot::channel(); - // if let Some(workspace) = workspace.upgrade(&cx) { - // let _subscription = cx.update(|cx| { - // cx.observe_release(&workspace, move |_, _| { - // let _ = done_tx.send(()); - // }) - // }); - // drop(workspace); - // let _ = done_rx.await; - // } - // } else { - // let _ = - // futures::future::try_join_all(item_release_futures).await; - // }; - // } - // .fuse(); - // futures::pin_mut!(wait); + let released = oneshot::channel(); + cx.update(|cx| { + item.on_release( + cx, + Box::new(move |_| { + let _ = released.0.send(()); + }), + ) + .detach(); + }); + item_release_futures.push(released.1); + } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", path, err), + }) + .log_err(); + errored = true; + } + None => {} + } + } - // loop { - // // Repeatedly check if CLI is still open to avoid wasting resources - // // waiting for files or workspaces to close. - // let mut timer = background.timer(Duration::from_secs(1)).fuse(); - // futures::select_biased! { - // _ = wait => break, - // _ = timer => { - // if responses.send(CliResponse::Ping).is_err() { - // break; - // } - // } - // } - // } - // } - // } - // Err(error) => { - // errored = true; - // responses - // .send(CliResponse::Stderr { - // message: format!("error opening {:?}: {}", paths, error), - // }) - // .log_err(); - // } - // } + if wait { + let executor = cx.executor(); + let wait = async move { + if paths.is_empty() { + let (done_tx, done_rx) = oneshot::channel(); + if let Some(workspace) = workspace.upgrade(&cx) { + let _subscription = cx.update(|cx| { + cx.observe_release(&workspace, move |_, _| { + let _ = done_tx.send(()); + }) + }); + drop(workspace); + let _ = done_rx.await; + } + } else { + let _ = + futures::future::try_join_all(item_release_futures).await; + }; + } + .fuse(); + futures::pin_mut!(wait); - // responses - // .send(CliResponse::Exit { - // status: i32::from(errored), - // }) - // .log_err(); + loop { + // Repeatedly check if CLI is still open to avoid wasting resources + // waiting for files or workspaces to close. + let mut timer = executor.timer(Duration::from_secs(1)).fuse(); + futures::select_biased! { + _ = wait => break, + _ = timer => { + if responses.send(CliResponse::Ping).is_err() { + break; + } + } + } + } + } + } + Err(error) => { + errored = true; + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", paths, error), + }) + .log_err(); + } + } + + responses + .send(CliResponse::Exit { + status: i32::from(errored), + }) + .log_err(); } } } From d9274416b4759e1f7369e63a286732069cec8ca1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Oct 2023 14:36:55 +0200 Subject: [PATCH 02/66] Make `activate_workspace_for_project` compile Co-authored-by: Mikayla Maki Co-Authored-By: Kirill Bulatov --- crates/gpui2/src/app.rs | 65 +- crates/gpui2/src/app/async_context.rs | 56 +- crates/gpui2/src/app/test_context.rs | 4 +- crates/gpui2/src/view.rs | 32 +- crates/gpui2/src/window.rs | 62 +- crates/workspace2/src/item.rs | 2158 +++++++++++++------------ crates/workspace2/src/pane.rs | 5 - crates/workspace2/src/workspace2.rs | 277 ++-- 8 files changed, 1331 insertions(+), 1328 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 260ec0b6b3..72b8be2f74 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 42c9e96dd0..308e519089 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 9133c31f52..8b287d9b88 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 ed633e8966..66f1a14869 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 55cf04c51d..cdd83265b2 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 39a1f0d51a..e5d7787782 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; +// 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(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 e885408221..b1fbae1987 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 607bc5b61c..36dd26eb3c 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 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, -// } +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, -// } +#[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(); +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 -// } + 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) -// }); +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; -// 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) -// } -// }) -// } +#[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, From 9ea79259d5f8b6ad53c1e7a99b285a072e2a7b2c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Oct 2023 15:00:44 +0200 Subject: [PATCH 03/66] Add spawn facilities to `AsyncWindowContext` --- crates/gpui2/src/app/async_context.rs | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 308e519089..ab42a56cbb 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -183,6 +183,41 @@ impl AsyncWindowContext { self.app .update_window(self.window, |cx| cx.update_global(update)) } + + pub fn spawn( + &self, + f: impl FnOnce(AsyncWindowContext) -> Fut + Send + 'static, + ) -> Task + where + Fut: Future + Send + 'static, + R: Send + 'static, + { + let this = self.clone(); + self.executor.spawn(async move { f(this).await }) + } + + pub fn spawn_on_main( + &self, + f: impl FnOnce(AsyncWindowContext) -> Fut + Send + 'static, + ) -> Task + where + Fut: Future + 'static, + R: Send + 'static, + { + let this = self.clone(); + self.executor.spawn_on_main(|| f(this)) + } + + pub fn run_on_main( + &self, + f: impl FnOnce(&mut MainThread) -> R + Send + 'static, + ) -> Task> + where + R: Send + 'static, + { + self.update(|cx| cx.run_on_main(f)) + .unwrap_or_else(|error| Task::ready(Err(error))) + } } impl Context for AsyncWindowContext { From e2ee9a28bfb3907447aecf85b8862dbee737e331 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 14:50:50 +0100 Subject: [PATCH 04/66] Implement `activate_workspace_for_project` --- crates/gpui2/src/app.rs | 32 ++++++++++++++- crates/gpui2/src/app/async_context.rs | 43 +++++++++++++++++++- crates/gpui2/src/view.rs | 34 +++++++++++----- crates/gpui2/src/window.rs | 57 ++++++++++++++++----------- crates/workspace2/src/workspace2.rs | 29 ++++++++------ 5 files changed, 146 insertions(+), 49 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index d39083c1aa..a4d574abcf 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,8 +17,8 @@ use crate::{ 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, + TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, + WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -303,6 +303,20 @@ impl AppContext { }) } + pub fn update_window_root( + &mut self, + handle: &WindowHandle, + update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R, + ) -> Result + where + V: 'static, + { + self.update_window(handle.any_handle, |cx| { + let root_view = cx.window.root_view.as_ref().unwrap().downcast().unwrap(); + root_view.update(cx, update) + }) + } + pub(crate) fn push_effect(&mut self, effect: Effect) { match &effect { Effect::Notify { emitter } => { @@ -841,6 +855,20 @@ impl MainThread { }) } + pub fn update_window_root( + &mut self, + handle: &WindowHandle, + update: impl FnOnce(&mut V, &mut MainThread>) -> R, + ) -> Result + where + V: 'static, + { + self.update_window(handle.any_handle, |cx| { + let root_view = cx.window.root_view.as_ref().unwrap().downcast().unwrap(); + root_view.update(cx, update) + }) + } + /// Opens a new window with the given option and the root view returned by the given function. /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific /// functionality. diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 6615fd535e..5998917e52 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,6 +1,6 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Executor, Handle, MainThread, ModelContext, Result, Task, - WindowContext, + AnyWindowHandle, AppContext, Component, Context, Executor, Handle, MainThread, ModelContext, + Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle, }; use anyhow::Context as _; use derive_more::{Deref, DerefMut}; @@ -78,6 +78,19 @@ impl AsyncAppContext { app_context.update_window(handle, update) } + pub fn update_window_root( + &mut self, + handle: &WindowHandle, + update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R, + ) -> Result + where + V: 'static, + { + let app = self.app.upgrade().context("app was released")?; + let mut app_context = app.lock(); + app_context.update_window_root(handle, update) + } + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task where Fut: Future + Send + 'static, @@ -245,6 +258,32 @@ impl Context for AsyncWindowContext { } } +impl VisualContext for AsyncWindowContext { + type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>; + + fn build_view( + &mut self, + build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + ) -> Self::Result> + where + E: Component, + V: 'static + Send, + { + self.app + .update_window(self.window, |cx| cx.build_view(build_entity, render)) + } + + fn update_view( + &mut self, + view: &View, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R, + ) -> Self::Result { + self.app + .update_window(self.window, |cx| cx.update_view(view, update)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index cf7441249f..e3e89b2add 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,11 +1,12 @@ use crate::{ AnyBox, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId, - EntityId, Handle, LayoutId, Pixels, Size, ViewContext, VisualContext, WeakHandle, + EntityId, Flatten, Handle, LayoutId, Pixels, Size, ViewContext, VisualContext, WeakHandle, WindowContext, }; use anyhow::{Context, Result}; use parking_lot::Mutex; use std::{ + any::Any, marker::PhantomData, sync::{Arc, Weak}, }; @@ -128,13 +129,17 @@ impl WeakView { Some(View { state, render }) } - pub fn update( + pub fn update( &self, - cx: &mut WindowContext, - f: impl FnOnce(&mut V, &mut ViewContext) -> R, - ) -> Result { + cx: &mut C, + f: impl FnOnce(&mut V, &mut C::ViewContext<'_, '_, V>) -> R, + ) -> Result + where + C: VisualContext, + Result>: Flatten, + { let view = self.upgrade().context("error upgrading view")?; - Ok(view.update(cx, f)) + Ok(view.update(cx, f)).flatten() } } @@ -201,15 +206,16 @@ trait ViewObject: Send + Sync { fn initialize(&self, cx: &mut WindowContext) -> AnyBox; fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId; fn paint(&self, bounds: Bounds, element: &mut AnyBox, cx: &mut WindowContext); + fn as_any(&self) -> &dyn Any; } impl ViewObject for View { fn entity_id(&self) -> EntityId { - self.state.entity_id() + self.state.entity_id } fn initialize(&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.update(cx, |state, cx| { let mut any_element = Box::new((self.render.lock())(state, cx)); any_element.initialize(state, cx); @@ -219,7 +225,7 @@ impl ViewObject for View { } fn layout(&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.update(cx, |state, cx| { let element = element.downcast_mut::>().unwrap(); element.layout(state, cx) @@ -228,19 +234,27 @@ impl ViewObject for View { } fn paint(&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.update(cx, |state, cx| { let element = element.downcast_mut::>().unwrap(); element.paint(state, cx); }); }); } + + fn as_any(&self) -> &dyn Any { + self + } } #[derive(Clone)] pub struct AnyView(Arc); impl AnyView { + pub fn downcast(&self) -> Option> { + self.0.as_any().downcast_ref().cloned() + } + pub(crate) fn draw(&self, available_space: Size, cx: &mut WindowContext) { let mut rendered_element = self.0.initialize(cx); let layout_id = self.0.layout(&mut rendered_element, cx); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a197b0b615..e89c713d93 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1237,16 +1237,6 @@ 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, T> = ModelContext<'a, T>; type Result = T; @@ -1864,6 +1854,16 @@ where } } +impl<'a, 'w, V: 'static> 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<'a, 'w, V> Context for ViewContext<'a, 'w, V> { type EntityContext<'b, U> = ModelContext<'b, U>; type Result = U; @@ -1934,38 +1934,40 @@ impl WindowId { } } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Deref, DerefMut)] pub struct WindowHandle { - id: WindowId, + #[deref] + #[deref_mut] + pub(crate) any_handle: AnyWindowHandle, state_type: PhantomData, } -impl Copy for WindowHandle {} +impl Copy for WindowHandle {} -impl Clone for WindowHandle { +impl Clone for WindowHandle { fn clone(&self) -> Self { WindowHandle { - id: self.id, + any_handle: self.any_handle, state_type: PhantomData, } } } -impl WindowHandle { +impl WindowHandle { pub fn new(id: WindowId) -> Self { WindowHandle { - id, + any_handle: AnyWindowHandle { + id, + state_type: TypeId::of::(), + }, state_type: PhantomData, } } } -impl Into for WindowHandle { +impl Into for WindowHandle { fn into(self) -> AnyWindowHandle { - AnyWindowHandle { - id: self.id, - state_type: TypeId::of::(), - } + self.any_handle } } @@ -1979,6 +1981,17 @@ impl AnyWindowHandle { pub fn window_id(&self) -> WindowId { self.id } + + pub fn downcast(&self) -> Option> { + if TypeId::of::() == self.state_type { + Some(WindowHandle { + any_handle: *self, + state_type: PhantomData, + }) + } else { + None + } + } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 36dd26eb3c..06f6d65e2e 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -4103,25 +4103,28 @@ pub struct Workspace { pub async fn activate_workspace_for_project( cx: &mut AsyncAppContext, predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static, -) -> Option> { +) -> 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()); - } + let Some(workspace) = window.downcast::() else { + continue; + }; + + let predicate = cx + .update_window_root(&workspace, |workspace, cx| { + let project = workspace.project.read(cx); + if predicate(project, cx) { + cx.activate_window(); + true + } else { + false } - None }) .log_err() - .flatten(); + .unwrap_or(false); - if let Some(handle) = handle { - return Some(handle.downgrade()); + if predicate { + return Some(workspace); } } From d4d9fcc88c30d85d1181d99177cd1a6591da0663 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 15:39:58 +0100 Subject: [PATCH 05/66] WIP --- crates/workspace2/src/item.rs | 558 +-- crates/workspace2/src/pane.rs | 4920 +++++++++++---------- crates/workspace2/src/pane_group.rs | 82 +- crates/workspace2/src/searchable.rs | 56 +- crates/workspace2/src/workspace2.rs | 6380 ++++++++++++++------------- 5 files changed, 6015 insertions(+), 5981 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index e5d7787782..06592bffac 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -3,7 +3,12 @@ // ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, // }; // use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; -// use anyhow::Result; +use anyhow::Result; +use client2::{ + proto::{self, PeerId, ViewId}, + Client, +}; +use theme2::Theme; // use client2::{ // proto::{self, PeerId}, // Client, @@ -78,218 +83,227 @@ // } // } -// #[derive(Eq, PartialEq, Hash, Debug)] -// pub enum ItemEvent { -// CloseItem, -// UpdateTab, -// UpdateBreadcrumbs, -// Edit, -// } - -// // TODO: Combine this with existing HighlightedText struct? -// pub struct BreadcrumbText { -// pub text: String, -// pub highlights: Option, HighlightStyle)>>, -// } - -// pub trait Item: View { -// fn deactivated(&mut self, _: &mut ViewContext) {} -// fn workspace_deactivated(&mut self, _: &mut ViewContext) {} -// fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { -// false -// } -// fn tab_tooltip_text(&self, _: &AppContext) -> Option> { -// None -// } -// fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { -// None -// } -// fn tab_content( -// &self, -// detail: Option, -// style: &theme2::Tab, -// cx: &AppContext, -// ) -> AnyElement; -// fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) { -// } // (model id, Item) -// fn is_singleton(&self, _cx: &AppContext) -> bool { -// false -// } -// fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} -// fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option -// where -// Self: Sized, -// { -// None -// } -// fn is_dirty(&self, _: &AppContext) -> bool { -// false -// } -// fn has_conflict(&self, _: &AppContext) -> bool { -// false -// } -// fn can_save(&self, _cx: &AppContext) -> bool { -// false -// } -// fn save( -// &mut self, -// _project: ModelHandle, -// _cx: &mut ViewContext, -// ) -> Task> { -// unimplemented!("save() must be implemented if can_save() returns true") -// } -// fn save_as( -// &mut self, -// _project: ModelHandle, -// _abs_path: PathBuf, -// _cx: &mut ViewContext, -// ) -> Task> { -// unimplemented!("save_as() must be implemented if can_save() returns true") -// } -// fn reload( -// &mut self, -// _project: ModelHandle, -// _cx: &mut ViewContext, -// ) -> Task> { -// unimplemented!("reload() must be implemented if can_save() returns true") -// } -// fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { -// SmallVec::new() -// } -// fn should_close_item_on_event(_: &Self::Event) -> bool { -// false -// } -// fn should_update_tab_on_event(_: &Self::Event) -> bool { -// false -// } - -// fn act_as_type<'a>( -// &'a self, -// type_id: TypeId, -// self_handle: &'a ViewHandle, -// _: &'a AppContext, -// ) -> Option<&AnyViewHandle> { -// if TypeId::of::() == type_id { -// Some(self_handle) -// } else { -// None -// } -// } - -// fn as_searchable(&self, _: &ViewHandle) -> Option> { -// None -// } - -// fn breadcrumb_location(&self) -> ToolbarItemLocation { -// ToolbarItemLocation::Hidden -// } - -// fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { -// None -// } - -// fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} - -// fn serialized_item_kind() -> Option<&'static str> { -// None -// } - -// fn deserialize( -// _project: ModelHandle, -// _workspace: WeakViewHandle, -// _workspace_id: WorkspaceId, -// _item_id: ItemId, -// _cx: &mut ViewContext, -// ) -> Task>> { -// unimplemented!( -// "deserialize() must be implemented if serialized_item_kind() returns Some(_)" -// ) -// } -// fn show_toolbar(&self) -> bool { -// true -// } -// fn pixel_position_of_cursor(&self, _: &AppContext) -> Option { -// None -// } -// } - -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(Eq, PartialEq, Hash, Debug)] +pub enum ItemEvent { + CloseItem, + UpdateTab, + UpdateBreadcrumbs, + Edit, } -// pub trait WeakItemHandle { -// fn id(&self) -> usize; -// fn window(&self) -> AnyWindowHandle; -// fn upgrade(&self, cx: &AppContext) -> Option>; -// } +// TODO: Combine this with existing HighlightedText struct? +pub struct BreadcrumbText { + pub text: String, + pub highlights: Option, HighlightStyle)>>, +} +pub trait Item: EventEmitter { + // fn deactivated(&mut self, _: &mut ViewContext) {} + // fn workspace_deactivated(&mut self, _: &mut ViewContext) {} + // fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { + // false + // } + // fn tab_tooltip_text(&self, _: &AppContext) -> Option> { + // None + // } + // fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { + // None + // } + // fn tab_content( + // &self, + // detail: Option, + // style: &theme2::Tab, + // cx: &AppContext, + // ) -> AnyElement; + // fn 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; +use std::{ + any::{Any, TypeId}, + borrow::Cow, + ops::Range, + path::PathBuf, + sync::Arc, +}; + +use gpui2::{ + AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, + Point, Task, View, ViewContext, WindowContext, +}; +use project2::{Project, ProjectEntryId, ProjectPath}; +use smallvec::SmallVec; + +use crate::{ + pane::Pane, searchable::SearchableItemHandle, ToolbarItemLocation, Workspace, WorkspaceId, +}; + +pub trait ItemHandle: 'static + fmt::Debug + Send { + fn subscribe_to_item_events( + &self, + cx: &mut WindowContext, + handler: Box, + ) -> gpui2::Subscription; + fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option>; + fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option>; + fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + fn dragged_tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; + fn project_path(&self, cx: &AppContext) -> Option; + fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)); + fn is_singleton(&self, cx: &AppContext) -> bool; + fn boxed_clone(&self) -> Box; + fn clone_on_split( + &self, + workspace_id: WorkspaceId, + cx: &mut WindowContext, + ) -> Option>; + fn added_to_pane( + &self, + workspace: &mut Workspace, + pane: View, + cx: &mut ViewContext, + ); + fn deactivated(&self, cx: &mut WindowContext); + fn workspace_deactivated(&self, cx: &mut WindowContext); + fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool; + fn id(&self) -> usize; + fn window(&self) -> AnyWindowHandle; + // fn as_any(&self) -> &AnyView; todo!() + fn is_dirty(&self, cx: &AppContext) -> bool; + fn has_conflict(&self, cx: &AppContext) -> bool; + fn can_save(&self, cx: &AppContext) -> bool; + fn save(&self, project: Handle, cx: &mut WindowContext) -> Task>; + fn save_as( + &self, + project: Handle, + abs_path: PathBuf, + cx: &mut WindowContext, + ) -> Task>; + fn reload(&self, project: Handle, cx: &mut WindowContext) -> Task>; + // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!() + fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; + fn on_release( + &self, + cx: &mut AppContext, + callback: Box, + ) -> gpui2::Subscription; + fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; + fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; + fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; + fn serialized_item_kind(&self) -> Option<&'static str>; + fn show_toolbar(&self, cx: &AppContext) -> bool; + fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option>; +} + +pub trait WeakItemHandle { + fn id(&self) -> usize; + fn window(&self) -> AnyWindowHandle; + fn upgrade(&self, cx: &AppContext) -> Option>; +} + +// todo!() // impl dyn ItemHandle { // pub fn downcast(&self) -> Option> { // self.as_any().clone().downcast() @@ -653,11 +667,11 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { // } // } -// impl Clone for Box { -// fn clone(&self) -> Box { -// self.boxed_clone() -// } -// } +impl Clone for Box { + fn clone(&self) -> Box { + self.boxed_clone() + } +} // impl WeakItemHandle for WeakViewHandle { // fn id(&self) -> usize { @@ -673,63 +687,65 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { // } // } -// pub trait ProjectItem: Item { -// type Item: project2::Item + gpui2::Entity; +pub trait ProjectItem: Item { + type Item: project2::Item; -// fn for_project_item( -// project: ModelHandle, -// item: ModelHandle, -// cx: &mut ViewContext, -// ) -> Self; -// } + fn for_project_item( + project: Handle, + item: Handle, + cx: &mut ViewContext, + ) -> Self + where + Self: Sized; +} -// 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; +pub trait FollowableItem: Item { + fn remote_id(&self) -> Option; + fn to_state_proto(&self, cx: &AppContext) -> Option; + fn from_state_proto( + pane: View, + project: View, + id: ViewId, + state: &mut Option, + cx: &mut AppContext, + ) -> Option>>>; + fn add_event_to_update_proto( + &self, + event: &Self::Event, + update: &mut Option, + cx: &AppContext, + ) -> bool; + fn apply_update_proto( + &mut self, + project: &Handle, + message: proto::update_view::Variant, + cx: &mut ViewContext, + ) -> Task>; + fn is_project_item(&self, cx: &AppContext) -> bool; -// fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); -// fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; -// } + fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext); + fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool; +} -// pub trait FollowableItemHandle: ItemHandle { -// fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; -// fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); -// fn to_state_proto(&self, cx: &AppContext) -> Option; -// fn add_event_to_update_proto( -// &self, -// event: &dyn Any, -// update: &mut Option, -// cx: &AppContext, -// ) -> bool; -// fn apply_update_proto( -// &self, -// project: &ModelHandle, -// message: proto::update_view::Variant, -// cx: &mut WindowContext, -// ) -> Task>; -// fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; -// fn is_project_item(&self, cx: &AppContext) -> bool; -// } +pub trait FollowableItemHandle: ItemHandle { + fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; + fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); + fn to_state_proto(&self, cx: &AppContext) -> Option; + fn add_event_to_update_proto( + &self, + event: &dyn Any, + update: &mut Option, + cx: &AppContext, + ) -> bool; + fn apply_update_proto( + &self, + project: &Handle, + message: proto::update_view::Variant, + cx: &mut WindowContext, + ) -> Task>; + fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool; + fn is_project_item(&self, cx: &AppContext) -> bool; +} // impl FollowableItemHandle for ViewHandle { // fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { @@ -981,9 +997,9 @@ pub trait ItemHandle: 'static + fmt::Debug + Send + Sync { // .for_each(|item| f(item.id(), item.read(cx))) // } -// fn is_singleton(&self, _: &AppContext) -> bool { -// self.is_singleton -// } +// fn is_singleton(&self, _: &AppContext) -> bool { +// self.is_singleton +// } // fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { // self.nav_history = Some(history); diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index b1fbae1987..44420714c7 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1,150 +1,150 @@ -mod dragged_item_receiver; +// 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 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; +// 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(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, Deserialize, PartialEq)] +// pub struct ActivateItem(pub usize); -#[derive(Clone, PartialEq)] -pub struct CloseItemById { - pub item_id: usize, - pub pane: WeakViewHandle, -} +// #[derive(Clone, PartialEq)] +// pub struct CloseItemById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } -#[derive(Clone, PartialEq)] -pub struct CloseItemsToTheLeftById { - pub item_id: usize, - pub pane: WeakViewHandle, -} +// #[derive(Clone, PartialEq)] +// pub struct CloseItemsToTheLeftById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } -#[derive(Clone, PartialEq)] -pub struct CloseItemsToTheRightById { - pub item_id: usize, - pub pane: WeakViewHandle, -} +// #[derive(Clone, PartialEq)] +// pub struct CloseItemsToTheRightById { +// pub item_id: usize, +// pub pane: WeakViewHandle, +// } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct CloseActiveItem { - pub save_intent: Option, -} +// #[derive(Clone, PartialEq, Debug, Deserialize, Default)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseActiveItem { +// pub save_intent: Option, +// } -#[derive(Clone, PartialEq, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CloseAllItems { - pub save_intent: Option, -} +// #[derive(Clone, PartialEq, Debug, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct CloseAllItems { +// pub save_intent: Option, +// } -actions!( - pane, - [ - ActivatePrevItem, - ActivateNextItem, - ActivateLastItem, - CloseInactiveItems, - CloseCleanItems, - CloseItemsToTheLeft, - CloseItemsToTheRight, - GoBack, - GoForward, - ReopenClosedItem, - SplitLeft, - SplitUp, - SplitRight, - SplitDown, - ] -); +// actions!( +// pane, +// [ +// ActivatePrevItem, +// ActivateNextItem, +// ActivateLastItem, +// CloseInactiveItems, +// CloseCleanItems, +// CloseItemsToTheLeft, +// CloseItemsToTheRight, +// GoBack, +// GoForward, +// ReopenClosedItem, +// SplitLeft, +// SplitUp, +// SplitRight, +// SplitDown, +// ] +// ); -impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); +// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); -const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; +// 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)); -} +// pub fn init(cx: &mut AppContext) { +// cx.add_action(Pane::toggle_zoom); +// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { +// pane.activate_item(action.0, true, true, cx); +// }); +// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { +// pane.activate_item(pane.items.len() - 1, true, true, cx); +// }); +// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { +// pane.activate_prev_item(true, cx); +// }); +// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| { +// pane.activate_next_item(true, cx); +// }); +// cx.add_async_action(Pane::close_active_item); +// cx.add_async_action(Pane::close_inactive_items); +// cx.add_async_action(Pane::close_clean_items); +// cx.add_async_action(Pane::close_items_to_the_left); +// cx.add_async_action(Pane::close_items_to_the_right); +// cx.add_async_action(Pane::close_all_items); +// cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)); +// cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)); +// cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)); +// cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)); +// } #[derive(Debug)] pub enum Event { @@ -159,23 +159,36 @@ pub enum Event { ZoomOut, } +use crate::item::{ItemHandle, WeakItemHandle}; +use collections::{HashMap, VecDeque}; +use gpui2::{Handle, ViewContext, WeakView}; +use project2::{Project, ProjectEntryId, ProjectPath}; +use std::{ + any::Any, + cell::RefCell, + cmp, mem, + path::PathBuf, + rc::Rc, + sync::{atomic::AtomicUsize, Arc}, +}; + pub struct Pane { items: Vec>, - activation_history: Vec, - zoomed: bool, - active_item_index: usize, - last_focused_view_by_item: HashMap, - autoscroll: bool, + // activation_history: Vec, + // zoomed: bool, + // active_item_index: usize, + // last_focused_view_by_item: HashMap, + // autoscroll: bool, nav_history: NavHistory, - toolbar: ViewHandle, - tab_bar_context_menu: TabBarContextMenu, - tab_context_menu: ViewHandle, - workspace: WeakViewHandle, - project: ModelHandle, - has_focus: bool, - can_drop: Rc, &WindowContext) -> bool>, - can_split: bool, - render_tab_bar_buttons: Rc) -> AnyElement>, + // toolbar: ViewHandle, + // tab_bar_context_menu: TabBarContextMenu, + // tab_context_menu: ViewHandle, + // workspace: WeakViewHandle, + project: Handle, + // has_focus: bool, + // can_drop: Rc, &WindowContext) -> bool>, + // can_split: bool, + // render_tab_bar_buttons: Rc) -> AnyElement>, } pub struct ItemNavHistory { @@ -192,7 +205,7 @@ struct NavHistoryState { forward_stack: VecDeque, closed_stack: VecDeque, paths_by_item: HashMap)>, - pane: WeakViewHandle, + pane: WeakView, next_timestamp: Arc, } @@ -218,261 +231,261 @@ pub struct NavigationEntry { pub timestamp: usize, } -pub struct DraggedItem { - pub handle: Box, - pub pane: WeakViewHandle, -} +// pub struct DraggedItem { +// pub handle: Box, +// pub pane: WeakViewHandle, +// } -pub enum ReorderBehavior { - None, - MoveAfterActive, - MoveToIndex(usize), -} +// pub enum ReorderBehavior { +// None, +// MoveAfterActive, +// MoveToIndex(usize), +// } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TabBarContextMenuKind { - New, - Split, -} +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// enum TabBarContextMenuKind { +// New, +// Split, +// } -struct TabBarContextMenu { - kind: TabBarContextMenuKind, - handle: ViewHandle, -} +// struct TabBarContextMenu { +// kind: TabBarContextMenuKind, +// handle: ViewHandle, +// } -impl TabBarContextMenu { - fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { - if self.kind == kind { - return Some(self.handle.clone()); - } - None - } -} +// impl TabBarContextMenu { +// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { +// if self.kind == kind { +// return Some(self.handle.clone()); +// } +// None +// } +// } -#[allow(clippy::too_many_arguments)] -fn nav_button)>( - svg_path: &'static str, - style: theme2::Interactive, - nav_button_height: f32, - tooltip_style: TooltipStyle, - enabled: bool, - on_click: F, - tooltip_action: A, - action_name: &str, - cx: &mut ViewContext, -) -> AnyElement { - MouseEventHandler::new::(0, cx, |state, _| { - let style = if enabled { - style.style_for(state) - } else { - style.disabled_style() - }; - Svg::new(svg_path) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .contained() - .with_style(style.container) - .constrained() - .with_width(style.button_width) - .with_height(nav_button_height) - .aligned() - .top() - }) - .with_cursor_style(if enabled { - CursorStyle::PointingHand - } else { - CursorStyle::default() - }) - .on_click(MouseButton::Left, move |_, toolbar, cx| { - on_click(toolbar, cx) - }) - .with_tooltip::( - 0, - action_name.to_string(), - Some(Box::new(tooltip_action)), - tooltip_style, - cx, - ) - .contained() - .into_any_named("nav button") -} +// #[allow(clippy::too_many_arguments)] +// fn nav_button)>( +// svg_path: &'static str, +// style: theme2::Interactive, +// nav_button_height: f32, +// tooltip_style: TooltipStyle, +// enabled: bool, +// on_click: F, +// tooltip_action: A, +// action_name: &str, +// cx: &mut ViewContext, +// ) -> AnyElement { +// MouseEventHandler::new::(0, cx, |state, _| { +// let style = if enabled { +// style.style_for(state) +// } else { +// style.disabled_style() +// }; +// Svg::new(svg_path) +// .with_color(style.color) +// .constrained() +// .with_width(style.icon_width) +// .aligned() +// .contained() +// .with_style(style.container) +// .constrained() +// .with_width(style.button_width) +// .with_height(nav_button_height) +// .aligned() +// .top() +// }) +// .with_cursor_style(if enabled { +// CursorStyle::PointingHand +// } else { +// CursorStyle::default() +// }) +// .on_click(MouseButton::Left, move |_, toolbar, cx| { +// on_click(toolbar, cx) +// }) +// .with_tooltip::( +// 0, +// action_name.to_string(), +// Some(Box::new(tooltip_action)), +// tooltip_style, +// cx, +// ) +// .contained() +// .into_any_named("nav button") +// } impl Pane { - pub fn new( - workspace: WeakViewHandle, - project: ModelHandle, - next_timestamp: Arc, - cx: &mut ViewContext, - ) -> Self { - let pane_view_id = cx.view_id(); - let handle = cx.weak_handle(); - let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); - context_menu.update(cx, |menu, _| { - menu.set_position_mode(OverlayPositionMode::Local) - }); + // pub fn new( + // workspace: WeakViewHandle, + // project: ModelHandle, + // next_timestamp: Arc, + // cx: &mut ViewContext, + // ) -> Self { + // let pane_view_id = cx.view_id(); + // let handle = cx.weak_handle(); + // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); + // context_menu.update(cx, |menu, _| { + // menu.set_position_mode(OverlayPositionMode::Local) + // }); - Self { - items: Vec::new(), - activation_history: Vec::new(), - zoomed: false, - active_item_index: 0, - last_focused_view_by_item: Default::default(), - autoscroll: false, - nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState { - mode: NavigationMode::Normal, - backward_stack: Default::default(), - forward_stack: Default::default(), - closed_stack: Default::default(), - paths_by_item: Default::default(), - pane: handle.clone(), - next_timestamp, - }))), - toolbar: cx.add_view(|_| Toolbar::new()), - tab_bar_context_menu: TabBarContextMenu { - kind: TabBarContextMenuKind::New, - handle: context_menu, - }, - tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), - workspace, - project, - has_focus: false, - can_drop: Rc::new(|_, _| true), - can_split: true, - render_tab_bar_buttons: Rc::new(move |pane, cx| { - Flex::row() - // New menu - .with_child(Self::render_tab_bar_button( - 0, - "icons/plus.svg", - false, - Some(("New...".into(), None)), - cx, - |pane, cx| pane.deploy_new_menu(cx), - |pane, cx| { - pane.tab_bar_context_menu - .handle - .update(cx, |menu, _| menu.delay_cancel()) - }, - pane.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::New), - )) - .with_child(Self::render_tab_bar_button( - 1, - "icons/split.svg", - false, - Some(("Split Pane".into(), None)), - cx, - |pane, cx| pane.deploy_split_menu(cx), - |pane, cx| { - pane.tab_bar_context_menu - .handle - .update(cx, |menu, _| menu.delay_cancel()) - }, - pane.tab_bar_context_menu - .handle_if_kind(TabBarContextMenuKind::Split), - )) - .with_child({ - let icon_path; - let tooltip_label; - if pane.is_zoomed() { - icon_path = "icons/minimize.svg"; - tooltip_label = "Zoom In"; - } else { - icon_path = "icons/maximize.svg"; - tooltip_label = "Zoom In"; - } + // 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() - }), - } - } + // Pane::render_tab_bar_button( + // 2, + // icon_path, + // pane.is_zoomed(), + // Some((tooltip_label, Some(Box::new(ToggleZoom)))), + // cx, + // move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + // move |_, _| {}, + // None, + // ) + // }) + // .into_any() + // }), + // } + // } - pub(crate) fn workspace(&self) -> &WeakViewHandle { - &self.workspace - } + // pub(crate) fn workspace(&self) -> &WeakViewHandle { + // &self.workspace + // } - pub fn has_focus(&self) -> bool { - self.has_focus - } + // pub fn has_focus(&self) -> bool { + // self.has_focus + // } - pub fn active_item_index(&self) -> usize { - self.active_item_index - } + // pub fn active_item_index(&self) -> usize { + // self.active_item_index + // } - pub fn on_can_drop(&mut self, can_drop: F) - where - F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, - { - self.can_drop = Rc::new(can_drop); - } + // pub fn on_can_drop(&mut self, can_drop: F) + // where + // F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, + // { + // self.can_drop = Rc::new(can_drop); + // } - pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { - self.can_split = can_split; - cx.notify(); - } + // pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { + // self.can_split = can_split; + // cx.notify(); + // } - pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { - self.toolbar.update(cx, |toolbar, cx| { - toolbar.set_can_navigate(can_navigate, cx); - }); - cx.notify(); - } + // pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext) { + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.set_can_navigate(can_navigate, cx); + // }); + // cx.notify(); + // } - pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) - where - F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, - { - self.render_tab_bar_buttons = Rc::new(render); - cx.notify(); - } + // pub fn set_render_tab_bar_buttons(&mut self, cx: &mut ViewContext, render: F) + // where + // F: 'static + Fn(&mut Pane, &mut ViewContext) -> AnyElement, + // { + // self.render_tab_bar_buttons = Rc::new(render); + // cx.notify(); + // } - pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { - ItemNavHistory { - history: self.nav_history.clone(), - item: Rc::new(item.downgrade()), - } - } + // pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { + // ItemNavHistory { + // history: self.nav_history.clone(), + // item: Rc::new(item.downgrade()), + // } + // } - pub fn nav_history(&self) -> &NavHistory { - &self.nav_history - } + // pub fn nav_history(&self) -> &NavHistory { + // &self.nav_history + // } - pub fn nav_history_mut(&mut self) -> &mut NavHistory { - &mut 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 disable_history(&mut self) { + // self.nav_history.disable(); + // } - pub fn enable_history(&mut self) { - self.nav_history.enable(); - } + // 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_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() - } + // pub fn can_navigate_forward(&self) -> bool { + // !self.nav_history.0.borrow().forward_stack.is_empty() + // } - fn history_updated(&mut self, cx: &mut ViewContext) { - self.toolbar.update(cx, |_, cx| cx.notify()); - } + // fn history_updated(&mut self, cx: &mut ViewContext) { + // self.toolbar.update(cx, |_, cx| cx.notify()); + // } pub(crate) fn open_item( &mut self, @@ -599,63 +612,63 @@ impl Pane { cx.emit(Event::AddItem { item }); } - pub fn items_len(&self) -> usize { - self.items.len() - } + // pub fn items_len(&self) -> usize { + // self.items.len() + // } - pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { - self.items.iter() - } + // pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { + // self.items.iter() + // } - pub fn items_of_type(&self) -> impl '_ + Iterator> { - self.items - .iter() - .filter_map(|item| item.as_any().clone().downcast()) - } + // pub fn items_of_type(&self) -> impl '_ + Iterator> { + // self.items + // .iter() + // .filter_map(|item| item.as_any().clone().downcast()) + // } - pub fn active_item(&self) -> Option> { - self.items.get(self.active_item_index).cloned() - } + // pub fn active_item(&self) -> Option> { + // self.items.get(self.active_item_index).cloned() + // } - pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { - self.items - .get(self.active_item_index)? - .pixel_position_of_cursor(cx) - } + // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { + // self.items + // .get(self.active_item_index)? + // .pixel_position_of_cursor(cx) + // } - pub fn item_for_entry( - &self, - entry_id: ProjectEntryId, - cx: &AppContext, - ) -> Option> { - self.items.iter().find_map(|item| { - if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { - Some(item.boxed_clone()) - } else { - None - } - }) - } + // pub fn item_for_entry( + // &self, + // entry_id: ProjectEntryId, + // cx: &AppContext, + // ) -> Option> { + // self.items.iter().find_map(|item| { + // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + // Some(item.boxed_clone()) + // } else { + // None + // } + // }) + // } - pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { - self.items.iter().position(|i| i.id() == item.id()) - } + // pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { + // self.items.iter().position(|i| i.id() == item.id()) + // } - pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // Potentially warn the user of the new keybinding - let workspace_handle = self.workspace().clone(); - cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) - .detach(); + // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // // Potentially warn the user of the new keybinding + // let workspace_handle = self.workspace().clone(); + // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) + // .detach(); - if self.zoomed { - cx.emit(Event::ZoomOut); - } else if !self.items.is_empty() { - if !self.has_focus { - cx.focus_self(); - } - cx.emit(Event::ZoomIn); - } - } + // if self.zoomed { + // cx.emit(Event::ZoomOut); + // } else if !self.items.is_empty() { + // if !self.has_focus { + // cx.focus_self(); + // } + // cx.emit(Event::ZoomIn); + // } + // } pub fn activate_item( &mut self, @@ -699,2039 +712,2040 @@ impl Pane { } } - pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { - let mut index = self.active_item_index; - if index > 0 { - index -= 1; - } else if !self.items.is_empty() { - index = self.items.len() - 1; - } - self.activate_item(index, activate_pane, activate_pane, cx); - } - - pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { - let mut index = self.active_item_index; - if index + 1 < self.items.len() { - index += 1; - } else { - index = 0; - } - self.activate_item(index, activate_pane, activate_pane, cx); - } - - pub fn close_active_item( - &mut self, - action: &CloseActiveItem, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_item_by_id( - active_item_id, - action.save_intent.unwrap_or(SaveIntent::Close), - cx, - )) - } - - pub fn close_item_by_id( - &mut self, - item_id_to_close: usize, - save_intent: SaveIntent, - cx: &mut ViewContext, - ) -> Task> { - self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) - } - - pub fn close_inactive_items( - &mut self, - _: &CloseInactiveItems, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items(cx, SaveIntent::Close, move |item_id| { - item_id != active_item_id - })) - } - - pub fn close_clean_items( - &mut self, - _: &CloseCleanItems, - cx: &mut ViewContext, - ) -> Option>> { - let item_ids: Vec<_> = self - .items() - .filter(|item| !item.is_dirty(cx)) - .map(|item| item.id()) - .collect(); - Some(self.close_items(cx, SaveIntent::Close, move |item_id| { - item_ids.contains(&item_id) - })) - } - - pub fn close_items_to_the_left( - &mut self, - _: &CloseItemsToTheLeft, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items_to_the_left_by_id(active_item_id, cx)) - } - - pub fn close_items_to_the_left_by_id( - &mut self, - item_id: usize, - cx: &mut ViewContext, - ) -> Task> { - let item_ids: Vec<_> = self - .items() - .take_while(|item| item.id() != item_id) - .map(|item| item.id()) - .collect(); - self.close_items(cx, SaveIntent::Close, move |item_id| { - item_ids.contains(&item_id) - }) - } - - pub fn close_items_to_the_right( - &mut self, - _: &CloseItemsToTheRight, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items_to_the_right_by_id(active_item_id, cx)) - } - - pub fn close_items_to_the_right_by_id( - &mut self, - item_id: usize, - cx: &mut ViewContext, - ) -> Task> { - let item_ids: Vec<_> = self - .items() - .rev() - .take_while(|item| item.id() != item_id) - .map(|item| item.id()) - .collect(); - self.close_items(cx, SaveIntent::Close, move |item_id| { - item_ids.contains(&item_id) - }) - } - - pub fn close_all_items( - &mut self, - action: &CloseAllItems, - cx: &mut ViewContext, - ) -> Option>> { - if self.items.is_empty() { - return None; - } - - Some( - self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { - true - }), - ) - } - - pub(super) fn file_names_for_prompt( - items: &mut dyn Iterator>, - all_dirty_items: usize, - cx: &AppContext, - ) -> String { - /// Quantity of item paths displayed in prompt prior to cutoff.. - const FILE_NAMES_CUTOFF_POINT: usize = 10; - let mut file_names: Vec<_> = items - .filter_map(|item| { - item.project_path(cx).and_then(|project_path| { - project_path - .path - .file_name() - .and_then(|name| name.to_str().map(ToOwned::to_owned)) - }) - }) - .take(FILE_NAMES_CUTOFF_POINT) - .collect(); - let should_display_followup_text = - all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; - if should_display_followup_text { - let not_shown_files = all_dirty_items - file_names.len(); - if not_shown_files == 1 { - file_names.push(".. 1 file not shown".into()); - } else { - file_names.push(format!(".. {} files not shown", not_shown_files).into()); - } - } - let file_names = file_names.join("\n"); - format!( - "Do you want to save changes to the following {} files?\n{file_names}", - all_dirty_items - ) - } - - pub fn close_items( - &mut self, - cx: &mut ViewContext, - mut save_intent: SaveIntent, - should_close: impl 'static + Fn(usize) -> bool, - ) -> Task> { - // Find the items to close. - let mut items_to_close = Vec::new(); - let mut dirty_items = Vec::new(); - for item in &self.items { - if should_close(item.id()) { - items_to_close.push(item.boxed_clone()); - if item.is_dirty(cx) { - dirty_items.push(item.boxed_clone()); - } - } - } - - // If a buffer is open both in a singleton editor and in a multibuffer, make sure - // to focus the singleton buffer when prompting to save that buffer, as opposed - // to focusing the multibuffer, because this gives the user a more clear idea - // of what content they would be saving. - items_to_close.sort_by_key(|item| !item.is_singleton(cx)); - - let workspace = self.workspace.clone(); - cx.spawn(|pane, mut cx| async move { - if save_intent == SaveIntent::Close && dirty_items.len() > 1 { - let mut answer = pane.update(&mut cx, |_, cx| { - let prompt = - Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); - cx.prompt( - PromptLevel::Warning, - &prompt, - &["Save all", "Discard all", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => save_intent = SaveIntent::SaveAll, - Some(1) => save_intent = SaveIntent::Skip, - _ => {} - } - } - let mut saved_project_items_ids = HashSet::default(); - for item in items_to_close.clone() { - // Find the item's current index and its set of project item models. Avoid - // storing these in advance, in case they have changed since this task - // was started. - let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { - (pane.index_for_item(&*item), item.project_item_model_ids(cx)) - })?; - let item_ix = if let Some(ix) = item_ix { - ix - } else { - continue; - }; - - // Check if this view has any project items that are not open anywhere else - // in the workspace, AND that the user has not already been prompted to save. - // If there are any such project entries, prompt the user to save this item. - let project = workspace.read_with(&cx, |workspace, cx| { - for item in workspace.items(cx) { - if !items_to_close - .iter() - .any(|item_to_close| item_to_close.id() == item.id()) - { - let other_project_item_ids = item.project_item_model_ids(cx); - project_item_ids.retain(|id| !other_project_item_ids.contains(id)); - } - } - workspace.project().clone() - })?; - let should_save = project_item_ids - .iter() - .any(|id| saved_project_items_ids.insert(*id)); - - if should_save - && !Self::save_item( - project.clone(), - &pane, - item_ix, - &*item, - save_intent, - &mut cx, - ) - .await? - { - break; - } - - // Remove the item from the pane. - pane.update(&mut cx, |pane, cx| { - if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { - pane.remove_item(item_ix, false, cx); - } - })?; - } - - pane.update(&mut cx, |_, cx| cx.notify())?; - Ok(()) - }) - } - - pub fn remove_item( - &mut self, - item_index: usize, - activate_pane: bool, - cx: &mut ViewContext, - ) { - self.activation_history - .retain(|&history_entry| history_entry != self.items[item_index].id()); - - if item_index == self.active_item_index { - let index_to_activate = self - .activation_history - .pop() - .and_then(|last_activated_item| { - self.items.iter().enumerate().find_map(|(index, item)| { - (item.id() == last_activated_item).then_some(index) - }) - }) - // We didn't have a valid activation history entry, so fallback - // to activating the item to the left - .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); - - let should_activate = activate_pane || self.has_focus; - self.activate_item(index_to_activate, should_activate, should_activate, cx); - } - - let item = self.items.remove(item_index); - - cx.emit(Event::RemoveItem { item_id: item.id() }); - if self.items.is_empty() { - item.deactivated(cx); - self.update_toolbar(cx); - cx.emit(Event::Remove); - } - - if item_index < self.active_item_index { - self.active_item_index -= 1; - } - - self.nav_history.set_mode(NavigationMode::ClosingItem); - item.deactivated(cx); - self.nav_history.set_mode(NavigationMode::Normal); - - if let Some(path) = item.project_path(cx) { - let abs_path = self - .nav_history - .0 - .borrow() - .paths_by_item - .get(&item.id()) - .and_then(|(_, abs_path)| abs_path.clone()); - - self.nav_history - .0 - .borrow_mut() - .paths_by_item - .insert(item.id(), (path, abs_path)); - } else { - self.nav_history - .0 - .borrow_mut() - .paths_by_item - .remove(&item.id()); - } - - if self.items.is_empty() && self.zoomed { - cx.emit(Event::ZoomOut); - } - - cx.notify(); - } - - pub async fn save_item( - project: ModelHandle, - pane: &WeakViewHandle, - item_ix: usize, - item: &dyn ItemHandle, - save_intent: SaveIntent, - cx: &mut AsyncAppContext, - ) -> Result { - const CONFLICT_MESSAGE: &str = - "This file has changed on disk since you started editing it. Do you want to overwrite it?"; - - if save_intent == SaveIntent::Skip { - return Ok(true); - } - - let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| { - ( - item.has_conflict(cx), - item.is_dirty(cx), - item.can_save(cx), - item.is_singleton(cx), - ) - }); - - // when saving a single buffer, we ignore whether or not it's dirty. - if save_intent == SaveIntent::Save { - is_dirty = true; - } - - if save_intent == SaveIntent::SaveAs { - is_dirty = true; - has_conflict = false; - can_save = false; - } - - if save_intent == SaveIntent::Overwrite { - has_conflict = false; - } - - if has_conflict && can_save { - let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); - cx.prompt( - PromptLevel::Warning, - CONFLICT_MESSAGE, - &["Overwrite", "Discard", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, - Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, - _ => return Ok(false), - } - } else if is_dirty && (can_save || can_save_as) { - if save_intent == SaveIntent::Close { - let will_autosave = cx.read(|cx| { - matches!( - settings::get::(cx).autosave, - AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ) && Self::can_autosave_item(&*item, cx) - }); - if !will_autosave { - let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); - let prompt = dirty_message_for(item.project_path(cx)); - cx.prompt( - PromptLevel::Warning, - &prompt, - &["Save", "Don't Save", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => {} - Some(1) => return Ok(true), // Don't save his file - _ => return Ok(false), // Cancel - } - } - } - - if can_save { - pane.update(cx, |_, cx| item.save(project, cx))?.await?; - } else if can_save_as { - let start_abs_path = project - .read_with(cx, |project, cx| { - let worktree = project.visible_worktrees(cx).next()?; - Some(worktree.read(cx).as_local()?.abs_path().to_path_buf()) - }) - .unwrap_or_else(|| Path::new("").into()); - - let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path)); - if let Some(abs_path) = abs_path.next().await.flatten() { - pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))? - .await?; - } else { - return Ok(false); - } - } - } - Ok(true) - } - - fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool { - let is_deleted = item.project_entry_ids(cx).is_empty(); - item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted - } - - pub fn autosave_item( - item: &dyn ItemHandle, - project: ModelHandle, - cx: &mut WindowContext, - ) -> Task> { - if Self::can_autosave_item(item, cx) { - item.save(project, cx) - } else { - Task::ready(Ok(())) - } - } - - pub fn focus_active_item(&mut self, cx: &mut ViewContext) { - if let Some(active_item) = self.active_item() { - cx.focus(active_item.as_any()); - } - } - - pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { - cx.emit(Event::Split(direction)); - } - - fn deploy_split_menu(&mut self, cx: &mut ViewContext) { - self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.toggle( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("Split Right", SplitRight), - ContextMenuItem::action("Split Left", SplitLeft), - ContextMenuItem::action("Split Up", SplitUp), - ContextMenuItem::action("Split Down", SplitDown), - ], - cx, - ); - }); - - self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; - } - - fn deploy_new_menu(&mut self, cx: &mut ViewContext) { - self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.toggle( - Default::default(), - AnchorCorner::TopRight, - vec![ - ContextMenuItem::action("New File", NewFile), - ContextMenuItem::action("New Terminal", NewCenterTerminal), - ContextMenuItem::action("New Search", NewSearch), - ], - cx, - ); - }); - - self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; - } - - fn deploy_tab_context_menu( - &mut self, - position: Vector2F, - target_item_id: usize, - cx: &mut ViewContext, - ) { - let active_item_id = self.items[self.active_item_index].id(); - let is_active_item = target_item_id == active_item_id; - let target_pane = cx.weak_handle(); - - // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab - - self.tab_context_menu.update(cx, |menu, cx| { - menu.show( - position, - AnchorCorner::TopLeft, - if is_active_item { - vec![ - ContextMenuItem::action( - "Close Active Item", - CloseActiveItem { save_intent: None }, - ), - ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::action("Close Clean Items", CloseCleanItems), - ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), - ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), - ContextMenuItem::action( - "Close All Items", - CloseAllItems { save_intent: None }, - ), - ] - } else { - // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. - vec![ - ContextMenuItem::handler("Close Inactive Item", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_item_by_id( - target_item_id, - SaveIntent::Close, - cx, - ) - .detach_and_log_err(cx); - }) - } - } - }), - ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - ContextMenuItem::action("Close Clean Items", CloseCleanItems), - ContextMenuItem::handler("Close Items To The Left", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_items_to_the_left_by_id(target_item_id, cx) - .detach_and_log_err(cx); - }) - } - } - }), - ContextMenuItem::handler("Close Items To The Right", { - let pane = target_pane.clone(); - move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_items_to_the_right_by_id(target_item_id, cx) - .detach_and_log_err(cx); - }) - } - } - }), - ContextMenuItem::action( - "Close All Items", - CloseAllItems { save_intent: None }, - ), - ] - }, - cx, - ); - }); - } - - pub fn toolbar(&self) -> &ViewHandle { - &self.toolbar - } - - pub fn handle_deleted_project_item( - &mut self, - entry_id: ProjectEntryId, - cx: &mut ViewContext, - ) -> Option<()> { - let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { - if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { - Some((i, item.id())) - } else { - None - } - })?; - - self.remove_item(item_index_to_delete, false, cx); - self.nav_history.remove_item(item_id); - - Some(()) - } - - fn update_toolbar(&mut self, cx: &mut ViewContext) { - let active_item = self - .items - .get(self.active_item_index) - .map(|item| item.as_ref()); - self.toolbar.update(cx, |toolbar, cx| { - toolbar.set_active_item(active_item, cx); - }); - } - - fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { - let theme = theme::current(cx).clone(); - - let pane = cx.handle().downgrade(); - let autoscroll = if mem::take(&mut self.autoscroll) { - Some(self.active_item_index) - } else { - None - }; - - let pane_active = self.has_focus; - - enum Tabs {} - let mut row = Flex::row().scrollable::(1, autoscroll, cx); - for (ix, (item, detail)) in self - .items - .iter() - .cloned() - .zip(self.tab_details(cx)) - .enumerate() - { - let git_status = item - .project_path(cx) - .and_then(|path| self.project.read(cx).entry_for_path(&path, cx)) - .and_then(|entry| entry.git_status()); - - let detail = if detail == 0 { None } else { Some(detail) }; - let tab_active = ix == self.active_item_index; - - row.add_child({ - enum TabDragReceiver {} - let mut receiver = - dragged_item_receiver::(self, ix, ix, true, None, cx, { - let item = item.clone(); - let pane = pane.clone(); - let detail = detail.clone(); - - let theme = theme::current(cx).clone(); - let mut tooltip_theme = theme.tooltip.clone(); - tooltip_theme.max_text_width = None; - let tab_tooltip_text = - item.tab_tooltip_text(cx).map(|text| text.into_owned()); - - let mut tab_style = theme - .workspace - .tab_bar - .tab_style(pane_active, tab_active) - .clone(); - let should_show_status = settings::get::(cx).git_status; - if should_show_status && git_status != None { - tab_style.label.text.color = match git_status.unwrap() { - GitFileStatus::Added => tab_style.git.inserted, - GitFileStatus::Modified => tab_style.git.modified, - GitFileStatus::Conflict => tab_style.git.conflict, - }; - } - - move |mouse_state, cx| { - let hovered = mouse_state.hovered(); - - enum Tab {} - let mouse_event_handler = - MouseEventHandler::new::(ix, cx, |_, cx| { - Self::render_tab( - &item, - pane.clone(), - ix == 0, - detail, - hovered, - &tab_style, - cx, - ) - }) - .on_down(MouseButton::Left, move |_, this, cx| { - this.activate_item(ix, true, true, cx); - }) - .on_click(MouseButton::Middle, { - let item_id = item.id(); - move |_, pane, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, cx) - .detach_and_log_err(cx); - } - }) - .on_down( - MouseButton::Right, - move |event, pane, cx| { - pane.deploy_tab_context_menu(event.position, item.id(), cx); - }, - ); - - if let Some(tab_tooltip_text) = tab_tooltip_text { - mouse_event_handler - .with_tooltip::( - ix, - tab_tooltip_text, - None, - tooltip_theme, - cx, - ) - .into_any() - } else { - mouse_event_handler.into_any() - } - } - }); - - if !pane_active || !tab_active { - receiver = receiver.with_cursor_style(CursorStyle::PointingHand); - } - - receiver.as_draggable( - DraggedItem { - handle: item, - pane: pane.clone(), - }, - { - let theme = theme::current(cx).clone(); - - let detail = detail.clone(); - move |_, dragged_item: &DraggedItem, cx: &mut ViewContext| { - let tab_style = &theme.workspace.tab_bar.dragged_tab; - Self::render_dragged_tab( - &dragged_item.handle, - dragged_item.pane.clone(), - false, - detail, - false, - &tab_style, - cx, - ) - } - }, - ) - }) - } - - // Use the inactive tab style along with the current pane's active status to decide how to render - // the filler - let filler_index = self.items.len(); - let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); - enum Filler {} - row.add_child( - dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { - Empty::new() - .contained() - .with_style(filler_style.container) - .with_border(filler_style.container.border) - }) - .flex(1., true) - .into_any_named("filler"), - ); - - row - } - - fn tab_details(&self, cx: &AppContext) -> Vec { - let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); - - let mut tab_descriptions = HashMap::default(); - let mut done = false; - while !done { - done = true; - - // Store item indices by their tab description. - for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { - if let Some(description) = item.tab_description(*detail, cx) { - if *detail == 0 - || Some(&description) != item.tab_description(detail - 1, cx).as_ref() - { - tab_descriptions - .entry(description) - .or_insert(Vec::new()) - .push(ix); - } - } - } - - // If two or more items have the same tab description, increase their level - // of detail and try again. - for (_, item_ixs) in tab_descriptions.drain() { - if item_ixs.len() > 1 { - done = false; - for ix in item_ixs { - tab_details[ix] += 1; - } - } - } - } - - tab_details - } - - fn render_tab( - item: &Box, - pane: WeakViewHandle, - first: bool, - detail: Option, - hovered: bool, - tab_style: &theme::Tab, - cx: &mut ViewContext, - ) -> AnyElement { - let title = item.tab_content(detail, &tab_style, cx); - Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) - } - - fn render_dragged_tab( - item: &Box, - pane: WeakViewHandle, - first: bool, - detail: Option, - hovered: bool, - tab_style: &theme::Tab, - cx: &mut ViewContext, - ) -> AnyElement { - let title = item.dragged_tab_content(detail, &tab_style, cx); - Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) - } - - fn render_tab_with_title( - title: AnyElement, - item: &Box, - pane: WeakViewHandle, - first: bool, - hovered: bool, - tab_style: &theme::Tab, - cx: &mut ViewContext, - ) -> AnyElement { - let mut container = tab_style.container.clone(); - if first { - container.border.left = false; - } - - let buffer_jewel_element = { - let diameter = 7.0; - let icon_color = if item.has_conflict(cx) { - Some(tab_style.icon_conflict) - } else if item.is_dirty(cx) { - Some(tab_style.icon_dirty) - } else { - None - }; - - Canvas::new(move |bounds, _, _, cx| { - if let Some(color) = icon_color { - let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); - cx.scene().push_quad(Quad { - bounds: square, - background: Some(color), - border: Default::default(), - corner_radii: (diameter / 2.).into(), - }); - } - }) - .constrained() - .with_width(diameter) - .with_height(diameter) - .aligned() - }; - - let title_element = title.aligned().contained().with_style(ContainerStyle { - margin: Margin { - left: tab_style.spacing, - right: tab_style.spacing, - ..Default::default() - }, - ..Default::default() - }); - - let close_element = if hovered { - let item_id = item.id(); - enum TabCloseButton {} - let icon = Svg::new("icons/x.svg"); - MouseEventHandler::new::(item_id, cx, |mouse_state, _| { - if mouse_state.hovered() { - icon.with_color(tab_style.icon_close_active) - } else { - icon.with_color(tab_style.icon_close) - } - }) - .with_padding(Padding::uniform(4.)) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let pane = pane.clone(); - move |_, _, cx| { - let pane = pane.clone(); - cx.window_context().defer(move |cx| { - if let Some(pane) = pane.upgrade(cx) { - pane.update(cx, |pane, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, cx) - .detach_and_log_err(cx); - }); - } - }); - } - }) - .into_any_named("close-tab-icon") - .constrained() - } else { - Empty::new().constrained() - } - .with_width(tab_style.close_icon_width) - .aligned(); - - let close_right = settings::get::(cx).close_position.right(); - - if close_right { - Flex::row() - .with_child(buffer_jewel_element) - .with_child(title_element) - .with_child(close_element) - } else { - Flex::row() - .with_child(close_element) - .with_child(title_element) - .with_child(buffer_jewel_element) - } - .contained() - .with_style(container) - .constrained() - .with_height(tab_style.height) - .into_any() - } - - pub fn render_tab_bar_button< - F1: 'static + Fn(&mut Pane, &mut EventContext), - F2: 'static + Fn(&mut Pane, &mut EventContext), - >( - index: usize, - icon: &'static str, - is_active: bool, - tooltip: Option<(&'static str, Option>)>, - cx: &mut ViewContext, - on_click: F1, - on_down: F2, - context_menu: Option>, - ) -> AnyElement { - enum TabBarButton {} - - let mut button = MouseEventHandler::new::(index, cx, |mouse_state, cx| { - let theme = &settings::get::(cx).theme.workspace.tab_bar; - let style = theme.pane_button.in_state(is_active).style_for(mouse_state); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) - .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) - .into_any(); - if let Some((tooltip, action)) = tooltip { - let tooltip_style = settings::get::(cx).theme.tooltip.clone(); - button = button - .with_tooltip::(index, tooltip, action, tooltip_style, cx) - .into_any(); - } - - Stack::new() - .with_child(button) - .with_children( - context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), - ) - .flex(1., false) - .into_any_named("tab bar button") - } - - fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { - let background = theme.workspace.background; - Empty::new() - .contained() - .with_background_color(background) - .into_any() - } - - pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { - self.zoomed = zoomed; - cx.notify(); - } - - pub fn is_zoomed(&self) -> bool { - self.zoomed - } -} - -impl Entity for Pane { - type Event = Event; -} - -impl View for Pane { - fn ui_name() -> &'static str { - "Pane" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - enum MouseNavigationHandler {} - - MouseEventHandler::new::(0, cx, |_, cx| { - let active_item_index = self.active_item_index; - - if let Some(active_item) = self.active_item() { - Flex::column() - .with_child({ - let theme = theme::current(cx).clone(); - - let mut stack = Stack::new(); - - enum TabBarEventHandler {} - stack.add_child( - MouseEventHandler::new::(0, cx, |_, _| { - Empty::new() - .contained() - .with_style(theme.workspace.tab_bar.container) - }) - .on_down( - MouseButton::Left, - move |_, this, cx| { - this.activate_item(active_item_index, true, true, cx); - }, - ), - ); - let tooltip_style = theme.tooltip.clone(); - let tab_bar_theme = theme.workspace.tab_bar.clone(); - - let nav_button_height = tab_bar_theme.height; - let button_style = tab_bar_theme.nav_button; - let border_for_nav_buttons = tab_bar_theme - .tab_style(false, false) - .container - .border - .clone(); - - let mut tab_row = Flex::row() - .with_child(nav_button( - "icons/arrow_left.svg", - button_style.clone(), - nav_button_height, - tooltip_style.clone(), - self.can_navigate_backward(), - { - move |pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace - .go_back(pane, cx) - .detach_and_log_err(cx) - }) - }) - } - } - }, - super::GoBack, - "Go Back", - cx, - )) - .with_child( - nav_button( - "icons/arrow_right.svg", - button_style.clone(), - nav_button_height, - tooltip_style, - self.can_navigate_forward(), - { - move |pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace - .go_forward(pane, cx) - .detach_and_log_err(cx) - }) - }) - } - } - }, - super::GoForward, - "Go Forward", - cx, - ) - .contained() - .with_border(border_for_nav_buttons), - ) - .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); - - if self.has_focus { - let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); - tab_row.add_child( - (render_tab_bar_buttons)(self, cx) - .contained() - .with_style(theme.workspace.tab_bar.pane_button_container) - .flex(1., false) - .into_any(), - ) - } - - stack.add_child(tab_row); - stack - .constrained() - .with_height(theme.workspace.tab_bar.height) - .flex(1., false) - .into_any_named("tab bar") - }) - .with_child({ - enum PaneContentTabDropTarget {} - dragged_item_receiver::( - self, - 0, - self.active_item_index + 1, - !self.can_split, - if self.can_split { Some(100.) } else { None }, - cx, - { - let toolbar = self.toolbar.clone(); - let toolbar_hidden = toolbar.read(cx).hidden(); - move |_, cx| { - Flex::column() - .with_children( - (!toolbar_hidden) - .then(|| ChildView::new(&toolbar, cx).expanded()), - ) - .with_child( - ChildView::new(active_item.as_any(), cx).flex(1., true), - ) - } - }, - ) - .flex(1., true) - }) - .with_child(ChildView::new(&self.tab_context_menu, cx)) - .into_any() - } else { - enum EmptyPane {} - let theme = theme::current(cx).clone(); - - dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { - self.render_blank_pane(&theme, cx) - }) - .on_down(MouseButton::Left, |_, _, cx| { - cx.focus_parent(); - }) - .into_any() - } - }) - .on_down( - MouseButton::Navigate(NavigationDirection::Back), - move |_, pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.go_back(pane, cx).detach_and_log_err(cx) - }) - }) - } - }, - ) - .on_down(MouseButton::Navigate(NavigationDirection::Forward), { - move |_, pane, cx| { - if let Some(workspace) = pane.workspace.upgrade(cx) { - let pane = cx.weak_handle(); - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.go_forward(pane, cx).detach_and_log_err(cx) - }) - }) - } - } - }) - .into_any_named("pane") - } - - fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - if !self.has_focus { - self.has_focus = true; - cx.emit(Event::Focus); - cx.notify(); - } - - self.toolbar.update(cx, |toolbar, cx| { - toolbar.focus_changed(true, cx); - }); - - if let Some(active_item) = self.active_item() { - if cx.is_self_focused() { - // Pane was focused directly. We need to either focus a view inside the active item, - // or focus the active item itself - if let Some(weak_last_focused_view) = - self.last_focused_view_by_item.get(&active_item.id()) - { - if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) { - cx.focus(&last_focused_view); - return; - } else { - self.last_focused_view_by_item.remove(&active_item.id()); - } - } - - cx.focus(active_item.as_any()); - } else if focused != self.tab_bar_context_menu.handle { - self.last_focused_view_by_item - .insert(active_item.id(), focused.downgrade()); - } - } - } - - fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = false; - self.toolbar.update(cx, |toolbar, cx| { - toolbar.focus_changed(false, cx); - }); - cx.notify(); - } - - fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { - Self::reset_to_default_keymap_context(keymap); - } -} - -impl ItemNavHistory { - pub fn push(&mut self, data: Option, cx: &mut WindowContext) { - self.history.push(data, self.item.clone(), cx); - } - - pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { - self.history.pop(NavigationMode::GoingBack, cx) - } - - pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { - self.history.pop(NavigationMode::GoingForward, cx) - } -} - -impl NavHistory { - pub fn for_each_entry( - &self, - cx: &AppContext, - mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), - ) { - let borrowed_history = self.0.borrow(); - borrowed_history - .forward_stack - .iter() - .chain(borrowed_history.backward_stack.iter()) - .chain(borrowed_history.closed_stack.iter()) - .for_each(|entry| { - if let Some(project_and_abs_path) = - borrowed_history.paths_by_item.get(&entry.item.id()) - { - f(entry, project_and_abs_path.clone()); - } else if let Some(item) = entry.item.upgrade(cx) { - if let Some(path) = item.project_path(cx) { - f(entry, (path, None)); - } - } - }) - } - - pub fn set_mode(&mut self, mode: NavigationMode) { - self.0.borrow_mut().mode = mode; - } + // pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { + // let mut index = self.active_item_index; + // if index > 0 { + // index -= 1; + // } else if !self.items.is_empty() { + // index = self.items.len() - 1; + // } + // self.activate_item(index, activate_pane, activate_pane, cx); + // } + + // pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext) { + // let mut index = self.active_item_index; + // if index + 1 < self.items.len() { + // index += 1; + // } else { + // index = 0; + // } + // self.activate_item(index, activate_pane, activate_pane, cx); + // } + + // pub fn close_active_item( + // &mut self, + // action: &CloseActiveItem, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_item_by_id( + // active_item_id, + // action.save_intent.unwrap_or(SaveIntent::Close), + // cx, + // )) + // } + + // pub fn close_item_by_id( + // &mut self, + // item_id_to_close: usize, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + // } + + // pub fn close_inactive_items( + // &mut self, + // _: &CloseInactiveItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_id != active_item_id + // })) + // } + + // pub fn close_clean_items( + // &mut self, + // _: &CloseCleanItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // let item_ids: Vec<_> = self + // .items() + // .filter(|item| !item.is_dirty(cx)) + // .map(|item| item.id()) + // .collect(); + // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // })) + // } + + // pub fn close_items_to_the_left( + // &mut self, + // _: &CloseItemsToTheLeft, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items_to_the_left_by_id(active_item_id, cx)) + // } + + // pub fn close_items_to_the_left_by_id( + // &mut self, + // item_id: usize, + // cx: &mut ViewContext, + // ) -> Task> { + // let item_ids: Vec<_> = self + // .items() + // .take_while(|item| item.id() != item_id) + // .map(|item| item.id()) + // .collect(); + // self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // }) + // } + + // pub fn close_items_to_the_right( + // &mut self, + // _: &CloseItemsToTheRight, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items_to_the_right_by_id(active_item_id, cx)) + // } + + // pub fn close_items_to_the_right_by_id( + // &mut self, + // item_id: usize, + // cx: &mut ViewContext, + // ) -> Task> { + // let item_ids: Vec<_> = self + // .items() + // .rev() + // .take_while(|item| item.id() != item_id) + // .map(|item| item.id()) + // .collect(); + // self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // }) + // } + + // pub fn close_all_items( + // &mut self, + // action: &CloseAllItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + + // Some( + // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { + // true + // }), + // ) + // } + + // pub(super) fn file_names_for_prompt( + // items: &mut dyn Iterator>, + // all_dirty_items: usize, + // cx: &AppContext, + // ) -> String { + // /// Quantity of item paths displayed in prompt prior to cutoff.. + // const FILE_NAMES_CUTOFF_POINT: usize = 10; + // let mut file_names: Vec<_> = items + // .filter_map(|item| { + // item.project_path(cx).and_then(|project_path| { + // project_path + // .path + // .file_name() + // .and_then(|name| name.to_str().map(ToOwned::to_owned)) + // }) + // }) + // .take(FILE_NAMES_CUTOFF_POINT) + // .collect(); + // let should_display_followup_text = + // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; + // if should_display_followup_text { + // let not_shown_files = all_dirty_items - file_names.len(); + // if not_shown_files == 1 { + // file_names.push(".. 1 file not shown".into()); + // } else { + // file_names.push(format!(".. {} files not shown", not_shown_files).into()); + // } + // } + // let file_names = file_names.join("\n"); + // format!( + // "Do you want to save changes to the following {} files?\n{file_names}", + // all_dirty_items + // ) + // } + + // pub fn close_items( + // &mut self, + // cx: &mut ViewContext, + // mut save_intent: SaveIntent, + // should_close: impl 'static + Fn(usize) -> bool, + // ) -> Task> { + // // Find the items to close. + // let mut items_to_close = Vec::new(); + // let mut dirty_items = Vec::new(); + // for item in &self.items { + // if should_close(item.id()) { + // items_to_close.push(item.boxed_clone()); + // if item.is_dirty(cx) { + // dirty_items.push(item.boxed_clone()); + // } + // } + // } + + // // If a buffer is open both in a singleton editor and in a multibuffer, make sure + // // to focus the singleton buffer when prompting to save that buffer, as opposed + // // to focusing the multibuffer, because this gives the user a more clear idea + // // of what content they would be saving. + // items_to_close.sort_by_key(|item| !item.is_singleton(cx)); + + // let workspace = self.workspace.clone(); + // cx.spawn(|pane, mut cx| async move { + // if save_intent == SaveIntent::Close && dirty_items.len() > 1 { + // let mut answer = pane.update(&mut cx, |_, cx| { + // let prompt = + // Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); + // cx.prompt( + // PromptLevel::Warning, + // &prompt, + // &["Save all", "Discard all", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => save_intent = SaveIntent::SaveAll, + // Some(1) => save_intent = SaveIntent::Skip, + // _ => {} + // } + // } + // let mut saved_project_items_ids = HashSet::default(); + // for item in items_to_close.clone() { + // // Find the item's current index and its set of project item models. Avoid + // // storing these in advance, in case they have changed since this task + // // was started. + // let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { + // (pane.index_for_item(&*item), item.project_item_model_ids(cx)) + // })?; + // let item_ix = if let Some(ix) = item_ix { + // ix + // } else { + // continue; + // }; + + // // Check if this view has any project items that are not open anywhere else + // // in the workspace, AND that the user has not already been prompted to save. + // // If there are any such project entries, prompt the user to save this item. + // let project = workspace.read_with(&cx, |workspace, cx| { + // for item in workspace.items(cx) { + // if !items_to_close + // .iter() + // .any(|item_to_close| item_to_close.id() == item.id()) + // { + // let other_project_item_ids = item.project_item_model_ids(cx); + // project_item_ids.retain(|id| !other_project_item_ids.contains(id)); + // } + // } + // workspace.project().clone() + // })?; + // let should_save = project_item_ids + // .iter() + // .any(|id| saved_project_items_ids.insert(*id)); + + // if should_save + // && !Self::save_item( + // project.clone(), + // &pane, + // item_ix, + // &*item, + // save_intent, + // &mut cx, + // ) + // .await? + // { + // break; + // } + + // // Remove the item from the pane. + // pane.update(&mut cx, |pane, cx| { + // if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { + // pane.remove_item(item_ix, false, cx); + // } + // })?; + // } + + // pane.update(&mut cx, |_, cx| cx.notify())?; + // Ok(()) + // }) + // } + + // pub fn remove_item( + // &mut self, + // item_index: usize, + // activate_pane: bool, + // cx: &mut ViewContext, + // ) { + // self.activation_history + // .retain(|&history_entry| history_entry != self.items[item_index].id()); + + // if item_index == self.active_item_index { + // let index_to_activate = self + // .activation_history + // .pop() + // .and_then(|last_activated_item| { + // self.items.iter().enumerate().find_map(|(index, item)| { + // (item.id() == last_activated_item).then_some(index) + // }) + // }) + // // We didn't have a valid activation history entry, so fallback + // // to activating the item to the left + // .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); + + // let should_activate = activate_pane || self.has_focus; + // self.activate_item(index_to_activate, should_activate, should_activate, cx); + // } + + // let item = self.items.remove(item_index); + + // cx.emit(Event::RemoveItem { item_id: item.id() }); + // if self.items.is_empty() { + // item.deactivated(cx); + // self.update_toolbar(cx); + // cx.emit(Event::Remove); + // } + + // if item_index < self.active_item_index { + // self.active_item_index -= 1; + // } + + // self.nav_history.set_mode(NavigationMode::ClosingItem); + // item.deactivated(cx); + // self.nav_history.set_mode(NavigationMode::Normal); + + // if let Some(path) = item.project_path(cx) { + // let abs_path = self + // .nav_history + // .0 + // .borrow() + // .paths_by_item + // .get(&item.id()) + // .and_then(|(_, abs_path)| abs_path.clone()); + + // self.nav_history + // .0 + // .borrow_mut() + // .paths_by_item + // .insert(item.id(), (path, abs_path)); + // } else { + // self.nav_history + // .0 + // .borrow_mut() + // .paths_by_item + // .remove(&item.id()); + // } + + // if self.items.is_empty() && self.zoomed { + // cx.emit(Event::ZoomOut); + // } + + // cx.notify(); + // } + + // pub async fn save_item( + // project: ModelHandle, + // pane: &WeakViewHandle, + // item_ix: usize, + // item: &dyn ItemHandle, + // save_intent: SaveIntent, + // cx: &mut AsyncAppContext, + // ) -> Result { + // const CONFLICT_MESSAGE: &str = + // "This file has changed on disk since you started editing it. Do you want to overwrite it?"; + + // if save_intent == SaveIntent::Skip { + // return Ok(true); + // } + + // let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| { + // ( + // item.has_conflict(cx), + // item.is_dirty(cx), + // item.can_save(cx), + // item.is_singleton(cx), + // ) + // }); + + // // when saving a single buffer, we ignore whether or not it's dirty. + // if save_intent == SaveIntent::Save { + // is_dirty = true; + // } + + // if save_intent == SaveIntent::SaveAs { + // is_dirty = true; + // has_conflict = false; + // can_save = false; + // } + + // if save_intent == SaveIntent::Overwrite { + // has_conflict = false; + // } + + // if has_conflict && can_save { + // let mut answer = pane.update(cx, |pane, cx| { + // pane.activate_item(item_ix, true, true, cx); + // cx.prompt( + // PromptLevel::Warning, + // CONFLICT_MESSAGE, + // &["Overwrite", "Discard", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, + // _ => return Ok(false), + // } + // } else if is_dirty && (can_save || can_save_as) { + // if save_intent == SaveIntent::Close { + // let will_autosave = cx.read(|cx| { + // matches!( + // settings::get::(cx).autosave, + // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange + // ) && Self::can_autosave_item(&*item, cx) + // }); + // if !will_autosave { + // let mut answer = pane.update(cx, |pane, cx| { + // pane.activate_item(item_ix, true, true, cx); + // let prompt = dirty_message_for(item.project_path(cx)); + // cx.prompt( + // PromptLevel::Warning, + // &prompt, + // &["Save", "Don't Save", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => {} + // Some(1) => return Ok(true), // Don't save his file + // _ => return Ok(false), // Cancel + // } + // } + // } + + // if can_save { + // pane.update(cx, |_, cx| item.save(project, cx))?.await?; + // } else if can_save_as { + // let start_abs_path = project + // .read_with(cx, |project, cx| { + // let worktree = project.visible_worktrees(cx).next()?; + // Some(worktree.read(cx).as_local()?.abs_path().to_path_buf()) + // }) + // .unwrap_or_else(|| Path::new("").into()); + + // let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path)); + // if let Some(abs_path) = abs_path.next().await.flatten() { + // pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))? + // .await?; + // } else { + // return Ok(false); + // } + // } + // } + // Ok(true) + // } + + // fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool { + // let is_deleted = item.project_entry_ids(cx).is_empty(); + // item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted + // } + + // pub fn autosave_item( + // item: &dyn ItemHandle, + // project: ModelHandle, + // cx: &mut WindowContext, + // ) -> Task> { + // if Self::can_autosave_item(item, cx) { + // item.save(project, cx) + // } else { + // Task::ready(Ok(())) + // } + // } + + // pub fn focus_active_item(&mut self, cx: &mut ViewContext) { + // if let Some(active_item) = self.active_item() { + // cx.focus(active_item.as_any()); + // } + // } + + // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { + // cx.emit(Event::Split(direction)); + // } + + // fn deploy_split_menu(&mut self, cx: &mut ViewContext) { + // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { + // menu.toggle( + // Default::default(), + // AnchorCorner::TopRight, + // vec![ + // ContextMenuItem::action("Split Right", SplitRight), + // ContextMenuItem::action("Split Left", SplitLeft), + // ContextMenuItem::action("Split Up", SplitUp), + // ContextMenuItem::action("Split Down", SplitDown), + // ], + // cx, + // ); + // }); + + // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; + // } + + // fn deploy_new_menu(&mut self, cx: &mut ViewContext) { + // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { + // menu.toggle( + // Default::default(), + // AnchorCorner::TopRight, + // vec![ + // ContextMenuItem::action("New File", NewFile), + // ContextMenuItem::action("New Terminal", NewCenterTerminal), + // ContextMenuItem::action("New Search", NewSearch), + // ], + // cx, + // ); + // }); + + // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; + // } + + // fn deploy_tab_context_menu( + // &mut self, + // position: Vector2F, + // target_item_id: usize, + // cx: &mut ViewContext, + // ) { + // let active_item_id = self.items[self.active_item_index].id(); + // let is_active_item = target_item_id == active_item_id; + // let target_pane = cx.weak_handle(); + + // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab + + // self.tab_context_menu.update(cx, |menu, cx| { + // menu.show( + // position, + // AnchorCorner::TopLeft, + // if is_active_item { + // vec![ + // ContextMenuItem::action( + // "Close Active Item", + // CloseActiveItem { save_intent: None }, + // ), + // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + // ContextMenuItem::action("Close Clean Items", CloseCleanItems), + // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), + // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), + // ContextMenuItem::action( + // "Close All Items", + // CloseAllItems { save_intent: None }, + // ), + // ] + // } else { + // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. + // vec![ + // ContextMenuItem::handler("Close Inactive Item", { + // let pane = target_pane.clone(); + // move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_item_by_id( + // target_item_id, + // SaveIntent::Close, + // cx, + // ) + // .detach_and_log_err(cx); + // }) + // } + // } + // }), + // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), + // ContextMenuItem::action("Close Clean Items", CloseCleanItems), + // ContextMenuItem::handler("Close Items To The Left", { + // let pane = target_pane.clone(); + // move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_items_to_the_left_by_id(target_item_id, cx) + // .detach_and_log_err(cx); + // }) + // } + // } + // }), + // ContextMenuItem::handler("Close Items To The Right", { + // let pane = target_pane.clone(); + // move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_items_to_the_right_by_id(target_item_id, cx) + // .detach_and_log_err(cx); + // }) + // } + // } + // }), + // ContextMenuItem::action( + // "Close All Items", + // CloseAllItems { save_intent: None }, + // ), + // ] + // }, + // cx, + // ); + // }); + // } + + // pub fn toolbar(&self) -> &ViewHandle { + // &self.toolbar + // } + + // pub fn handle_deleted_project_item( + // &mut self, + // entry_id: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> Option<()> { + // let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { + // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + // Some((i, item.id())) + // } else { + // None + // } + // })?; + + // self.remove_item(item_index_to_delete, false, cx); + // self.nav_history.remove_item(item_id); + + // Some(()) + // } + + // fn update_toolbar(&mut self, cx: &mut ViewContext) { + // let active_item = self + // .items + // .get(self.active_item_index) + // .map(|item| item.as_ref()); + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.set_active_item(active_item, cx); + // }); + // } + + // fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { + // let theme = theme::current(cx).clone(); + + // let pane = cx.handle().downgrade(); + // let autoscroll = if mem::take(&mut self.autoscroll) { + // Some(self.active_item_index) + // } else { + // None + // }; + + // let pane_active = self.has_focus; + + // enum Tabs {} + // let mut row = Flex::row().scrollable::(1, autoscroll, cx); + // for (ix, (item, detail)) in self + // .items + // .iter() + // .cloned() + // .zip(self.tab_details(cx)) + // .enumerate() + // { + // let git_status = item + // .project_path(cx) + // .and_then(|path| self.project.read(cx).entry_for_path(&path, cx)) + // .and_then(|entry| entry.git_status()); + + // let detail = if detail == 0 { None } else { Some(detail) }; + // let tab_active = ix == self.active_item_index; + + // row.add_child({ + // enum TabDragReceiver {} + // let mut receiver = + // dragged_item_receiver::(self, ix, ix, true, None, cx, { + // let item = item.clone(); + // let pane = pane.clone(); + // let detail = detail.clone(); + + // let theme = theme::current(cx).clone(); + // let mut tooltip_theme = theme.tooltip.clone(); + // tooltip_theme.max_text_width = None; + // let tab_tooltip_text = + // item.tab_tooltip_text(cx).map(|text| text.into_owned()); + + // let mut tab_style = theme + // .workspace + // .tab_bar + // .tab_style(pane_active, tab_active) + // .clone(); + // let should_show_status = settings::get::(cx).git_status; + // if should_show_status && git_status != None { + // tab_style.label.text.color = match git_status.unwrap() { + // GitFileStatus::Added => tab_style.git.inserted, + // GitFileStatus::Modified => tab_style.git.modified, + // GitFileStatus::Conflict => tab_style.git.conflict, + // }; + // } + + // move |mouse_state, cx| { + // let hovered = mouse_state.hovered(); + + // enum Tab {} + // let mouse_event_handler = + // MouseEventHandler::new::(ix, cx, |_, cx| { + // Self::render_tab( + // &item, + // pane.clone(), + // ix == 0, + // detail, + // hovered, + // &tab_style, + // cx, + // ) + // }) + // .on_down(MouseButton::Left, move |_, this, cx| { + // this.activate_item(ix, true, true, cx); + // }) + // .on_click(MouseButton::Middle, { + // let item_id = item.id(); + // move |_, pane, cx| { + // pane.close_item_by_id(item_id, SaveIntent::Close, cx) + // .detach_and_log_err(cx); + // } + // }) + // .on_down( + // MouseButton::Right, + // move |event, pane, cx| { + // pane.deploy_tab_context_menu(event.position, item.id(), cx); + // }, + // ); + + // if let Some(tab_tooltip_text) = tab_tooltip_text { + // mouse_event_handler + // .with_tooltip::( + // ix, + // tab_tooltip_text, + // None, + // tooltip_theme, + // cx, + // ) + // .into_any() + // } else { + // mouse_event_handler.into_any() + // } + // } + // }); + + // if !pane_active || !tab_active { + // receiver = receiver.with_cursor_style(CursorStyle::PointingHand); + // } + + // receiver.as_draggable( + // DraggedItem { + // handle: item, + // pane: pane.clone(), + // }, + // { + // let theme = theme::current(cx).clone(); + + // let detail = detail.clone(); + // move |_, dragged_item: &DraggedItem, cx: &mut ViewContext| { + // let tab_style = &theme.workspace.tab_bar.dragged_tab; + // Self::render_dragged_tab( + // &dragged_item.handle, + // dragged_item.pane.clone(), + // false, + // detail, + // false, + // &tab_style, + // cx, + // ) + // } + // }, + // ) + // }) + // } + + // // Use the inactive tab style along with the current pane's active status to decide how to render + // // the filler + // let filler_index = self.items.len(); + // let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); + // enum Filler {} + // row.add_child( + // dragged_item_receiver::(self, 0, filler_index, true, None, cx, |_, _| { + // Empty::new() + // .contained() + // .with_style(filler_style.container) + // .with_border(filler_style.container.border) + // }) + // .flex(1., true) + // .into_any_named("filler"), + // ); + + // row + // } + + // fn tab_details(&self, cx: &AppContext) -> Vec { + // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); + + // let mut tab_descriptions = HashMap::default(); + // let mut done = false; + // while !done { + // done = true; + + // // Store item indices by their tab description. + // for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { + // if let Some(description) = item.tab_description(*detail, cx) { + // if *detail == 0 + // || Some(&description) != item.tab_description(detail - 1, cx).as_ref() + // { + // tab_descriptions + // .entry(description) + // .or_insert(Vec::new()) + // .push(ix); + // } + // } + // } + + // // If two or more items have the same tab description, increase their level + // // of detail and try again. + // for (_, item_ixs) in tab_descriptions.drain() { + // if item_ixs.len() > 1 { + // done = false; + // for ix in item_ixs { + // tab_details[ix] += 1; + // } + // } + // } + // } + + // tab_details + // } + + // fn render_tab( + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // detail: Option, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let title = item.tab_content(detail, &tab_style, cx); + // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) + // } + + // fn render_dragged_tab( + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // detail: Option, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let title = item.dragged_tab_content(detail, &tab_style, cx); + // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx) + // } + + // fn render_tab_with_title( + // title: AnyElement, + // item: &Box, + // pane: WeakViewHandle, + // first: bool, + // hovered: bool, + // tab_style: &theme::Tab, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let mut container = tab_style.container.clone(); + // if first { + // container.border.left = false; + // } + + // let buffer_jewel_element = { + // let diameter = 7.0; + // let icon_color = if item.has_conflict(cx) { + // Some(tab_style.icon_conflict) + // } else if item.is_dirty(cx) { + // Some(tab_style.icon_dirty) + // } else { + // None + // }; + + // Canvas::new(move |bounds, _, _, cx| { + // if let Some(color) = icon_color { + // let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); + // cx.scene().push_quad(Quad { + // bounds: square, + // background: Some(color), + // border: Default::default(), + // corner_radii: (diameter / 2.).into(), + // }); + // } + // }) + // .constrained() + // .with_width(diameter) + // .with_height(diameter) + // .aligned() + // }; + + // let title_element = title.aligned().contained().with_style(ContainerStyle { + // margin: Margin { + // left: tab_style.spacing, + // right: tab_style.spacing, + // ..Default::default() + // }, + // ..Default::default() + // }); + + // let close_element = if hovered { + // let item_id = item.id(); + // enum TabCloseButton {} + // let icon = Svg::new("icons/x.svg"); + // MouseEventHandler::new::(item_id, cx, |mouse_state, _| { + // if mouse_state.hovered() { + // icon.with_color(tab_style.icon_close_active) + // } else { + // icon.with_color(tab_style.icon_close) + // } + // }) + // .with_padding(Padding::uniform(4.)) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, { + // let pane = pane.clone(); + // move |_, _, cx| { + // let pane = pane.clone(); + // cx.window_context().defer(move |cx| { + // if let Some(pane) = pane.upgrade(cx) { + // pane.update(cx, |pane, cx| { + // pane.close_item_by_id(item_id, SaveIntent::Close, cx) + // .detach_and_log_err(cx); + // }); + // } + // }); + // } + // }) + // .into_any_named("close-tab-icon") + // .constrained() + // } else { + // Empty::new().constrained() + // } + // .with_width(tab_style.close_icon_width) + // .aligned(); + + // let close_right = settings::get::(cx).close_position.right(); + + // if close_right { + // Flex::row() + // .with_child(buffer_jewel_element) + // .with_child(title_element) + // .with_child(close_element) + // } else { + // Flex::row() + // .with_child(close_element) + // .with_child(title_element) + // .with_child(buffer_jewel_element) + // } + // .contained() + // .with_style(container) + // .constrained() + // .with_height(tab_style.height) + // .into_any() + // } + + // pub fn render_tab_bar_button< + // F1: 'static + Fn(&mut Pane, &mut EventContext), + // F2: 'static + Fn(&mut Pane, &mut EventContext), + // >( + // index: usize, + // icon: &'static str, + // is_active: bool, + // tooltip: Option<(&'static str, Option>)>, + // cx: &mut ViewContext, + // on_click: F1, + // on_down: F2, + // context_menu: Option>, + // ) -> AnyElement { + // enum TabBarButton {} + + // let mut button = MouseEventHandler::new::(index, cx, |mouse_state, cx| { + // let theme = &settings2::get::(cx).theme.workspace.tab_bar; + // let style = theme.pane_button.in_state(is_active).style_for(mouse_state); + // Svg::new(icon) + // .with_color(style.color) + // .constrained() + // .with_width(style.icon_width) + // .aligned() + // .constrained() + // .with_width(style.button_width) + // .with_height(style.button_width) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) + // .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) + // .into_any(); + // if let Some((tooltip, action)) = tooltip { + // let tooltip_style = settings::get::(cx).theme.tooltip.clone(); + // button = button + // .with_tooltip::(index, tooltip, action, tooltip_style, cx) + // .into_any(); + // } + + // Stack::new() + // .with_child(button) + // .with_children( + // context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()), + // ) + // .flex(1., false) + // .into_any_named("tab bar button") + // } + + // fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext) -> AnyElement { + // let background = theme.workspace.background; + // Empty::new() + // .contained() + // .with_background_color(background) + // .into_any() + // } + + // pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + // self.zoomed = zoomed; + // cx.notify(); + // } + + // pub fn is_zoomed(&self) -> bool { + // self.zoomed + // } + // } + + // impl Entity for Pane { + // type Event = Event; + // } + + // impl View for Pane { + // fn ui_name() -> &'static str { + // "Pane" + // } + + // fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + // enum MouseNavigationHandler {} + + // MouseEventHandler::new::(0, cx, |_, cx| { + // let active_item_index = self.active_item_index; + + // if let Some(active_item) = self.active_item() { + // Flex::column() + // .with_child({ + // let theme = theme::current(cx).clone(); + + // let mut stack = Stack::new(); + + // enum TabBarEventHandler {} + // stack.add_child( + // MouseEventHandler::new::(0, cx, |_, _| { + // Empty::new() + // .contained() + // .with_style(theme.workspace.tab_bar.container) + // }) + // .on_down( + // MouseButton::Left, + // move |_, this, cx| { + // this.activate_item(active_item_index, true, true, cx); + // }, + // ), + // ); + // let tooltip_style = theme.tooltip.clone(); + // let tab_bar_theme = theme.workspace.tab_bar.clone(); + + // let nav_button_height = tab_bar_theme.height; + // let button_style = tab_bar_theme.nav_button; + // let border_for_nav_buttons = tab_bar_theme + // .tab_style(false, false) + // .container + // .border + // .clone(); + + // let mut tab_row = Flex::row() + // .with_child(nav_button( + // "icons/arrow_left.svg", + // button_style.clone(), + // nav_button_height, + // tooltip_style.clone(), + // self.can_navigate_backward(), + // { + // move |pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace + // .go_back(pane, cx) + // .detach_and_log_err(cx) + // }) + // }) + // } + // } + // }, + // super::GoBack, + // "Go Back", + // cx, + // )) + // .with_child( + // nav_button( + // "icons/arrow_right.svg", + // button_style.clone(), + // nav_button_height, + // tooltip_style, + // self.can_navigate_forward(), + // { + // move |pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace + // .go_forward(pane, cx) + // .detach_and_log_err(cx) + // }) + // }) + // } + // } + // }, + // super::GoForward, + // "Go Forward", + // cx, + // ) + // .contained() + // .with_border(border_for_nav_buttons), + // ) + // .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); + + // if self.has_focus { + // let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); + // tab_row.add_child( + // (render_tab_bar_buttons)(self, cx) + // .contained() + // .with_style(theme.workspace.tab_bar.pane_button_container) + // .flex(1., false) + // .into_any(), + // ) + // } + + // stack.add_child(tab_row); + // stack + // .constrained() + // .with_height(theme.workspace.tab_bar.height) + // .flex(1., false) + // .into_any_named("tab bar") + // }) + // .with_child({ + // enum PaneContentTabDropTarget {} + // dragged_item_receiver::( + // self, + // 0, + // self.active_item_index + 1, + // !self.can_split, + // if self.can_split { Some(100.) } else { None }, + // cx, + // { + // let toolbar = self.toolbar.clone(); + // let toolbar_hidden = toolbar.read(cx).hidden(); + // move |_, cx| { + // Flex::column() + // .with_children( + // (!toolbar_hidden) + // .then(|| ChildView::new(&toolbar, cx).expanded()), + // ) + // .with_child( + // ChildView::new(active_item.as_any(), cx).flex(1., true), + // ) + // } + // }, + // ) + // .flex(1., true) + // }) + // .with_child(ChildView::new(&self.tab_context_menu, cx)) + // .into_any() + // } else { + // enum EmptyPane {} + // let theme = theme::current(cx).clone(); + + // dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { + // self.render_blank_pane(&theme, cx) + // }) + // .on_down(MouseButton::Left, |_, _, cx| { + // cx.focus_parent(); + // }) + // .into_any() + // } + // }) + // .on_down( + // MouseButton::Navigate(NavigationDirection::Back), + // move |_, pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace.go_back(pane, cx).detach_and_log_err(cx) + // }) + // }) + // } + // }, + // ) + // .on_down(MouseButton::Navigate(NavigationDirection::Forward), { + // move |_, pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace.go_forward(pane, cx).detach_and_log_err(cx) + // }) + // }) + // } + // } + // }) + // .into_any_named("pane") + // } + + // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + // if !self.has_focus { + // self.has_focus = true; + // cx.emit(Event::Focus); + // cx.notify(); + // } + + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.focus_changed(true, cx); + // }); + + // if let Some(active_item) = self.active_item() { + // if cx.is_self_focused() { + // // Pane was focused directly. We need to either focus a view inside the active item, + // // or focus the active item itself + // if let Some(weak_last_focused_view) = + // self.last_focused_view_by_item.get(&active_item.id()) + // { + // if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) { + // cx.focus(&last_focused_view); + // return; + // } else { + // self.last_focused_view_by_item.remove(&active_item.id()); + // } + // } + + // cx.focus(active_item.as_any()); + // } else if focused != self.tab_bar_context_menu.handle { + // self.last_focused_view_by_item + // .insert(active_item.id(), focused.downgrade()); + // } + // } + // } + + // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + // self.has_focus = false; + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.focus_changed(false, cx); + // }); + // cx.notify(); + // } + + // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { + // Self::reset_to_default_keymap_context(keymap); + // } + // } + + // impl ItemNavHistory { + // pub fn push(&mut self, data: Option, cx: &mut WindowContext) { + // self.history.push(data, self.item.clone(), cx); + // } + + // pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { + // self.history.pop(NavigationMode::GoingBack, cx) + // } + + // pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { + // self.history.pop(NavigationMode::GoingForward, cx) + // } + // } + + // impl NavHistory { + // pub fn for_each_entry( + // &self, + // cx: &AppContext, + // mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), + // ) { + // let borrowed_history = self.0.borrow(); + // borrowed_history + // .forward_stack + // .iter() + // .chain(borrowed_history.backward_stack.iter()) + // .chain(borrowed_history.closed_stack.iter()) + // .for_each(|entry| { + // if let Some(project_and_abs_path) = + // borrowed_history.paths_by_item.get(&entry.item.id()) + // { + // f(entry, project_and_abs_path.clone()); + // } else if let Some(item) = entry.item.upgrade(cx) { + // if let Some(path) = item.project_path(cx) { + // f(entry, (path, None)); + // } + // } + // }) + // } + + // pub fn set_mode(&mut self, mode: NavigationMode) { + // self.0.borrow_mut().mode = mode; + // } pub fn mode(&self) -> NavigationMode { self.0.borrow().mode } - pub fn disable(&mut self) { - self.0.borrow_mut().mode = NavigationMode::Disabled; - } + // pub fn disable(&mut self) { + // self.0.borrow_mut().mode = NavigationMode::Disabled; + // } - pub fn enable(&mut self) { - self.0.borrow_mut().mode = NavigationMode::Normal; - } + // pub fn enable(&mut self) { + // self.0.borrow_mut().mode = NavigationMode::Normal; + // } - pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { - let mut state = self.0.borrow_mut(); - let entry = match mode { - NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { - return None - } - NavigationMode::GoingBack => &mut state.backward_stack, - NavigationMode::GoingForward => &mut state.forward_stack, - NavigationMode::ReopeningClosedItem => &mut state.closed_stack, - } - .pop_back(); - if entry.is_some() { - state.did_update(cx); - } - entry - } + // pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { + // let mut state = self.0.borrow_mut(); + // let entry = match mode { + // NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { + // return None + // } + // NavigationMode::GoingBack => &mut state.backward_stack, + // NavigationMode::GoingForward => &mut state.forward_stack, + // NavigationMode::ReopeningClosedItem => &mut state.closed_stack, + // } + // .pop_back(); + // if entry.is_some() { + // state.did_update(cx); + // } + // entry + // } - pub fn push( - &mut self, - data: Option, - item: Rc, - cx: &mut WindowContext, - ) { - let state = &mut *self.0.borrow_mut(); - match state.mode { - NavigationMode::Disabled => {} - NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { - if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.backward_stack.pop_front(); - } - state.backward_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - state.forward_stack.clear(); - } - NavigationMode::GoingBack => { - if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.forward_stack.pop_front(); - } - state.forward_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - } - NavigationMode::GoingForward => { - if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.backward_stack.pop_front(); - } - state.backward_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - } - NavigationMode::ClosingItem => { - if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - state.closed_stack.pop_front(); - } - state.closed_stack.push_back(NavigationEntry { - item, - data: data.map(|data| Box::new(data) as Box), - timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - }); - } - } - state.did_update(cx); - } + // pub fn push( + // &mut self, + // data: Option, + // item: Rc, + // cx: &mut WindowContext, + // ) { + // let state = &mut *self.0.borrow_mut(); + // match state.mode { + // NavigationMode::Disabled => {} + // NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { + // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.backward_stack.pop_front(); + // } + // state.backward_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // state.forward_stack.clear(); + // } + // NavigationMode::GoingBack => { + // if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.forward_stack.pop_front(); + // } + // state.forward_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // } + // NavigationMode::GoingForward => { + // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.backward_stack.pop_front(); + // } + // state.backward_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // } + // NavigationMode::ClosingItem => { + // if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + // state.closed_stack.pop_front(); + // } + // state.closed_stack.push_back(NavigationEntry { + // item, + // data: data.map(|data| Box::new(data) as Box), + // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + // }); + // } + // } + // state.did_update(cx); + // } - pub fn remove_item(&mut self, item_id: usize) { - let mut state = self.0.borrow_mut(); - state.paths_by_item.remove(&item_id); - state - .backward_stack - .retain(|entry| entry.item.id() != item_id); - state - .forward_stack - .retain(|entry| entry.item.id() != item_id); - state - .closed_stack - .retain(|entry| entry.item.id() != item_id); - } + // pub fn remove_item(&mut self, item_id: usize) { + // let mut state = self.0.borrow_mut(); + // state.paths_by_item.remove(&item_id); + // state + // .backward_stack + // .retain(|entry| entry.item.id() != item_id); + // state + // .forward_stack + // .retain(|entry| entry.item.id() != item_id); + // state + // .closed_stack + // .retain(|entry| entry.item.id() != item_id); + // } - pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { - self.0.borrow().paths_by_item.get(&item_id).cloned() - } + // pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { + // self.0.borrow().paths_by_item.get(&item_id).cloned() + // } } -impl NavHistoryState { - pub fn did_update(&self, cx: &mut WindowContext) { - if let Some(pane) = self.pane.upgrade(cx) { - cx.defer(move |cx| { - pane.update(cx, |pane, cx| pane.history_updated(cx)); - }); - } - } -} - -pub struct PaneBackdrop { - child_view: usize, - child: AnyElement, -} - -impl PaneBackdrop { - pub fn new(pane_item_view: usize, child: AnyElement) -> Self { - PaneBackdrop { - child, - child_view: pane_item_view, - } - } -} - -impl Element for PaneBackdrop { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState) { - let size = self.child.layout(constraint, view, cx); - (size, ()) - } - - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - _: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) -> Self::PaintState { - let background = theme::current(cx).editor.background; - - let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - - cx.scene().push_quad(gpui::Quad { - bounds: RectF::new(bounds.origin(), bounds.size()), - background: Some(background), - ..Default::default() - }); - - let child_view_id = self.child_view; - cx.scene().push_mouse_region( - MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( - gpui::platform::MouseButton::Left, - move |_, _: &mut V, cx| { - let window = cx.window(); - cx.app_context().focus(window, Some(child_view_id)) - }, - ), - ); - - cx.scene().push_layer(Some(bounds)); - self.child.paint(bounds.origin(), visible_bounds, view, cx); - cx.scene().pop_layer(); - } - - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - _bounds: RectF, - _visible_bounds: RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _bounds: RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> serde_json::Value { - gpui::json::json!({ - "type": "Pane Back Drop", - "view": self.child_view, - "child": self.child.debug(view, cx), - }) - } -} - -fn dirty_message_for(buffer_path: Option) -> String { - let path = buffer_path - .as_ref() - .and_then(|p| p.path.to_str()) - .unwrap_or(&"This buffer"); - let path = truncate_and_remove_front(path, 80); - format!("{path} contains unsaved edits. Do you want to save it?") -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::item::test::{TestItem, TestProjectItem}; - use gpui::TestAppContext; - use project::FakeFs; - use settings::SettingsStore; - - #[gpui::test] - async fn test_remove_active_empty(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - pane.update(cx, |pane, cx| { - assert!(pane - .close_active_item(&CloseActiveItem { save_intent: None }, cx) - .is_none()) - }); - } - - #[gpui::test] - async fn test_add_item_with_new_item(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - // 1. Add with a destination index - // a. Add before the active item - set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - Some(0), - cx, - ); - }); - assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); - - // b. Add after the active item - set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - Some(2), - cx, - ); - }); - assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); - - // c. Add at the end of the item list (including off the length) - set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - Some(5), - cx, - ); - }); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - - // 2. Add without a destination index - // a. Add with active item at the start of the item list - set_labeled_items(&pane, ["A*", "B", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - None, - cx, - ); - }); - set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); - - // b. Add with active item at the end of the item list - set_labeled_items(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| { - pane.add_item( - Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), - false, - false, - None, - cx, - ); - }); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - } - - #[gpui::test] - async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - // 1. Add with a destination index - // 1a. Add before the active item - let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(d, false, false, Some(0), cx); - }); - assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); - - // 1b. Add after the active item - let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(d, false, false, Some(2), cx); - }); - assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); - - // 1c. Add at the end of the item list (including off the length) - let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(a, false, false, Some(5), cx); - }); - assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); - - // 1d. Add same item to active index - let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(b, false, false, Some(1), cx); - }); - assert_item_labels(&pane, ["A", "B*", "C"], cx); - - // 1e. Add item to index after same item in last position - let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(c, false, false, Some(2), cx); - }); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - - // 2. Add without a destination index - // 2a. Add with active item at the start of the item list - let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(d, false, false, None, cx); - }); - assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); - - // 2b. Add with active item at the end of the item list - let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(a, false, false, None, cx); - }); - assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); - - // 2c. Add active item to active item at end of list - let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(c, false, false, None, cx); - }); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - - // 2d. Add active item to active item at start of list - let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); - pane.update(cx, |pane, cx| { - pane.add_item(a, false, false, None, cx); - }); - assert_item_labels(&pane, ["A*", "B", "C"], cx); - } - - #[gpui::test] - async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - // singleton view - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(true) - .with_label("buffer 1") - .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1*"], cx); - - // new singleton view with the same project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(true) - .with_label("buffer 1") - .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1*"], cx); - - // new singleton view with different project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(true) - .with_label("buffer 2") - .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); - - // new multibuffer view with the same project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(false) - .with_label("multibuffer 1") - .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); - - // another multibuffer view with the same project entry - pane.update(cx, |pane, cx| { - let item = TestItem::new() - .with_singleton(false) - .with_label("multibuffer 1b") - .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); - - pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); - }); - assert_item_labels( - &pane, - ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], - cx, - ); - } - - #[gpui::test] - async fn test_remove_item_ordering(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - add_labeled_item(&pane, "A", false, cx); - add_labeled_item(&pane, "B", false, cx); - add_labeled_item(&pane, "C", false, cx); - add_labeled_item(&pane, "D", false, cx); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - - pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx)); - add_labeled_item(&pane, "1", false, cx); - assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); - - pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); - assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "B*", "C"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "C*"], cx); - - pane.update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A*"], cx); - } - - #[gpui::test] - async fn test_close_inactive_items(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - - pane.update(cx, |pane, cx| { - pane.close_inactive_items(&CloseInactiveItems, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["C*"], cx); - } - - #[gpui::test] - async fn test_close_clean_items(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - add_labeled_item(&pane, "A", true, cx); - add_labeled_item(&pane, "B", false, cx); - add_labeled_item(&pane, "C", true, cx); - add_labeled_item(&pane, "D", false, cx); - add_labeled_item(&pane, "E", false, cx); - assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - - pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A^", "C*^"], cx); - } - - #[gpui::test] - async fn test_close_items_to_the_left(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - - pane.update(cx, |pane, cx| { - pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["C*", "D", "E"], cx); - } - - #[gpui::test] - async fn test_close_items_to_the_right(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - - pane.update(cx, |pane, cx| { - pane.close_items_to_the_right(&CloseItemsToTheRight, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - } - - #[gpui::test] - async fn test_close_all_items(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.background()); - - let project = Project::test(fs, None, cx).await; - let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let workspace = window.root(cx); - let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); - - add_labeled_item(&pane, "A", false, cx); - add_labeled_item(&pane, "B", false, cx); - add_labeled_item(&pane, "C", false, cx); - assert_item_labels(&pane, ["A", "B", "C*"], cx); - - pane.update(cx, |pane, cx| { - pane.close_all_items(&CloseAllItems { save_intent: None }, cx) - }) - .unwrap() - .await - .unwrap(); - assert_item_labels(&pane, [], cx); - - add_labeled_item(&pane, "A", true, cx); - add_labeled_item(&pane, "B", true, cx); - add_labeled_item(&pane, "C", true, cx); - assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); - - let save = pane - .update(cx, |pane, cx| { - pane.close_all_items(&CloseAllItems { save_intent: None }, cx) - }) - .unwrap(); - - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - save.await.unwrap(); - assert_item_labels(&pane, [], cx); - } - - fn init_test(cx: &mut TestAppContext) { - cx.update(|cx| { - cx.set_global(SettingsStore::test(cx)); - theme::init((), cx); - crate::init_settings(cx); - Project::init_settings(cx); - }); - } - - fn add_labeled_item( - pane: &ViewHandle, - label: &str, - is_dirty: bool, - cx: &mut TestAppContext, - ) -> Box> { - pane.update(cx, |pane, cx| { - let labeled_item = - Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty))); - pane.add_item(labeled_item.clone(), false, false, None, cx); - labeled_item - }) - } - - fn set_labeled_items( - pane: &ViewHandle, - labels: [&str; COUNT], - cx: &mut TestAppContext, - ) -> [Box>; COUNT] { - pane.update(cx, |pane, cx| { - pane.items.clear(); - let mut active_item_index = 0; - - let mut index = 0; - let items = labels.map(|mut label| { - if label.ends_with("*") { - label = label.trim_end_matches("*"); - active_item_index = index; - } - - let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); - pane.add_item(labeled_item.clone(), false, false, None, cx); - index += 1; - labeled_item - }); - - pane.activate_item(active_item_index, false, false, cx); - - items - }) - } - - // Assert the item label, with the active item label suffixed with a '*' - fn assert_item_labels( - pane: &ViewHandle, - expected_states: [&str; COUNT], - cx: &mut TestAppContext, - ) { - pane.read_with(cx, |pane, cx| { - let actual_states = pane - .items - .iter() - .enumerate() - .map(|(ix, item)| { - let mut state = item - .as_any() - .downcast_ref::() - .unwrap() - .read(cx) - .label - .clone(); - if ix == pane.active_item_index { - state.push('*'); - } - if item.is_dirty(cx) { - state.push('^'); - } - state - }) - .collect::>(); - - assert_eq!( - actual_states, expected_states, - "pane items do not match expectation" - ); - }) - } -} +// impl NavHistoryState { +// pub fn did_update(&self, cx: &mut WindowContext) { +// if let Some(pane) = self.pane.upgrade(cx) { +// cx.defer(move |cx| { +// pane.update(cx, |pane, cx| pane.history_updated(cx)); +// }); +// } +// } +// } + +// pub struct PaneBackdrop { +// child_view: usize, +// child: AnyElement, +// } + +// impl PaneBackdrop { +// pub fn new(pane_item_view: usize, child: AnyElement) -> Self { +// PaneBackdrop { +// child, +// child_view: pane_item_view, +// } +// } +// } + +// impl Element for PaneBackdrop { +// type LayoutState = (); + +// type PaintState = (); + +// fn layout( +// &mut self, +// constraint: gpui::SizeConstraint, +// view: &mut V, +// cx: &mut ViewContext, +// ) -> (Vector2F, Self::LayoutState) { +// let size = self.child.layout(constraint, view, cx); +// (size, ()) +// } + +// fn paint( +// &mut self, +// bounds: RectF, +// visible_bounds: RectF, +// _: &mut Self::LayoutState, +// view: &mut V, +// cx: &mut ViewContext, +// ) -> Self::PaintState { +// let background = theme::current(cx).editor.background; + +// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + +// cx.scene().push_quad(gpui::Quad { +// bounds: RectF::new(bounds.origin(), bounds.size()), +// background: Some(background), +// ..Default::default() +// }); + +// let child_view_id = self.child_view; +// cx.scene().push_mouse_region( +// MouseRegion::new::(child_view_id, 0, visible_bounds).on_down( +// gpui::platform::MouseButton::Left, +// move |_, _: &mut V, cx| { +// let window = cx.window(); +// cx.app_context().focus(window, Some(child_view_id)) +// }, +// ), +// ); + +// cx.scene().push_layer(Some(bounds)); +// self.child.paint(bounds.origin(), visible_bounds, view, cx); +// cx.scene().pop_layer(); +// } + +// fn rect_for_text_range( +// &self, +// range_utf16: std::ops::Range, +// _bounds: RectF, +// _visible_bounds: RectF, +// _layout: &Self::LayoutState, +// _paint: &Self::PaintState, +// view: &V, +// cx: &gpui::ViewContext, +// ) -> Option { +// self.child.rect_for_text_range(range_utf16, view, cx) +// } + +// fn debug( +// &self, +// _bounds: RectF, +// _layout: &Self::LayoutState, +// _paint: &Self::PaintState, +// view: &V, +// cx: &gpui::ViewContext, +// ) -> serde_json::Value { +// gpui::json::json!({ +// "type": "Pane Back Drop", +// "view": self.child_view, +// "child": self.child.debug(view, cx), +// }) +// } +// } + +// fn dirty_message_for(buffer_path: Option) -> String { +// let path = buffer_path +// .as_ref() +// .and_then(|p| p.path.to_str()) +// .unwrap_or(&"This buffer"); +// let path = truncate_and_remove_front(path, 80); +// format!("{path} contains unsaved edits. Do you want to save it?") +// } + +// todo!("uncomment tests") +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::item::test::{TestItem, TestProjectItem}; +// use gpui::TestAppContext; +// use project::FakeFs; +// use settings::SettingsStore; + +// #[gpui::test] +// async fn test_remove_active_empty(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// pane.update(cx, |pane, cx| { +// assert!(pane +// .close_active_item(&CloseActiveItem { save_intent: None }, cx) +// .is_none()) +// }); +// } + +// #[gpui::test] +// async fn test_add_item_with_new_item(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// // 1. Add with a destination index +// // a. Add before the active item +// set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// Some(0), +// cx, +// ); +// }); +// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + +// // b. Add after the active item +// set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// Some(2), +// cx, +// ); +// }); +// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + +// // c. Add at the end of the item list (including off the length) +// set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// Some(5), +// cx, +// ); +// }); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + +// // 2. Add without a destination index +// // a. Add with active item at the start of the item list +// set_labeled_items(&pane, ["A*", "B", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// None, +// cx, +// ); +// }); +// set_labeled_items(&pane, ["A", "D*", "B", "C"], cx); + +// // b. Add with active item at the end of the item list +// set_labeled_items(&pane, ["A", "B", "C*"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item( +// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), +// false, +// false, +// None, +// cx, +// ); +// }); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); +// } + +// #[gpui::test] +// async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// // 1. Add with a destination index +// // 1a. Add before the active item +// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(d, false, false, Some(0), cx); +// }); +// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + +// // 1b. Add after the active item +// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(d, false, false, Some(2), cx); +// }); +// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + +// // 1c. Add at the end of the item list (including off the length) +// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(a, false, false, Some(5), cx); +// }); +// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + +// // 1d. Add same item to active index +// let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(b, false, false, Some(1), cx); +// }); +// assert_item_labels(&pane, ["A", "B*", "C"], cx); + +// // 1e. Add item to index after same item in last position +// let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(c, false, false, Some(2), cx); +// }); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); + +// // 2. Add without a destination index +// // 2a. Add with active item at the start of the item list +// let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(d, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); + +// // 2b. Add with active item at the end of the item list +// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(a, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + +// // 2c. Add active item to active item at end of list +// let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(c, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); + +// // 2d. Add active item to active item at start of list +// let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx); +// pane.update(cx, |pane, cx| { +// pane.add_item(a, false, false, None, cx); +// }); +// assert_item_labels(&pane, ["A*", "B", "C"], cx); +// } + +// #[gpui::test] +// async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { +// cx.foreground().forbid_parking(); +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// // singleton view +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(true) +// .with_label("buffer 1") +// .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1*"], cx); + +// // new singleton view with the same project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(true) +// .with_label("buffer 1") +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1*"], cx); + +// // new singleton view with different project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(true) +// .with_label("buffer 2") +// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx); + +// // new multibuffer view with the same project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(false) +// .with_label("multibuffer 1") +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx); + +// // another multibuffer view with the same project entry +// pane.update(cx, |pane, cx| { +// let item = TestItem::new() +// .with_singleton(false) +// .with_label("multibuffer 1b") +// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); + +// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx); +// }); +// assert_item_labels( +// &pane, +// ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"], +// cx, +// ); +// } + +// #[gpui::test] +// async fn test_remove_item_ordering(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// add_labeled_item(&pane, "A", false, cx); +// add_labeled_item(&pane, "B", false, cx); +// add_labeled_item(&pane, "C", false, cx); +// add_labeled_item(&pane, "D", false, cx); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + +// pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx)); +// add_labeled_item(&pane, "1", false, cx); +// assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); + +// pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); +// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "B*", "C"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "C*"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A*"], cx); +// } + +// #[gpui::test] +// async fn test_close_inactive_items(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_inactive_items(&CloseInactiveItems, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["C*"], cx); +// } + +// #[gpui::test] +// async fn test_close_clean_items(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// add_labeled_item(&pane, "A", true, cx); +// add_labeled_item(&pane, "B", false, cx); +// add_labeled_item(&pane, "C", true, cx); +// add_labeled_item(&pane, "D", false, cx); +// add_labeled_item(&pane, "E", false, cx); +// assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); + +// pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A^", "C*^"], cx); +// } + +// #[gpui::test] +// async fn test_close_items_to_the_left(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["C*", "D", "E"], cx); +// } + +// #[gpui::test] +// async fn test_close_items_to_the_right(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_items_to_the_right(&CloseItemsToTheRight, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); +// } + +// #[gpui::test] +// async fn test_close_all_items(cx: &mut TestAppContext) { +// init_test(cx); +// let fs = FakeFs::new(cx.background()); + +// let project = Project::test(fs, None, cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// add_labeled_item(&pane, "A", false, cx); +// add_labeled_item(&pane, "B", false, cx); +// add_labeled_item(&pane, "C", false, cx); +// assert_item_labels(&pane, ["A", "B", "C*"], cx); + +// pane.update(cx, |pane, cx| { +// pane.close_all_items(&CloseAllItems { save_intent: None }, cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_item_labels(&pane, [], cx); + +// add_labeled_item(&pane, "A", true, cx); +// add_labeled_item(&pane, "B", true, cx); +// add_labeled_item(&pane, "C", true, cx); +// assert_item_labels(&pane, ["A^", "B^", "C*^"], cx); + +// let save = pane +// .update(cx, |pane, cx| { +// pane.close_all_items(&CloseAllItems { save_intent: None }, cx) +// }) +// .unwrap(); + +// cx.foreground().run_until_parked(); +// window.simulate_prompt_answer(2, cx); +// save.await.unwrap(); +// assert_item_labels(&pane, [], cx); +// } + +// fn init_test(cx: &mut TestAppContext) { +// cx.update(|cx| { +// cx.set_global(SettingsStore::test(cx)); +// theme::init((), cx); +// crate::init_settings(cx); +// Project::init_settings(cx); +// }); +// } + +// fn add_labeled_item( +// pane: &ViewHandle, +// label: &str, +// is_dirty: bool, +// cx: &mut TestAppContext, +// ) -> Box> { +// pane.update(cx, |pane, cx| { +// let labeled_item = +// Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty))); +// pane.add_item(labeled_item.clone(), false, false, None, cx); +// labeled_item +// }) +// } + +// fn set_labeled_items( +// pane: &ViewHandle, +// labels: [&str; COUNT], +// cx: &mut TestAppContext, +// ) -> [Box>; COUNT] { +// pane.update(cx, |pane, cx| { +// pane.items.clear(); +// let mut active_item_index = 0; + +// let mut index = 0; +// let items = labels.map(|mut label| { +// if label.ends_with("*") { +// label = label.trim_end_matches("*"); +// active_item_index = index; +// } + +// let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); +// pane.add_item(labeled_item.clone(), false, false, None, cx); +// index += 1; +// labeled_item +// }); + +// pane.activate_item(active_item_index, false, false, cx); + +// items +// }) +// } + +// // Assert the item label, with the active item label suffixed with a '*' +// fn assert_item_labels( +// pane: &ViewHandle, +// expected_states: [&str; COUNT], +// cx: &mut TestAppContext, +// ) { +// pane.read_with(cx, |pane, cx| { +// let actual_states = pane +// .items +// .iter() +// .enumerate() +// .map(|(ix, item)| { +// let mut state = item +// .as_any() +// .downcast_ref::() +// .unwrap() +// .read(cx) +// .label +// .clone(); +// if ix == pane.active_item_index { +// state.push('*'); +// } +// if item.is_dirty(cx) { +// state.push('^'); +// } +// state +// }) +// .collect::>(); + +// assert_eq!( +// actual_states, expected_states, +// "pane items do not match expectation" +// ); +// }) +// } +// } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index aef03dcda0..fce913128a 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,22 +1,22 @@ use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, Result}; -use call::{ActiveCall, ParticipantLocation}; +use call2::{ActiveCall, ParticipantLocation}; use collections::HashMap; -use gpui::{ - elements::*, - geometry::{rect::RectF, vector::Vector2F}, - platform::{CursorStyle, MouseButton}, - AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle, -}; -use project::Project; +use gpui2::{Bounds, Handle, Pixels, Point, View, ViewContext}; +use project2::Project; use serde::Deserialize; use std::{cell::RefCell, rc::Rc, sync::Arc}; -use theme::Theme; +use theme2::Theme; const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; +enum Axis { + Vertical, + Horizontal, +} + #[derive(Clone, Debug, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, @@ -27,7 +27,7 @@ impl PaneGroup { Self { root } } - pub fn new(pane: ViewHandle) -> Self { + pub fn new(pane: View) -> Self { Self { root: Member::Pane(pane), } @@ -35,8 +35,8 @@ impl PaneGroup { pub fn split( &mut self, - old_pane: &ViewHandle, - new_pane: &ViewHandle, + old_pane: &View, + new_pane: &View, direction: SplitDirection, ) -> Result<()> { match &mut self.root { @@ -52,14 +52,14 @@ impl PaneGroup { } } - pub fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + pub fn bounding_box_for_pane(&self, pane: &View) -> Option> { match &self.root { Member::Pane(_) => None, Member::Axis(axis) => axis.bounding_box_for_pane(pane), } } - pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + pub fn pane_at_pixel_position(&self, coordinate: Point) -> Option<&View> { match &self.root { Member::Pane(pane) => Some(pane), Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), @@ -70,7 +70,7 @@ impl PaneGroup { /// - Ok(true) if it found and removed a pane /// - Ok(false) if it found but did not remove the pane /// - Err(_) if it did not find the pane - pub fn remove(&mut self, pane: &ViewHandle) -> Result { + pub fn remove(&mut self, pane: &View) -> Result { match &mut self.root { Member::Pane(_) => Ok(false), Member::Axis(axis) => { @@ -82,7 +82,7 @@ impl PaneGroup { } } - pub fn swap(&mut self, from: &ViewHandle, to: &ViewHandle) { + pub fn swap(&mut self, from: &View, to: &View) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => axis.swap(from, to), @@ -91,11 +91,11 @@ impl PaneGroup { pub(crate) fn render( &self, - project: &ModelHandle, + project: &Handle, theme: &Theme, - follower_states: &HashMap, FollowerState>, - active_call: Option<&ModelHandle>, - active_pane: &ViewHandle, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, @@ -113,7 +113,7 @@ impl PaneGroup { ) } - pub(crate) fn panes(&self) -> Vec<&ViewHandle> { + pub(crate) fn panes(&self) -> Vec<&View> { let mut panes = Vec::new(); self.root.collect_panes(&mut panes); panes @@ -123,15 +123,11 @@ impl PaneGroup { #[derive(Clone, Debug, PartialEq)] pub(crate) enum Member { Axis(PaneAxis), - Pane(ViewHandle), + Pane(View), } impl Member { - fn new_axis( - old_pane: ViewHandle, - new_pane: ViewHandle, - direction: SplitDirection, - ) -> Self { + fn new_axis(old_pane: View, new_pane: View, direction: SplitDirection) -> Self { use Axis::*; use SplitDirection::*; @@ -148,7 +144,7 @@ impl Member { Member::Axis(PaneAxis::new(axis, members)) } - fn contains(&self, needle: &ViewHandle) -> bool { + fn contains(&self, needle: &View) -> bool { match self { Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)), Member::Pane(pane) => pane == needle, @@ -157,12 +153,12 @@ impl Member { pub fn render( &self, - project: &ModelHandle, + project: &Handle, basis: usize, theme: &Theme, - follower_states: &HashMap, FollowerState>, - active_call: Option<&ModelHandle>, - active_pane: &ViewHandle, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, @@ -295,7 +291,7 @@ impl Member { } } - fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle>) { + fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View>) { match self { Member::Axis(axis) => { for member in &axis.members { @@ -343,8 +339,8 @@ impl PaneAxis { fn split( &mut self, - old_pane: &ViewHandle, - new_pane: &ViewHandle, + old_pane: &View, + new_pane: &View, direction: SplitDirection, ) -> Result<()> { for (mut idx, member) in self.members.iter_mut().enumerate() { @@ -375,7 +371,7 @@ impl PaneAxis { Err(anyhow!("Pane not found")) } - fn remove(&mut self, pane_to_remove: &ViewHandle) -> Result> { + fn remove(&mut self, pane_to_remove: &View) -> Result> { let mut found_pane = false; let mut remove_member = None; for (idx, member) in self.members.iter_mut().enumerate() { @@ -417,7 +413,7 @@ impl PaneAxis { } } - fn swap(&mut self, from: &ViewHandle, to: &ViewHandle) { + fn swap(&mut self, from: &View, to: &View) { for member in self.members.iter_mut() { match member { Member::Axis(axis) => axis.swap(from, to), @@ -432,7 +428,7 @@ impl PaneAxis { } } - fn bounding_box_for_pane(&self, pane: &ViewHandle) -> Option { + fn bounding_box_for_pane(&self, pane: &View) -> Option { debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); for (idx, member) in self.members.iter().enumerate() { @@ -452,7 +448,7 @@ impl PaneAxis { None } - fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle> { + fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&View> { debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); let bounding_boxes = self.bounding_boxes.borrow(); @@ -472,12 +468,12 @@ impl PaneAxis { fn render( &self, - project: &ModelHandle, + project: &Handle, basis: usize, theme: &Theme, - follower_states: &HashMap, FollowerState>, - active_call: Option<&ModelHandle>, - active_pane: &ViewHandle, + follower_states: &HashMap, FollowerState>, + active_call: Option<&Handle>, + active_pane: &View, zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index ddde5c3554..591fab5cd9 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -1,12 +1,12 @@ use std::{any::Any, sync::Arc}; -use gpui::{ - AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, -}; -use project::search::SearchQuery; +use gpui2::{AppContext, Subscription, Task, View, ViewContext, WindowContext}; +use project2::search::SearchQuery; -use crate::{item::WeakItemHandle, Item, ItemHandle}; +use crate::{ + item::{Item, WeakItemHandle}, + ItemHandle, +}; #[derive(Debug)] pub enum SearchEvent { @@ -128,7 +128,7 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; } -impl SearchableItemHandle for ViewHandle { +impl SearchableItemHandle for View { fn downgrade(&self) -> Box { Box::new(self.downgrade()) } @@ -231,17 +231,19 @@ fn downcast_matches(matches: &Vec>) -> Vec> for AnyViewHandle { - fn from(this: Box) -> Self { - this.as_any().clone() - } -} +// todo!() +// impl From> for AnyViewHandle { +// fn from(this: Box) -> Self { +// this.as_any().clone() +// } +// } -impl From<&Box> for AnyViewHandle { - fn from(this: &Box) -> Self { - this.as_any().clone() - } -} +// todo!() +// impl From<&Box> for AnyViewHandle { +// fn from(this: &Box) -> Self { +// this.as_any().clone() +// } +// } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { @@ -254,18 +256,20 @@ impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; - fn into_any(self) -> AnyWeakViewHandle; + // todo!() + // fn into_any(self) -> AnyWeakViewHandle; } -impl WeakSearchableItemHandle for WeakViewHandle { - fn upgrade(&self, cx: &AppContext) -> Option> { - Some(Box::new(self.upgrade(cx)?)) - } +// todo!() +// impl WeakSearchableItemHandle for WeakViewHandle { +// fn upgrade(&self, cx: &AppContext) -> Option> { +// Some(Box::new(self.upgrade(cx)?)) +// } - fn into_any(self) -> AnyWeakViewHandle { - self.into_any() - } -} +// fn into_any(self) -> AnyWeakViewHandle { +// self.into_any() +// } +// } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 06f6d65e2e..3436a6805e 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,16 +1,16 @@ // pub mod dock; pub mod item; // pub mod notifications; -// pub mod pane; -// pub mod pane_group; +pub mod pane; +pub mod pane_group; // mod persistence; -// pub mod searchable; +pub mod searchable; // pub mod shared_screen; // mod status_bar; -// mod toolbar; +mod toolbar; // mod workspace_settings; -// use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; // use call2::ActiveCall; // use client2::{ // proto::{self, PeerId}, @@ -52,8 +52,8 @@ pub mod item; // use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; // use lazy_static::lazy_static; // use notifications::{NotificationHandle, NotifyResultExt}; -// pub use pane::*; -// pub use pane_group::*; +pub use pane::*; +pub use pane_group::*; // use persistence::{model::SerializedItem, DB}; // pub use persistence::{ // model::{ItemId, WorkspaceLocation}, @@ -66,7 +66,7 @@ pub mod item; // use status_bar::StatusBar; // pub use status_bar::StatusItemView; // use theme::{Theme, ThemeSettings}; -// pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; +pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; // use util::ResultExt; // pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings}; @@ -234,7 +234,7 @@ pub mod item; // ] // ); -// pub type WorkspaceId = i64; +pub type WorkspaceId = i64; // pub fn init_settings(cx: &mut AppContext) { // settings::register::(cx); @@ -365,18 +365,16 @@ pub mod item; // }); // } -// type ProjectItemBuilders = HashMap< -// TypeId, -// fn(ModelHandle, AnyModelHandle, &mut ViewContext) -> Box, -// >; -// pub fn register_project_item(cx: &mut AppContext) { -// cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { -// builders.insert(TypeId::of::(), |project, model, cx| { -// let item = model.downcast::().unwrap(); -// Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) -// }); -// }); -// } +type ProjectItemBuilders = + HashMap, AnyHandle, &mut ViewContext) -> Box>; +pub fn register_project_item(cx: &mut AppContext) { + cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { + builders.insert(TypeId::of::(), |project, model, cx| { + let item = model.downcast::().unwrap(); + Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) + }); + }); +} // type FollowableItemBuilder = fn( // ViewHandle, @@ -555,10 +553,10 @@ pub struct Workspace { // left_dock: ViewHandle, // bottom_dock: ViewHandle, // right_dock: ViewHandle, - // panes: Vec>, + panes: Vec>, // panes_by_item: HashMap>, // active_pane: ViewHandle, - // last_active_center_pane: Option>, + last_active_center_pane: Option>, // last_active_view_id: Option, // status_bar: ViewHandle, // titlebar_item: Option, @@ -570,7 +568,7 @@ pub struct Workspace { // active_call: Option<(ModelHandle, Vec)>, // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, // database_id: WorkspaceId, - // app_state: Arc, + app_state: Arc, // subscriptions: Vec, // _apply_leader_updates: Task>, // _observe_current_user: Task>, @@ -589,3156 +587,3158 @@ pub struct Workspace { // pub id: u64, // } -// #[derive(Default)] -// struct FollowerState { -// leader_id: PeerId, -// active_view_id: Option, -// items_by_leader_view_id: HashMap>, -// } +#[derive(Default)] +struct FollowerState { + leader_id: PeerId, + active_view_id: Option, + items_by_leader_view_id: HashMap>, +} // enum WorkspaceBounds {} -// impl Workspace { -// pub fn new( -// workspace_id: WorkspaceId, -// project: ModelHandle, -// app_state: Arc, -// cx: &mut ViewContext, -// ) -> Self { -// cx.observe(&project, |_, _, cx| cx.notify()).detach(); -// cx.subscribe(&project, move |this, _, event, cx| { -// match event { -// project::Event::RemoteIdChanged(_) => { -// this.update_window_title(cx); -// } - -// project::Event::CollaboratorLeft(peer_id) => { -// this.collaborator_left(*peer_id, cx); -// } - -// project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { -// this.update_window_title(cx); -// this.serialize_workspace(cx); -// } - -// project::Event::DisconnectedFromHost => { -// this.update_window_edited(cx); -// cx.blur(); -// } - -// project::Event::Closed => { -// cx.remove_window(); -// } - -// project::Event::DeletedEntry(entry_id) => { -// for pane in this.panes.iter() { -// pane.update(cx, |pane, cx| { -// pane.handle_deleted_project_item(*entry_id, cx) -// }); -// } -// } - -// project::Event::Notification(message) => this.show_notification(0, cx, |cx| { -// cx.add_view(|_| MessageNotification::new(message.clone())) -// }), - -// _ => {} -// } -// cx.notify() -// }) -// .detach(); - -// let weak_handle = cx.weak_handle(); -// let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); - -// let center_pane = cx.add_view(|cx| { -// Pane::new( -// weak_handle.clone(), -// project.clone(), -// pane_history_timestamp.clone(), -// cx, -// ) -// }); -// cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); -// cx.focus(¢er_pane); -// cx.emit(Event::PaneAdded(center_pane.clone())); - -// app_state.workspace_store.update(cx, |store, _| { -// store.workspaces.insert(weak_handle.clone()); -// }); - -// let mut current_user = app_state.user_store.read(cx).watch_current_user(); -// let mut connection_status = app_state.client.status(); -// let _observe_current_user = cx.spawn(|this, mut cx| async move { -// current_user.recv().await; -// connection_status.recv().await; -// let mut stream = -// Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); - -// while stream.recv().await.is_some() { -// this.update(&mut cx, |_, cx| cx.notify())?; -// } -// anyhow::Ok(()) -// }); - -// // All leader updates are enqueued and then processed in a single task, so -// // that each asynchronous operation can be run in order. -// let (leader_updates_tx, mut leader_updates_rx) = -// mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); -// let _apply_leader_updates = cx.spawn(|this, mut cx| async move { -// while let Some((leader_id, update)) = leader_updates_rx.next().await { -// Self::process_leader_update(&this, leader_id, update, &mut cx) -// .await -// .log_err(); -// } - -// Ok(()) -// }); - -// cx.emit_global(WorkspaceCreated(weak_handle.clone())); - -// let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); -// let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); -// let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); -// let left_dock_buttons = -// cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); -// let bottom_dock_buttons = -// cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); -// let right_dock_buttons = -// cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); -// let status_bar = cx.add_view(|cx| { -// let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); -// status_bar.add_left_item(left_dock_buttons, cx); -// status_bar.add_right_item(right_dock_buttons, cx); -// status_bar.add_right_item(bottom_dock_buttons, cx); -// status_bar -// }); - -// cx.update_default_global::, _, _>(|drag_and_drop, _| { -// drag_and_drop.register_container(weak_handle.clone()); -// }); - -// let mut active_call = None; -// if cx.has_global::>() { -// let call = cx.global::>().clone(); -// let mut subscriptions = Vec::new(); -// subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); -// active_call = Some((call, subscriptions)); -// } - -// let subscriptions = vec![ -// cx.observe_fullscreen(|_, _, cx| cx.notify()), -// cx.observe_window_activation(Self::on_window_activation_changed), -// cx.observe_window_bounds(move |_, mut bounds, display, cx| { -// // Transform fixed bounds to be stored in terms of the containing display -// if let WindowBounds::Fixed(mut window_bounds) = bounds { -// if let Some(screen) = cx.platform().screen_by_id(display) { -// let screen_bounds = screen.bounds(); -// window_bounds -// .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); -// window_bounds -// .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); -// bounds = WindowBounds::Fixed(window_bounds); -// } -// } - -// cx.background() -// .spawn(DB.set_window_bounds(workspace_id, bounds, display)) -// .detach_and_log_err(cx); -// }), -// cx.observe(&left_dock, |this, _, cx| { -// this.serialize_workspace(cx); -// cx.notify(); -// }), -// cx.observe(&bottom_dock, |this, _, cx| { -// this.serialize_workspace(cx); -// cx.notify(); -// }), -// cx.observe(&right_dock, |this, _, cx| { -// this.serialize_workspace(cx); -// cx.notify(); -// }), -// ]; - -// cx.defer(|this, cx| this.update_window_title(cx)); -// Workspace { -// weak_self: weak_handle.clone(), -// modal: None, -// zoomed: None, -// zoomed_position: None, -// center: PaneGroup::new(center_pane.clone()), -// panes: vec![center_pane.clone()], -// panes_by_item: Default::default(), -// active_pane: center_pane.clone(), -// last_active_center_pane: Some(center_pane.downgrade()), -// last_active_view_id: None, -// status_bar, -// titlebar_item: None, -// notifications: Default::default(), -// left_dock, -// bottom_dock, -// right_dock, -// project: project.clone(), -// follower_states: Default::default(), -// last_leaders_by_pane: Default::default(), -// window_edited: false, -// active_call, -// database_id: workspace_id, -// app_state, -// _observe_current_user, -// _apply_leader_updates, -// _schedule_serialize: None, -// leader_updates_tx, -// subscriptions, -// pane_history_timestamp, -// } -// } - -// fn new_local( -// abs_paths: Vec, -// app_state: Arc, -// requesting_window: Option>, -// cx: &mut AppContext, -// ) -> Task<( -// WeakViewHandle, -// Vec, anyhow::Error>>>, -// )> { -// let project_handle = Project::local( -// app_state.client.clone(), -// app_state.node_runtime.clone(), -// app_state.user_store.clone(), -// app_state.languages.clone(), -// app_state.fs.clone(), -// cx, -// ); - -// cx.spawn(|mut cx| async move { -// let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); - -// let paths_to_open = Arc::new(abs_paths); - -// // Get project paths for all of the abs_paths -// let mut worktree_roots: HashSet> = Default::default(); -// let mut project_paths: Vec<(PathBuf, Option)> = -// Vec::with_capacity(paths_to_open.len()); -// for path in paths_to_open.iter().cloned() { -// if let Some((worktree, project_entry)) = cx -// .update(|cx| { -// Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) -// }) -// .await -// .log_err() -// { -// worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); -// project_paths.push((path, Some(project_entry))); -// } else { -// project_paths.push((path, None)); -// } -// } - -// let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { -// serialized_workspace.id -// } else { -// DB.next_id().await.unwrap_or(0) -// }; - -// let window = if let Some(window) = requesting_window { -// window.replace_root(&mut cx, |cx| { -// Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) -// }); -// window -// } else { -// { -// let window_bounds_override = window_bounds_env_override(&cx); -// let (bounds, display) = if let Some(bounds) = window_bounds_override { -// (Some(bounds), None) -// } else { -// serialized_workspace -// .as_ref() -// .and_then(|serialized_workspace| { -// let display = serialized_workspace.display?; -// let mut bounds = serialized_workspace.bounds?; - -// // Stored bounds are relative to the containing display. -// // So convert back to global coordinates if that screen still exists -// if let WindowBounds::Fixed(mut window_bounds) = bounds { -// if let Some(screen) = cx.platform().screen_by_id(display) { -// let screen_bounds = screen.bounds(); -// window_bounds.set_origin_x( -// window_bounds.origin_x() + screen_bounds.origin_x(), -// ); -// window_bounds.set_origin_y( -// window_bounds.origin_y() + screen_bounds.origin_y(), -// ); -// bounds = WindowBounds::Fixed(window_bounds); -// } else { -// // Screen no longer exists. Return none here. -// return None; -// } -// } - -// Some((bounds, display)) -// }) -// .unzip() -// }; - -// // Use the serialized workspace to construct the new window -// cx.add_window( -// (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), -// |cx| { -// Workspace::new( -// workspace_id, -// project_handle.clone(), -// app_state.clone(), -// cx, -// ) -// }, -// ) -// } -// }; - -// // We haven't yielded the main thread since obtaining the window handle, -// // so the window exists. -// let workspace = window.root(&cx).unwrap(); - -// (app_state.initialize_workspace)( -// workspace.downgrade(), -// serialized_workspace.is_some(), -// app_state.clone(), -// cx.clone(), -// ) -// .await -// .log_err(); - -// window.update(&mut cx, |cx| cx.activate_window()); - -// let workspace = workspace.downgrade(); -// notify_if_database_failed(&workspace, &mut cx); -// let opened_items = open_items( -// serialized_workspace, -// &workspace, -// project_paths, -// app_state, -// cx, -// ) -// .await -// .unwrap_or_default(); - -// (workspace, opened_items) -// }) -// } - -// pub fn weak_handle(&self) -> WeakViewHandle { -// self.weak_self.clone() -// } - -// pub fn left_dock(&self) -> &ViewHandle { -// &self.left_dock -// } - -// pub fn bottom_dock(&self) -> &ViewHandle { -// &self.bottom_dock -// } - -// pub fn right_dock(&self) -> &ViewHandle { -// &self.right_dock -// } - -// pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) -// where -// T::Event: std::fmt::Debug, -// { -// self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) -// } - -// pub fn add_panel_with_extra_event_handler( -// &mut self, -// panel: ViewHandle, -// cx: &mut ViewContext, -// handler: F, -// ) where -// T::Event: std::fmt::Debug, -// F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, -// { -// let dock = match panel.position(cx) { -// DockPosition::Left => &self.left_dock, -// DockPosition::Bottom => &self.bottom_dock, -// DockPosition::Right => &self.right_dock, -// }; - -// self.subscriptions.push(cx.subscribe(&panel, { -// let mut dock = dock.clone(); -// let mut prev_position = panel.position(cx); -// move |this, panel, event, cx| { -// if T::should_change_position_on_event(event) { -// let new_position = panel.read(cx).position(cx); -// let mut was_visible = false; -// dock.update(cx, |dock, cx| { -// prev_position = new_position; - -// was_visible = dock.is_open() -// && dock -// .visible_panel() -// .map_or(false, |active_panel| active_panel.id() == panel.id()); -// dock.remove_panel(&panel, cx); -// }); - -// if panel.is_zoomed(cx) { -// this.zoomed_position = Some(new_position); -// } - -// dock = match panel.read(cx).position(cx) { -// DockPosition::Left => &this.left_dock, -// DockPosition::Bottom => &this.bottom_dock, -// DockPosition::Right => &this.right_dock, -// } -// .clone(); -// dock.update(cx, |dock, cx| { -// dock.add_panel(panel.clone(), cx); -// if was_visible { -// dock.set_open(true, cx); -// dock.activate_panel(dock.panels_len() - 1, cx); -// } -// }); -// } else if T::should_zoom_in_on_event(event) { -// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); -// if !panel.has_focus(cx) { -// cx.focus(&panel); -// } -// this.zoomed = Some(panel.downgrade().into_any()); -// this.zoomed_position = Some(panel.read(cx).position(cx)); -// } else if T::should_zoom_out_on_event(event) { -// dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); -// if this.zoomed_position == Some(prev_position) { -// this.zoomed = None; -// this.zoomed_position = None; -// } -// cx.notify(); -// } else if T::is_focus_event(event) { -// let position = panel.read(cx).position(cx); -// this.dismiss_zoomed_items_to_reveal(Some(position), cx); -// if panel.is_zoomed(cx) { -// this.zoomed = Some(panel.downgrade().into_any()); -// this.zoomed_position = Some(position); -// } else { -// this.zoomed = None; -// this.zoomed_position = None; -// } -// this.update_active_view_for_followers(cx); -// cx.notify(); -// } else { -// handler(this, &panel, event, cx) -// } -// } -// })); - -// dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); -// } - -// pub fn status_bar(&self) -> &ViewHandle { -// &self.status_bar -// } - -// pub fn app_state(&self) -> &Arc { -// &self.app_state -// } - -// pub fn user_store(&self) -> &ModelHandle { -// &self.app_state.user_store -// } - -// pub fn project(&self) -> &ModelHandle { -// &self.project -// } - -// pub fn recent_navigation_history( -// &self, -// limit: Option, -// cx: &AppContext, -// ) -> Vec<(ProjectPath, Option)> { -// let mut abs_paths_opened: HashMap> = HashMap::default(); -// let mut history: HashMap, usize)> = HashMap::default(); -// for pane in &self.panes { -// let pane = pane.read(cx); -// pane.nav_history() -// .for_each_entry(cx, |entry, (project_path, fs_path)| { -// if let Some(fs_path) = &fs_path { -// abs_paths_opened -// .entry(fs_path.clone()) -// .or_default() -// .insert(project_path.clone()); -// } -// let timestamp = entry.timestamp; -// match history.entry(project_path) { -// hash_map::Entry::Occupied(mut entry) => { -// let (_, old_timestamp) = entry.get(); -// if ×tamp > old_timestamp { -// entry.insert((fs_path, timestamp)); -// } -// } -// hash_map::Entry::Vacant(entry) => { -// entry.insert((fs_path, timestamp)); -// } -// } -// }); -// } - -// history -// .into_iter() -// .sorted_by_key(|(_, (_, timestamp))| *timestamp) -// .map(|(project_path, (fs_path, _))| (project_path, fs_path)) -// .rev() -// .filter(|(history_path, abs_path)| { -// let latest_project_path_opened = abs_path -// .as_ref() -// .and_then(|abs_path| abs_paths_opened.get(abs_path)) -// .and_then(|project_paths| { -// project_paths -// .iter() -// .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) -// }); - -// match latest_project_path_opened { -// Some(latest_project_path_opened) => latest_project_path_opened == history_path, -// None => true, -// } -// }) -// .take(limit.unwrap_or(usize::MAX)) -// .collect() -// } - -// fn navigate_history( -// &mut self, -// pane: WeakViewHandle, -// mode: NavigationMode, -// cx: &mut ViewContext, -// ) -> Task> { -// let to_load = if let Some(pane) = pane.upgrade(cx) { -// cx.focus(&pane); - -// pane.update(cx, |pane, cx| { -// loop { -// // Retrieve the weak item handle from the history. -// let entry = pane.nav_history_mut().pop(mode, cx)?; - -// // If the item is still present in this pane, then activate it. -// if let Some(index) = entry -// .item -// .upgrade(cx) -// .and_then(|v| pane.index_for_item(v.as_ref())) -// { -// let prev_active_item_index = pane.active_item_index(); -// pane.nav_history_mut().set_mode(mode); -// pane.activate_item(index, true, true, cx); -// pane.nav_history_mut().set_mode(NavigationMode::Normal); - -// let mut navigated = prev_active_item_index != pane.active_item_index(); -// if let Some(data) = entry.data { -// navigated |= pane.active_item()?.navigate(data, cx); -// } - -// if navigated { -// break None; -// } -// } -// // If the item is no longer present in this pane, then retrieve its -// // project path in order to reopen it. -// else { -// break pane -// .nav_history() -// .path_for_item(entry.item.id()) -// .map(|(project_path, _)| (project_path, entry)); -// } -// } -// }) -// } else { -// None -// }; - -// if let Some((project_path, entry)) = to_load { -// // If the item was no longer present, then load it again from its previous path. -// let task = self.load_path(project_path, cx); -// cx.spawn(|workspace, mut cx| async move { -// let task = task.await; -// let mut navigated = false; -// if let Some((project_entry_id, build_item)) = task.log_err() { -// let prev_active_item_id = pane.update(&mut cx, |pane, _| { -// pane.nav_history_mut().set_mode(mode); -// pane.active_item().map(|p| p.id()) -// })?; - -// pane.update(&mut cx, |pane, cx| { -// let item = pane.open_item(project_entry_id, true, cx, build_item); -// navigated |= Some(item.id()) != prev_active_item_id; -// pane.nav_history_mut().set_mode(NavigationMode::Normal); -// if let Some(data) = entry.data { -// navigated |= item.navigate(data, cx); -// } -// })?; -// } - -// if !navigated { -// workspace -// .update(&mut cx, |workspace, cx| { -// Self::navigate_history(workspace, pane, mode, cx) -// })? -// .await?; -// } - -// Ok(()) -// }) -// } else { -// Task::ready(Ok(())) -// } -// } - -// pub fn go_back( -// &mut self, -// pane: WeakViewHandle, -// cx: &mut ViewContext, -// ) -> Task> { -// self.navigate_history(pane, NavigationMode::GoingBack, cx) -// } - -// pub fn go_forward( -// &mut self, -// pane: WeakViewHandle, -// cx: &mut ViewContext, -// ) -> Task> { -// self.navigate_history(pane, NavigationMode::GoingForward, cx) -// } - -// pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { -// self.navigate_history( -// self.active_pane().downgrade(), -// NavigationMode::ReopeningClosedItem, -// cx, -// ) -// } - -// pub fn client(&self) -> &Client { -// &self.app_state.client -// } - -// pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext) { -// self.titlebar_item = Some(item); -// cx.notify(); -// } - -// pub fn titlebar_item(&self) -> Option { -// self.titlebar_item.clone() -// } - -// /// Call the given callback with a workspace whose project is local. -// /// -// /// If the given workspace has a local project, then it will be passed -// /// to the callback. Otherwise, a new empty window will be created. -// pub fn with_local_workspace( -// &mut self, -// cx: &mut ViewContext, -// callback: F, -// ) -> Task> -// where -// T: 'static, -// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, -// { -// if self.project.read(cx).is_local() { -// Task::Ready(Some(Ok(callback(self, cx)))) -// } else { -// let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); -// cx.spawn(|_vh, mut cx| async move { -// let (workspace, _) = task.await; -// workspace.update(&mut cx, callback) -// }) -// } -// } - -// pub fn worktrees<'a>( -// &self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.project.read(cx).worktrees(cx) -// } - -// pub fn visible_worktrees<'a>( -// &self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.project.read(cx).visible_worktrees(cx) -// } - -// pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { -// let futures = self -// .worktrees(cx) -// .filter_map(|worktree| worktree.read(cx).as_local()) -// .map(|worktree| worktree.scan_complete()) -// .collect::>(); -// async move { -// for future in futures { -// future.await; -// } -// } -// } - -// pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { -// cx.spawn(|mut cx| async move { -// let window = cx -// .windows() -// .into_iter() -// .find(|window| window.is_active(&cx).unwrap_or(false)); -// if let Some(window) = window { -// //This can only get called when the window's project connection has been lost -// //so we don't need to prompt the user for anything and instead just close the window -// window.remove(&mut cx); -// } -// }) -// .detach(); -// } - -// pub fn close( -// &mut self, -// _: &CloseWindow, -// cx: &mut ViewContext, -// ) -> Option>> { -// let window = cx.window(); -// let prepare = self.prepare_to_close(false, cx); -// Some(cx.spawn(|_, mut cx| async move { -// if prepare.await? { -// window.remove(&mut cx); -// } -// Ok(()) -// })) -// } - -// pub fn prepare_to_close( -// &mut self, -// quitting: bool, -// cx: &mut ViewContext, -// ) -> Task> { -// let active_call = self.active_call().cloned(); -// let window = cx.window(); - -// cx.spawn(|this, mut cx| async move { -// let workspace_count = cx -// .windows() -// .into_iter() -// .filter(|window| window.root_is::()) -// .count(); - -// if let Some(active_call) = active_call { -// if !quitting -// && workspace_count == 1 -// && active_call.read_with(&cx, |call, _| call.room().is_some()) -// { -// let answer = window.prompt( -// PromptLevel::Warning, -// "Do you want to leave the current call?", -// &["Close window and hang up", "Cancel"], -// &mut cx, -// ); - -// if let Some(mut answer) = answer { -// if answer.next().await == Some(1) { -// return anyhow::Ok(false); -// } else { -// active_call -// .update(&mut cx, |call, cx| call.hang_up(cx)) -// .await -// .log_err(); -// } -// } -// } -// } - -// Ok(this -// .update(&mut cx, |this, cx| { -// this.save_all_internal(SaveIntent::Close, cx) -// })? -// .await?) -// }) -// } - -// fn save_all( -// &mut self, -// action: &SaveAll, -// cx: &mut ViewContext, -// ) -> Option>> { -// let save_all = -// self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx); -// Some(cx.foreground().spawn(async move { -// save_all.await?; -// Ok(()) -// })) -// } - -// fn save_all_internal( -// &mut self, -// mut save_intent: SaveIntent, -// cx: &mut ViewContext, -// ) -> Task> { -// if self.project.read(cx).is_read_only() { -// return Task::ready(Ok(true)); -// } -// let dirty_items = self -// .panes -// .iter() -// .flat_map(|pane| { -// pane.read(cx).items().filter_map(|item| { -// if item.is_dirty(cx) { -// Some((pane.downgrade(), item.boxed_clone())) -// } else { -// None -// } -// }) -// }) -// .collect::>(); - -// let project = self.project.clone(); -// cx.spawn(|workspace, mut cx| async move { -// // Override save mode and display "Save all files" prompt -// if save_intent == SaveIntent::Close && dirty_items.len() > 1 { -// let mut answer = workspace.update(&mut cx, |_, cx| { -// let prompt = Pane::file_names_for_prompt( -// &mut dirty_items.iter().map(|(_, handle)| handle), -// dirty_items.len(), -// cx, -// ); -// cx.prompt( -// PromptLevel::Warning, -// &prompt, -// &["Save all", "Discard all", "Cancel"], -// ) -// })?; -// match answer.next().await { -// Some(0) => save_intent = SaveIntent::SaveAll, -// Some(1) => save_intent = SaveIntent::Skip, -// _ => {} -// } -// } -// for (pane, item) in dirty_items { -// let (singleton, project_entry_ids) = -// cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); -// if singleton || !project_entry_ids.is_empty() { -// if let Some(ix) = -// pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))? -// { -// if !Pane::save_item( -// project.clone(), -// &pane, -// ix, -// &*item, -// save_intent, -// &mut cx, -// ) -// .await? -// { -// return Ok(false); -// } -// } -// } -// } -// Ok(true) -// }) -// } - -// pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { -// let mut paths = cx.prompt_for_paths(PathPromptOptions { -// files: true, -// directories: true, -// multiple: true, -// }); - -// Some(cx.spawn(|this, mut cx| async move { -// if let Some(paths) = paths.recv().await.flatten() { -// if let Some(task) = this -// .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) -// .log_err() -// { -// task.await? -// } -// } -// Ok(()) -// })) -// } - -// pub fn open_workspace_for_paths( -// &mut self, -// paths: Vec, -// cx: &mut ViewContext, -// ) -> Task> { -// let window = cx.window().downcast::(); -// let is_remote = self.project.read(cx).is_remote(); -// let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); -// let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); -// let close_task = if is_remote || has_worktree || has_dirty_items { -// None -// } else { -// Some(self.prepare_to_close(false, cx)) -// }; -// let app_state = self.app_state.clone(); - -// cx.spawn(|_, mut cx| async move { -// let window_to_replace = if let Some(close_task) = close_task { -// if !close_task.await? { -// return Ok(()); -// } -// window -// } else { -// None -// }; -// cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx)) -// .await?; -// Ok(()) -// }) -// } - -// #[allow(clippy::type_complexity)] -// pub fn open_paths( -// &mut self, -// mut abs_paths: Vec, -// visible: bool, -// cx: &mut ViewContext, -// ) -> Task, anyhow::Error>>>> { -// log::info!("open paths {:?}", abs_paths); - -// let fs = self.app_state.fs.clone(); - -// // Sort the paths to ensure we add worktrees for parents before their children. -// abs_paths.sort_unstable(); -// cx.spawn(|this, mut cx| async move { -// let mut tasks = Vec::with_capacity(abs_paths.len()); -// for abs_path in &abs_paths { -// let project_path = match this -// .update(&mut cx, |this, cx| { -// Workspace::project_path_for_path( -// this.project.clone(), -// abs_path, -// visible, -// cx, -// ) -// }) -// .log_err() -// { -// Some(project_path) => project_path.await.log_err(), -// None => None, -// }; - -// let this = this.clone(); -// let task = cx.spawn(|mut cx| { -// let fs = fs.clone(); -// let abs_path = abs_path.clone(); -// async move { -// let (worktree, project_path) = project_path?; -// if fs.is_file(&abs_path).await { -// Some( -// this.update(&mut cx, |this, cx| { -// this.open_path(project_path, None, true, cx) -// }) -// .log_err()? -// .await, -// ) -// } else { -// this.update(&mut cx, |workspace, cx| { -// let worktree = worktree.read(cx); -// let worktree_abs_path = worktree.abs_path(); -// let entry_id = if abs_path == worktree_abs_path.as_ref() { -// worktree.root_entry() -// } else { -// abs_path -// .strip_prefix(worktree_abs_path.as_ref()) -// .ok() -// .and_then(|relative_path| { -// worktree.entry_for_path(relative_path) -// }) -// } -// .map(|entry| entry.id); -// if let Some(entry_id) = entry_id { -// workspace.project().update(cx, |_, cx| { -// cx.emit(project::Event::ActiveEntryChanged(Some(entry_id))); -// }) -// } -// }) -// .log_err()?; -// None -// } -// } -// }); -// tasks.push(task); -// } - -// futures::future::join_all(tasks).await -// }) -// } - -// fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { -// let mut paths = cx.prompt_for_paths(PathPromptOptions { -// files: false, -// directories: true, -// multiple: true, -// }); -// cx.spawn(|this, mut cx| async move { -// if let Some(paths) = paths.recv().await.flatten() { -// let results = this -// .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? -// .await; -// for result in results.into_iter().flatten() { -// result.log_err(); -// } -// } -// anyhow::Ok(()) -// }) -// .detach_and_log_err(cx); -// } - -// fn project_path_for_path( -// project: ModelHandle, -// abs_path: &Path, -// visible: bool, -// cx: &mut AppContext, -// ) -> Task, ProjectPath)>> { -// let entry = project.update(cx, |project, cx| { -// project.find_or_create_local_worktree(abs_path, visible, cx) -// }); -// cx.spawn(|cx| async move { -// let (worktree, path) = entry.await?; -// let worktree_id = worktree.read_with(&cx, |t, _| t.id()); -// Ok(( -// worktree, -// ProjectPath { -// worktree_id, -// path: path.into(), -// }, -// )) -// }) -// } - -// /// Returns the modal that was toggled closed if it was open. -// pub fn toggle_modal( -// &mut self, -// cx: &mut ViewContext, -// add_view: F, -// ) -> Option> -// where -// V: 'static + Modal, -// F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, -// { -// cx.notify(); -// // Whatever modal was visible is getting clobbered. If its the same type as V, then return -// // it. Otherwise, create a new modal and set it as active. -// if let Some(already_open_modal) = self -// .dismiss_modal(cx) -// .and_then(|modal| modal.downcast::()) -// { -// cx.focus_self(); -// Some(already_open_modal) -// } else { -// let modal = add_view(self, cx); -// cx.subscribe(&modal, |this, _, event, cx| { -// if V::dismiss_on_event(event) { -// this.dismiss_modal(cx); -// } -// }) -// .detach(); -// let previously_focused_view_id = cx.focused_view_id(); -// cx.focus(&modal); -// self.modal = Some(ActiveModal { -// view: Box::new(modal), -// previously_focused_view_id, -// }); -// None -// } -// } - -// pub fn modal(&self) -> Option> { -// self.modal -// .as_ref() -// .and_then(|modal| modal.view.as_any().clone().downcast::()) -// } - -// pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { -// if let Some(modal) = self.modal.take() { -// if let Some(previously_focused_view_id) = modal.previously_focused_view_id { -// if modal.view.has_focus(cx) { -// cx.window_context().focus(Some(previously_focused_view_id)); -// } -// } -// cx.notify(); -// Some(modal.view.as_any().clone()) -// } else { -// None -// } -// } - -// pub fn items<'a>( -// &'a self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.panes.iter().flat_map(|pane| pane.read(cx).items()) -// } - -// pub fn item_of_type(&self, cx: &AppContext) -> Option> { -// self.items_of_type(cx).max_by_key(|item| item.id()) -// } - -// pub fn items_of_type<'a, T: Item>( -// &'a self, -// cx: &'a AppContext, -// ) -> impl 'a + Iterator> { -// self.panes -// .iter() -// .flat_map(|pane| pane.read(cx).items_of_type()) -// } - -// pub fn active_item(&self, cx: &AppContext) -> Option> { -// self.active_pane().read(cx).active_item() -// } - -// fn active_project_path(&self, cx: &ViewContext) -> Option { -// self.active_item(cx).and_then(|item| item.project_path(cx)) -// } - -// pub fn save_active_item( -// &mut self, -// save_intent: SaveIntent, -// cx: &mut ViewContext, -// ) -> Task> { -// let project = self.project.clone(); -// let pane = self.active_pane(); -// let item_ix = pane.read(cx).active_item_index(); -// let item = pane.read(cx).active_item(); -// let pane = pane.downgrade(); - -// cx.spawn(|_, mut cx| async move { -// if let Some(item) = item { -// Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx) -// .await -// .map(|_| ()) -// } else { -// Ok(()) -// } -// }) -// } - -// pub fn close_inactive_items_and_panes( -// &mut self, -// _: &CloseInactiveTabsAndPanes, -// cx: &mut ViewContext, -// ) -> Option>> { -// self.close_all_internal(true, SaveIntent::Close, cx) -// } - -// pub fn close_all_items_and_panes( -// &mut self, -// action: &CloseAllItemsAndPanes, -// cx: &mut ViewContext, -// ) -> Option>> { -// self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) -// } - -// fn close_all_internal( -// &mut self, -// retain_active_pane: bool, -// save_intent: SaveIntent, -// cx: &mut ViewContext, -// ) -> Option>> { -// let current_pane = self.active_pane(); - -// let mut tasks = Vec::new(); - -// if retain_active_pane { -// if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| { -// pane.close_inactive_items(&CloseInactiveItems, cx) -// }) { -// tasks.push(current_pane_close); -// }; -// } - -// for pane in self.panes() { -// if retain_active_pane && pane.id() == current_pane.id() { -// continue; -// } - -// if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| { -// pane.close_all_items( -// &CloseAllItems { -// save_intent: Some(save_intent), -// }, -// cx, -// ) -// }) { -// tasks.push(close_pane_items) -// } -// } - -// if tasks.is_empty() { -// None -// } else { -// Some(cx.spawn(|_, _| async move { -// for task in tasks { -// task.await? -// } -// Ok(()) -// })) -// } -// } - -// pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { -// let dock = match dock_side { -// DockPosition::Left => &self.left_dock, -// DockPosition::Bottom => &self.bottom_dock, -// DockPosition::Right => &self.right_dock, -// }; -// let mut focus_center = false; -// let mut reveal_dock = false; -// dock.update(cx, |dock, cx| { -// let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); -// let was_visible = dock.is_open() && !other_is_zoomed; -// dock.set_open(!was_visible, cx); - -// if let Some(active_panel) = dock.active_panel() { -// if was_visible { -// if active_panel.has_focus(cx) { -// focus_center = true; -// } -// } else { -// cx.focus(active_panel.as_any()); -// reveal_dock = true; -// } -// } -// }); - -// if reveal_dock { -// self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); -// } - -// if focus_center { -// cx.focus_self(); -// } - -// cx.notify(); -// self.serialize_workspace(cx); -// } - -// pub fn close_all_docks(&mut self, cx: &mut ViewContext) { -// let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; - -// for dock in docks { -// dock.update(cx, |dock, cx| { -// dock.set_open(false, cx); -// }); -// } - -// cx.focus_self(); -// cx.notify(); -// self.serialize_workspace(cx); -// } - -// /// Transfer focus to the panel of the given type. -// pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { -// self.focus_or_unfocus_panel::(cx, |_, _| true)? -// .as_any() -// .clone() -// .downcast() -// } - -// /// Focus the panel of the given type if it isn't already focused. If it is -// /// already focused, then transfer focus back to the workspace center. -// pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { -// self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); -// } - -// /// Focus or unfocus the given panel type, depending on the given callback. -// fn focus_or_unfocus_panel( -// &mut self, -// cx: &mut ViewContext, -// should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, -// ) -> Option> { -// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { -// if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { -// let mut focus_center = false; -// let mut reveal_dock = false; -// let panel = dock.update(cx, |dock, cx| { -// dock.activate_panel(panel_index, cx); - -// let panel = dock.active_panel().cloned(); -// if let Some(panel) = panel.as_ref() { -// if should_focus(&**panel, cx) { -// dock.set_open(true, cx); -// cx.focus(panel.as_any()); -// reveal_dock = true; -// } else { -// // if panel.is_zoomed(cx) { -// // dock.set_open(false, cx); -// // } -// focus_center = true; -// } -// } -// panel -// }); - -// if focus_center { -// cx.focus_self(); -// } - -// self.serialize_workspace(cx); -// cx.notify(); -// return panel; -// } -// } -// None -// } - -// pub fn panel(&self, cx: &WindowContext) -> Option> { -// for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { -// let dock = dock.read(cx); -// if let Some(panel) = dock.panel::() { -// return Some(panel); -// } -// } -// None -// } - -// fn zoom_out(&mut self, cx: &mut ViewContext) { -// for pane in &self.panes { -// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); -// } - -// self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); -// self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); -// self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); -// self.zoomed = None; -// self.zoomed_position = None; - -// cx.notify(); -// } - -// #[cfg(any(test, feature = "test-support"))] -// pub fn zoomed_view(&self, cx: &AppContext) -> Option { -// self.zoomed.and_then(|view| view.upgrade(cx)) -// } - -// fn dismiss_zoomed_items_to_reveal( -// &mut self, -// dock_to_reveal: Option, -// cx: &mut ViewContext, -// ) { -// // If a center pane is zoomed, unzoom it. -// for pane in &self.panes { -// if pane != &self.active_pane || dock_to_reveal.is_some() { -// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); -// } -// } - -// // If another dock is zoomed, hide it. -// let mut focus_center = false; -// for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { -// dock.update(cx, |dock, cx| { -// if Some(dock.position()) != dock_to_reveal { -// if let Some(panel) = dock.active_panel() { -// if panel.is_zoomed(cx) { -// focus_center |= panel.has_focus(cx); -// dock.set_open(false, cx); -// } -// } -// } -// }); -// } - -// if focus_center { -// cx.focus_self(); -// } - -// if self.zoomed_position != dock_to_reveal { -// self.zoomed = None; -// self.zoomed_position = None; -// } - -// cx.notify(); -// } - -// fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { -// let pane = cx.add_view(|cx| { -// Pane::new( -// self.weak_handle(), -// self.project.clone(), -// self.pane_history_timestamp.clone(), -// cx, -// ) -// }); -// cx.subscribe(&pane, Self::handle_pane_event).detach(); -// self.panes.push(pane.clone()); -// cx.focus(&pane); -// cx.emit(Event::PaneAdded(pane.clone())); -// pane -// } - -// pub fn add_item_to_center( -// &mut self, -// item: Box, -// cx: &mut ViewContext, -// ) -> bool { -// if let Some(center_pane) = self.last_active_center_pane.clone() { -// if let Some(center_pane) = center_pane.upgrade(cx) { -// center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); -// true -// } else { -// false -// } -// } else { -// false -// } -// } - -// pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { -// self.active_pane -// .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); -// } - -// pub fn split_item( -// &mut self, -// split_direction: SplitDirection, -// item: Box, -// cx: &mut ViewContext, -// ) { -// let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); -// new_pane.update(cx, move |new_pane, cx| { -// new_pane.add_item(item, true, true, None, cx) -// }) -// } - -// pub fn open_abs_path( -// &mut self, -// abs_path: PathBuf, -// visible: bool, -// cx: &mut ViewContext, -// ) -> Task>> { -// cx.spawn(|workspace, mut cx| async move { -// let open_paths_task_result = workspace -// .update(&mut cx, |workspace, cx| { -// workspace.open_paths(vec![abs_path.clone()], visible, cx) -// }) -// .with_context(|| format!("open abs path {abs_path:?} task spawn"))? -// .await; -// anyhow::ensure!( -// open_paths_task_result.len() == 1, -// "open abs path {abs_path:?} task returned incorrect number of results" -// ); -// match open_paths_task_result -// .into_iter() -// .next() -// .expect("ensured single task result") -// { -// Some(open_result) => { -// open_result.with_context(|| format!("open abs path {abs_path:?} task join")) -// } -// None => anyhow::bail!("open abs path {abs_path:?} task returned None"), -// } -// }) -// } - -// pub fn split_abs_path( -// &mut self, -// abs_path: PathBuf, -// visible: bool, -// cx: &mut ViewContext, -// ) -> Task>> { -// let project_path_task = -// Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); -// cx.spawn(|this, mut cx| async move { -// let (_, path) = project_path_task.await?; -// this.update(&mut cx, |this, cx| this.split_path(path, cx))? -// .await -// }) -// } - -// pub fn open_path( -// &mut self, -// path: impl Into, -// pane: Option>, -// focus_item: bool, -// cx: &mut ViewContext, -// ) -> Task, anyhow::Error>> { -// let pane = pane.unwrap_or_else(|| { -// self.last_active_center_pane.clone().unwrap_or_else(|| { -// self.panes -// .first() -// .expect("There must be an active pane") -// .downgrade() -// }) -// }); - -// let task = self.load_path(path.into(), cx); -// cx.spawn(|_, mut cx| async move { -// let (project_entry_id, build_item) = task.await?; -// pane.update(&mut cx, |pane, cx| { -// pane.open_item(project_entry_id, focus_item, cx, build_item) -// }) -// }) -// } - -// pub fn split_path( -// &mut self, -// path: impl Into, -// cx: &mut ViewContext, -// ) -> Task, anyhow::Error>> { -// let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { -// self.panes -// .first() -// .expect("There must be an active pane") -// .downgrade() -// }); - -// if let Member::Pane(center_pane) = &self.center.root { -// if center_pane.read(cx).items_len() == 0 { -// return self.open_path(path, Some(pane), true, cx); -// } -// } - -// let task = self.load_path(path.into(), cx); -// cx.spawn(|this, mut cx| async move { -// let (project_entry_id, build_item) = task.await?; -// this.update(&mut cx, move |this, cx| -> Option<_> { -// let pane = pane.upgrade(cx)?; -// let new_pane = this.split_pane(pane, SplitDirection::Right, cx); -// new_pane.update(cx, |new_pane, cx| { -// Some(new_pane.open_item(project_entry_id, true, cx, build_item)) -// }) -// }) -// .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? -// }) -// } - -// pub(crate) fn load_path( -// &mut self, -// path: ProjectPath, -// cx: &mut ViewContext, -// ) -> Task< -// Result<( -// ProjectEntryId, -// impl 'static + FnOnce(&mut ViewContext) -> Box, -// )>, -// > { -// let project = self.project().clone(); -// let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); -// cx.spawn(|_, mut cx| async move { -// let (project_entry_id, project_item) = project_item.await?; -// let build_item = cx.update(|cx| { -// cx.default_global::() -// .get(&project_item.model_type()) -// .ok_or_else(|| anyhow!("no item builder for project item")) -// .cloned() -// })?; -// let build_item = -// move |cx: &mut ViewContext| build_item(project, project_item, cx); -// Ok((project_entry_id, build_item)) -// }) -// } - -// pub fn open_project_item( -// &mut self, -// project_item: ModelHandle, -// cx: &mut ViewContext, -// ) -> ViewHandle -// where -// T: ProjectItem, -// { -// use project::Item as _; - -// let entry_id = project_item.read(cx).entry_id(cx); -// if let Some(item) = entry_id -// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) -// .and_then(|item| item.downcast()) -// { -// self.activate_item(&item, cx); -// return item; -// } - -// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); -// self.add_item(Box::new(item.clone()), cx); -// item -// } - -// pub fn split_project_item( -// &mut self, -// project_item: ModelHandle, -// cx: &mut ViewContext, -// ) -> ViewHandle -// where -// T: ProjectItem, -// { -// use project::Item as _; - -// let entry_id = project_item.read(cx).entry_id(cx); -// if let Some(item) = entry_id -// .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) -// .and_then(|item| item.downcast()) -// { -// self.activate_item(&item, cx); -// return item; -// } - -// let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); -// self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); -// item -// } - -// pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { -// if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { -// self.active_pane.update(cx, |pane, cx| { -// pane.add_item(Box::new(shared_screen), false, true, None, cx) -// }); -// } -// } - -// pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { -// let result = self.panes.iter().find_map(|pane| { -// pane.read(cx) -// .index_for_item(item) -// .map(|ix| (pane.clone(), ix)) -// }); -// if let Some((pane, ix)) = result { -// pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); -// true -// } else { -// false -// } -// } - -// fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { -// let panes = self.center.panes(); -// if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { -// cx.focus(&pane); -// } else { -// self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); -// } -// } - -// pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { -// let panes = self.center.panes(); -// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { -// let next_ix = (ix + 1) % panes.len(); -// let next_pane = panes[next_ix].clone(); -// cx.focus(&next_pane); -// } -// } - -// pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { -// let panes = self.center.panes(); -// if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { -// let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); -// let prev_pane = panes[prev_ix].clone(); -// cx.focus(&prev_pane); -// } -// } - -// pub fn activate_pane_in_direction( -// &mut self, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) { -// if let Some(pane) = self.find_pane_in_direction(direction, cx) { -// cx.focus(pane); -// } -// } - -// pub fn swap_pane_in_direction( -// &mut self, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) { -// if let Some(to) = self -// .find_pane_in_direction(direction, cx) -// .map(|pane| pane.clone()) -// { -// self.center.swap(&self.active_pane.clone(), &to); -// cx.notify(); -// } -// } - -// fn find_pane_in_direction( -// &mut self, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) -> Option<&ViewHandle> { -// let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { -// return None; -// }; -// let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); -// let center = match cursor { -// Some(cursor) if bounding_box.contains_point(cursor) => cursor, -// _ => bounding_box.center(), -// }; - -// let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; - -// let target = match direction { -// SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), -// SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), -// SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), -// SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), -// }; -// self.center.pane_at_pixel_position(target) -// } - -// fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { -// if self.active_pane != pane { -// self.active_pane = pane.clone(); -// self.status_bar.update(cx, |status_bar, cx| { -// status_bar.set_active_pane(&self.active_pane, cx); -// }); -// self.active_item_path_changed(cx); -// self.last_active_center_pane = Some(pane.downgrade()); -// } - -// self.dismiss_zoomed_items_to_reveal(None, cx); -// if pane.read(cx).is_zoomed() { -// self.zoomed = Some(pane.downgrade().into_any()); -// } else { -// self.zoomed = None; -// } -// self.zoomed_position = None; -// self.update_active_view_for_followers(cx); - -// cx.notify(); -// } - -// fn handle_pane_event( -// &mut self, -// pane: ViewHandle, -// event: &pane::Event, -// cx: &mut ViewContext, -// ) { -// match event { -// pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), -// pane::Event::Split(direction) => { -// self.split_and_clone(pane, *direction, cx); -// } -// pane::Event::Remove => self.remove_pane(pane, cx), -// pane::Event::ActivateItem { local } => { -// if *local { -// self.unfollow(&pane, cx); -// } -// if &pane == self.active_pane() { -// self.active_item_path_changed(cx); -// } -// } -// pane::Event::ChangeItemTitle => { -// if pane == self.active_pane { -// self.active_item_path_changed(cx); -// } -// self.update_window_edited(cx); -// } -// pane::Event::RemoveItem { item_id } => { -// self.update_window_edited(cx); -// if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { -// if entry.get().id() == pane.id() { -// entry.remove(); -// } -// } -// } -// pane::Event::Focus => { -// self.handle_pane_focused(pane.clone(), cx); -// } -// pane::Event::ZoomIn => { -// if pane == self.active_pane { -// pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); -// if pane.read(cx).has_focus() { -// self.zoomed = Some(pane.downgrade().into_any()); -// self.zoomed_position = None; -// } -// cx.notify(); -// } -// } -// pane::Event::ZoomOut => { -// pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); -// if self.zoomed_position.is_none() { -// self.zoomed = None; -// } -// cx.notify(); -// } -// } - -// self.serialize_workspace(cx); -// } - -// pub fn split_pane( -// &mut self, -// pane_to_split: ViewHandle, -// split_direction: SplitDirection, -// cx: &mut ViewContext, -// ) -> ViewHandle { -// let new_pane = self.add_pane(cx); -// self.center -// .split(&pane_to_split, &new_pane, split_direction) -// .unwrap(); -// cx.notify(); -// new_pane -// } - -// pub fn split_and_clone( -// &mut self, -// pane: ViewHandle, -// direction: SplitDirection, -// cx: &mut ViewContext, -// ) -> Option> { -// let item = pane.read(cx).active_item()?; -// let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { -// let new_pane = self.add_pane(cx); -// new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); -// self.center.split(&pane, &new_pane, direction).unwrap(); -// Some(new_pane) -// } else { -// None -// }; -// cx.notify(); -// maybe_pane_handle -// } - -// pub fn split_pane_with_item( -// &mut self, -// pane_to_split: WeakViewHandle, -// split_direction: SplitDirection, -// from: WeakViewHandle, -// item_id_to_move: usize, -// cx: &mut ViewContext, -// ) { -// let Some(pane_to_split) = pane_to_split.upgrade(cx) else { -// return; -// }; -// let Some(from) = from.upgrade(cx) else { -// return; -// }; - -// let new_pane = self.add_pane(cx); -// self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); -// self.center -// .split(&pane_to_split, &new_pane, split_direction) -// .unwrap(); -// cx.notify(); -// } - -// pub fn split_pane_with_project_entry( -// &mut self, -// pane_to_split: WeakViewHandle, -// split_direction: SplitDirection, -// project_entry: ProjectEntryId, -// cx: &mut ViewContext, -// ) -> Option>> { -// let pane_to_split = pane_to_split.upgrade(cx)?; -// let new_pane = self.add_pane(cx); -// self.center -// .split(&pane_to_split, &new_pane, split_direction) -// .unwrap(); - -// let path = self.project.read(cx).path_for_entry(project_entry, cx)?; -// let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); -// Some(cx.foreground().spawn(async move { -// task.await?; -// Ok(()) -// })) -// } - -// pub fn move_item( -// &mut self, -// source: ViewHandle, -// destination: ViewHandle, -// item_id_to_move: usize, -// destination_index: usize, -// cx: &mut ViewContext, -// ) { -// let item_to_move = source -// .read(cx) -// .items() -// .enumerate() -// .find(|(_, item_handle)| item_handle.id() == item_id_to_move); - -// if item_to_move.is_none() { -// log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); -// return; -// } -// let (item_ix, item_handle) = item_to_move.unwrap(); -// let item_handle = item_handle.clone(); - -// if source != destination { -// // Close item from previous pane -// source.update(cx, |source, cx| { -// source.remove_item(item_ix, false, cx); -// }); -// } - -// // This automatically removes duplicate items in the pane -// destination.update(cx, |destination, cx| { -// destination.add_item(item_handle, true, true, Some(destination_index), cx); -// cx.focus_self(); -// }); -// } - -// fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { -// if self.center.remove(&pane).unwrap() { -// self.force_remove_pane(&pane, cx); -// self.unfollow(&pane, cx); -// self.last_leaders_by_pane.remove(&pane.downgrade()); -// for removed_item in pane.read(cx).items() { -// self.panes_by_item.remove(&removed_item.id()); -// } - -// cx.notify(); -// } else { -// self.active_item_path_changed(cx); -// } -// } - -// pub fn panes(&self) -> &[ViewHandle] { -// &self.panes -// } - -// pub fn active_pane(&self) -> &ViewHandle { -// &self.active_pane -// } - -// fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { -// self.follower_states.retain(|_, state| { -// if state.leader_id == peer_id { -// for item in state.items_by_leader_view_id.values() { -// item.set_leader_peer_id(None, cx); -// } -// false -// } else { -// true -// } -// }); -// cx.notify(); -// } - -// fn start_following( -// &mut self, -// leader_id: PeerId, -// cx: &mut ViewContext, -// ) -> Option>> { -// let pane = self.active_pane().clone(); - -// self.last_leaders_by_pane -// .insert(pane.downgrade(), leader_id); -// self.unfollow(&pane, cx); -// self.follower_states.insert( -// pane.clone(), -// FollowerState { -// leader_id, -// active_view_id: None, -// items_by_leader_view_id: Default::default(), -// }, -// ); -// cx.notify(); - -// let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); -// let project_id = self.project.read(cx).remote_id(); -// let request = self.app_state.client.request(proto::Follow { -// room_id, -// project_id, -// leader_id: Some(leader_id), -// }); - -// Some(cx.spawn(|this, mut cx| async move { -// let response = request.await?; -// this.update(&mut cx, |this, _| { -// let state = this -// .follower_states -// .get_mut(&pane) -// .ok_or_else(|| anyhow!("following interrupted"))?; -// state.active_view_id = if let Some(active_view_id) = response.active_view_id { -// Some(ViewId::from_proto(active_view_id)?) -// } else { -// None -// }; -// Ok::<_, anyhow::Error>(()) -// })??; -// Self::add_views_from_leader( -// this.clone(), -// leader_id, -// vec![pane], -// response.views, -// &mut cx, -// ) -// .await?; -// this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?; -// Ok(()) -// })) -// } - -// pub fn follow_next_collaborator( -// &mut self, -// _: &FollowNextCollaborator, -// cx: &mut ViewContext, -// ) -> Option>> { -// let collaborators = self.project.read(cx).collaborators(); -// let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) { -// let mut collaborators = collaborators.keys().copied(); -// for peer_id in collaborators.by_ref() { -// if peer_id == leader_id { -// break; -// } -// } -// collaborators.next() -// } else if let Some(last_leader_id) = -// self.last_leaders_by_pane.get(&self.active_pane.downgrade()) -// { -// if collaborators.contains_key(last_leader_id) { -// Some(*last_leader_id) -// } else { -// None -// } -// } else { -// None -// }; - -// let pane = self.active_pane.clone(); -// let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next()) -// else { -// return None; -// }; -// if Some(leader_id) == self.unfollow(&pane, cx) { -// return None; -// } -// self.follow(leader_id, cx) -// } - -// pub fn follow( -// &mut self, -// leader_id: PeerId, -// cx: &mut ViewContext, -// ) -> Option>> { -// let room = ActiveCall::global(cx).read(cx).room()?.read(cx); -// let project = self.project.read(cx); - -// let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else { -// return None; -// }; - -// let other_project_id = match remote_participant.location { -// call::ParticipantLocation::External => None, -// call::ParticipantLocation::UnsharedProject => None, -// call::ParticipantLocation::SharedProject { project_id } => { -// if Some(project_id) == project.remote_id() { -// None -// } else { -// Some(project_id) -// } -// } -// }; - -// // if they are active in another project, follow there. -// if let Some(project_id) = other_project_id { -// let app_state = self.app_state.clone(); -// return Some(crate::join_remote_project( -// project_id, -// remote_participant.user.id, -// app_state, -// cx, -// )); -// } - -// // if you're already following, find the right pane and focus it. -// for (pane, state) in &self.follower_states { -// if leader_id == state.leader_id { -// cx.focus(pane); -// return None; -// } -// } - -// // Otherwise, follow. -// self.start_following(leader_id, cx) -// } - -// pub fn unfollow( -// &mut self, -// pane: &ViewHandle, -// cx: &mut ViewContext, -// ) -> Option { -// let state = self.follower_states.remove(pane)?; -// let leader_id = state.leader_id; -// for (_, item) in state.items_by_leader_view_id { -// item.set_leader_peer_id(None, cx); -// } - -// if self -// .follower_states -// .values() -// .all(|state| state.leader_id != state.leader_id) -// { -// let project_id = self.project.read(cx).remote_id(); -// let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); -// self.app_state -// .client -// .send(proto::Unfollow { -// room_id, -// project_id, -// leader_id: Some(leader_id), -// }) -// .log_err(); -// } - -// cx.notify(); -// Some(leader_id) -// } - -// pub fn is_being_followed(&self, peer_id: PeerId) -> bool { -// self.follower_states -// .values() -// .any(|state| state.leader_id == peer_id) -// } - -// fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { -// // TODO: There should be a better system in place for this -// // (https://github.com/zed-industries/zed/issues/1290) -// let is_fullscreen = cx.window_is_fullscreen(); -// let container_theme = if is_fullscreen { -// let mut container_theme = theme.titlebar.container; -// container_theme.padding.left = container_theme.padding.right; -// container_theme -// } else { -// theme.titlebar.container -// }; - -// enum TitleBar {} -// MouseEventHandler::new::(0, cx, |_, cx| { -// Stack::new() -// .with_children( -// self.titlebar_item -// .as_ref() -// .map(|item| ChildView::new(item, cx)), -// ) -// .contained() -// .with_style(container_theme) -// }) -// .on_click(MouseButton::Left, |event, _, cx| { -// if event.click_count == 2 { -// cx.zoom_window(); -// } -// }) -// .constrained() -// .with_height(theme.titlebar.height) -// .into_any_named("titlebar") -// } - -// fn active_item_path_changed(&mut self, cx: &mut ViewContext) { -// let active_entry = self.active_project_path(cx); -// self.project -// .update(cx, |project, cx| project.set_active_path(active_entry, cx)); -// self.update_window_title(cx); -// } - -// fn update_window_title(&mut self, cx: &mut ViewContext) { -// let project = self.project().read(cx); -// let mut title = String::new(); - -// if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { -// let filename = path -// .path -// .file_name() -// .map(|s| s.to_string_lossy()) -// .or_else(|| { -// Some(Cow::Borrowed( -// project -// .worktree_for_id(path.worktree_id, cx)? -// .read(cx) -// .root_name(), -// )) -// }); - -// if let Some(filename) = filename { -// title.push_str(filename.as_ref()); -// title.push_str(" — "); -// } -// } - -// for (i, name) in project.worktree_root_names(cx).enumerate() { -// if i > 0 { -// title.push_str(", "); -// } -// title.push_str(name); -// } - -// if title.is_empty() { -// title = "empty project".to_string(); -// } - -// if project.is_remote() { -// title.push_str(" ↙"); -// } else if project.is_shared() { -// title.push_str(" ↗"); -// } - -// cx.set_window_title(&title); -// } - -// fn update_window_edited(&mut self, cx: &mut ViewContext) { -// let is_edited = !self.project.read(cx).is_read_only() -// && self -// .items(cx) -// .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); -// if is_edited != self.window_edited { -// self.window_edited = is_edited; -// cx.set_window_edited(self.window_edited) -// } -// } - -// fn render_disconnected_overlay( -// &self, -// cx: &mut ViewContext, -// ) -> Option> { -// if self.project.read(cx).is_read_only() { -// enum DisconnectedOverlay {} -// Some( -// MouseEventHandler::new::(0, cx, |_, cx| { -// let theme = &theme::current(cx); -// Label::new( -// "Your connection to the remote project has been lost.", -// theme.workspace.disconnected_overlay.text.clone(), -// ) -// .aligned() -// .contained() -// .with_style(theme.workspace.disconnected_overlay.container) -// }) -// .with_cursor_style(CursorStyle::Arrow) -// .capture_all() -// .into_any_named("disconnected overlay"), -// ) -// } else { -// None -// } -// } - -// fn render_notifications( -// &self, -// theme: &theme::Workspace, -// cx: &AppContext, -// ) -> Option> { -// if self.notifications.is_empty() { -// None -// } else { -// Some( -// Flex::column() -// .with_children(self.notifications.iter().map(|(_, _, notification)| { -// ChildView::new(notification.as_any(), cx) -// .contained() -// .with_style(theme.notification) -// })) -// .constrained() -// .with_width(theme.notifications.width) -// .contained() -// .with_style(theme.notifications.container) -// .aligned() -// .bottom() -// .right() -// .into_any(), -// ) -// } -// } - -// // RPC handlers - -// fn handle_follow( -// &mut self, -// follower_project_id: Option, -// cx: &mut ViewContext, -// ) -> proto::FollowResponse { -// let client = &self.app_state.client; -// let project_id = self.project.read(cx).remote_id(); - -// let active_view_id = self.active_item(cx).and_then(|i| { -// Some( -// i.to_followable_item_handle(cx)? -// .remote_id(client, cx)? -// .to_proto(), -// ) -// }); - -// cx.notify(); - -// self.last_active_view_id = active_view_id.clone(); -// proto::FollowResponse { -// active_view_id, -// views: self -// .panes() -// .iter() -// .flat_map(|pane| { -// let leader_id = self.leader_for_pane(pane); -// pane.read(cx).items().filter_map({ -// let cx = &cx; -// move |item| { -// let item = item.to_followable_item_handle(cx)?; -// if (project_id.is_none() || project_id != follower_project_id) -// && item.is_project_item(cx) -// { -// return None; -// } -// let id = item.remote_id(client, cx)?.to_proto(); -// let variant = item.to_state_proto(cx)?; -// Some(proto::View { -// id: Some(id), -// leader_id, -// variant: Some(variant), -// }) -// } -// }) -// }) -// .collect(), -// } -// } - -// fn handle_update_followers( -// &mut self, -// leader_id: PeerId, -// message: proto::UpdateFollowers, -// _cx: &mut ViewContext, -// ) { -// self.leader_updates_tx -// .unbounded_send((leader_id, message)) -// .ok(); -// } - -// async fn process_leader_update( -// this: &WeakViewHandle, -// leader_id: PeerId, -// update: proto::UpdateFollowers, -// cx: &mut AsyncAppContext, -// ) -> Result<()> { -// match update.variant.ok_or_else(|| anyhow!("invalid update"))? { -// proto::update_followers::Variant::UpdateActiveView(update_active_view) => { -// this.update(cx, |this, _| { -// for (_, state) in &mut this.follower_states { -// if state.leader_id == leader_id { -// state.active_view_id = -// if let Some(active_view_id) = update_active_view.id.clone() { -// Some(ViewId::from_proto(active_view_id)?) -// } else { -// None -// }; -// } -// } -// anyhow::Ok(()) -// })??; -// } -// proto::update_followers::Variant::UpdateView(update_view) => { -// let variant = update_view -// .variant -// .ok_or_else(|| anyhow!("missing update view variant"))?; -// let id = update_view -// .id -// .ok_or_else(|| anyhow!("missing update view id"))?; -// let mut tasks = Vec::new(); -// this.update(cx, |this, cx| { -// let project = this.project.clone(); -// for (_, state) in &mut this.follower_states { -// if state.leader_id == leader_id { -// let view_id = ViewId::from_proto(id.clone())?; -// if let Some(item) = state.items_by_leader_view_id.get(&view_id) { -// tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); -// } -// } -// } -// anyhow::Ok(()) -// })??; -// try_join_all(tasks).await.log_err(); -// } -// proto::update_followers::Variant::CreateView(view) => { -// let panes = this.read_with(cx, |this, _| { -// this.follower_states -// .iter() -// .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) -// .cloned() -// .collect() -// })?; -// Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?; -// } -// } -// this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?; -// Ok(()) -// } - -// async fn add_views_from_leader( -// this: WeakViewHandle, -// leader_id: PeerId, -// panes: Vec>, -// views: Vec, -// cx: &mut AsyncAppContext, -// ) -> Result<()> { -// let this = this -// .upgrade(cx) -// .ok_or_else(|| anyhow!("workspace dropped"))?; - -// let item_builders = cx.update(|cx| { -// cx.default_global::() -// .values() -// .map(|b| b.0) -// .collect::>() -// }); - -// let mut item_tasks_by_pane = HashMap::default(); -// for pane in panes { -// let mut item_tasks = Vec::new(); -// let mut leader_view_ids = Vec::new(); -// for view in &views { -// let Some(id) = &view.id else { continue }; -// let id = ViewId::from_proto(id.clone())?; -// let mut variant = view.variant.clone(); -// if variant.is_none() { -// Err(anyhow!("missing view variant"))?; -// } -// for build_item in &item_builders { -// let task = cx -// .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); -// if let Some(task) = task { -// item_tasks.push(task); -// leader_view_ids.push(id); -// break; -// } else { -// assert!(variant.is_some()); -// } -// } -// } - -// item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids)); -// } - -// for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { -// let items = futures::future::try_join_all(item_tasks).await?; -// this.update(cx, |this, cx| { -// let state = this.follower_states.get_mut(&pane)?; -// for (id, item) in leader_view_ids.into_iter().zip(items) { -// item.set_leader_peer_id(Some(leader_id), cx); -// state.items_by_leader_view_id.insert(id, item); -// } - -// Some(()) -// }); -// } -// Ok(()) -// } - -// fn update_active_view_for_followers(&mut self, cx: &AppContext) { -// let mut is_project_item = true; -// let mut update = proto::UpdateActiveView::default(); -// if self.active_pane.read(cx).has_focus() { -// let item = self -// .active_item(cx) -// .and_then(|item| item.to_followable_item_handle(cx)); -// if let Some(item) = item { -// is_project_item = item.is_project_item(cx); -// update = proto::UpdateActiveView { -// id: item -// .remote_id(&self.app_state.client, cx) -// .map(|id| id.to_proto()), -// leader_id: self.leader_for_pane(&self.active_pane), -// }; -// } -// } - -// if update.id != self.last_active_view_id { -// self.last_active_view_id = update.id.clone(); -// self.update_followers( -// is_project_item, -// proto::update_followers::Variant::UpdateActiveView(update), -// cx, -// ); -// } -// } - -// fn update_followers( -// &self, -// project_only: bool, -// update: proto::update_followers::Variant, -// cx: &AppContext, -// ) -> Option<()> { -// let project_id = if project_only { -// self.project.read(cx).remote_id() -// } else { -// None -// }; -// self.app_state().workspace_store.read_with(cx, |store, cx| { -// store.update_followers(project_id, update, cx) -// }) -// } - -// pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { -// self.follower_states.get(pane).map(|state| state.leader_id) -// } - -// fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { -// cx.notify(); - -// let call = self.active_call()?; -// let room = call.read(cx).room()?.read(cx); -// let participant = room.remote_participant_for_peer_id(leader_id)?; -// let mut items_to_activate = Vec::new(); - -// let leader_in_this_app; -// let leader_in_this_project; -// match participant.location { -// call::ParticipantLocation::SharedProject { project_id } => { -// leader_in_this_app = true; -// leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); -// } -// call::ParticipantLocation::UnsharedProject => { -// leader_in_this_app = true; -// leader_in_this_project = false; -// } -// call::ParticipantLocation::External => { -// leader_in_this_app = false; -// leader_in_this_project = false; -// } -// }; - -// for (pane, state) in &self.follower_states { -// if state.leader_id != leader_id { -// continue; -// } -// if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { -// if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { -// if leader_in_this_project || !item.is_project_item(cx) { -// items_to_activate.push((pane.clone(), item.boxed_clone())); -// } -// } else { -// log::warn!( -// "unknown view id {:?} for leader {:?}", -// active_view_id, -// leader_id -// ); -// } -// continue; -// } -// if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { -// items_to_activate.push((pane.clone(), Box::new(shared_screen))); -// } -// } - -// for (pane, item) in items_to_activate { -// let pane_was_focused = pane.read(cx).has_focus(); -// if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { -// pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); -// } else { -// pane.update(cx, |pane, cx| { -// pane.add_item(item.boxed_clone(), false, false, None, cx) -// }); -// } - -// if pane_was_focused { -// pane.update(cx, |pane, cx| pane.focus_active_item(cx)); -// } -// } - -// None -// } - -// fn shared_screen_for_peer( -// &self, -// peer_id: PeerId, -// pane: &ViewHandle, -// cx: &mut ViewContext, -// ) -> Option> { -// let call = self.active_call()?; -// let room = call.read(cx).room()?.read(cx); -// let participant = room.remote_participant_for_peer_id(peer_id)?; -// let track = participant.video_tracks.values().next()?.clone(); -// let user = participant.user.clone(); - -// for item in pane.read(cx).items_of_type::() { -// if item.read(cx).peer_id == peer_id { -// return Some(item); -// } -// } - -// Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) -// } - -// pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { -// if active { -// self.update_active_view_for_followers(cx); -// cx.background() -// .spawn(persistence::DB.update_timestamp(self.database_id())) -// .detach(); -// } else { -// for pane in &self.panes { -// pane.update(cx, |pane, cx| { -// if let Some(item) = pane.active_item() { -// item.workspace_deactivated(cx); -// } -// if matches!( -// settings::get::(cx).autosave, -// AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange -// ) { -// for item in pane.items() { -// Pane::autosave_item(item.as_ref(), self.project.clone(), cx) -// .detach_and_log_err(cx); -// } -// } -// }); -// } -// } -// } - -// fn active_call(&self) -> Option<&ModelHandle> { -// self.active_call.as_ref().map(|(call, _)| call) -// } - -// fn on_active_call_event( -// &mut self, -// _: ModelHandle, -// event: &call::room::Event, -// cx: &mut ViewContext, -// ) { -// match event { -// call::room::Event::ParticipantLocationChanged { participant_id } -// | call::room::Event::RemoteVideoTracksChanged { participant_id } => { -// self.leader_updated(*participant_id, cx); -// } -// _ => {} -// } -// } - -// pub fn database_id(&self) -> WorkspaceId { -// self.database_id -// } - -// fn location(&self, cx: &AppContext) -> Option { -// let project = self.project().read(cx); - -// if project.is_local() { -// Some( -// project -// .visible_worktrees(cx) -// .map(|worktree| worktree.read(cx).abs_path()) -// .collect::>() -// .into(), -// ) -// } else { -// None -// } -// } - -// fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { -// match member { -// Member::Axis(PaneAxis { members, .. }) => { -// for child in members.iter() { -// self.remove_panes(child.clone(), cx) -// } -// } -// Member::Pane(pane) => { -// self.force_remove_pane(&pane, cx); -// } -// } -// } - -// fn force_remove_pane(&mut self, pane: &ViewHandle, cx: &mut ViewContext) { -// self.panes.retain(|p| p != pane); -// cx.focus(self.panes.last().unwrap()); -// if self.last_active_center_pane == Some(pane.downgrade()) { -// self.last_active_center_pane = None; -// } -// cx.notify(); -// } - -// fn schedule_serialize(&mut self, cx: &mut ViewContext) { -// self._schedule_serialize = Some(cx.spawn(|this, cx| async move { -// cx.background().timer(Duration::from_millis(100)).await; -// this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) -// .ok(); -// })); -// } - -// fn serialize_workspace(&self, cx: &ViewContext) { -// fn serialize_pane_handle( -// pane_handle: &ViewHandle, -// cx: &AppContext, -// ) -> SerializedPane { -// let (items, active) = { -// let pane = pane_handle.read(cx); -// let active_item_id = pane.active_item().map(|item| item.id()); -// ( -// pane.items() -// .filter_map(|item_handle| { -// Some(SerializedItem { -// kind: Arc::from(item_handle.serialized_item_kind()?), -// item_id: item_handle.id(), -// active: Some(item_handle.id()) == active_item_id, -// }) -// }) -// .collect::>(), -// pane.has_focus(), -// ) -// }; - -// SerializedPane::new(items, active) -// } - -// fn build_serialized_pane_group( -// pane_group: &Member, -// cx: &AppContext, -// ) -> SerializedPaneGroup { -// match pane_group { -// Member::Axis(PaneAxis { -// axis, -// members, -// flexes, -// bounding_boxes: _, -// }) => SerializedPaneGroup::Group { -// axis: *axis, -// children: members -// .iter() -// .map(|member| build_serialized_pane_group(member, cx)) -// .collect::>(), -// flexes: Some(flexes.borrow().clone()), -// }, -// Member::Pane(pane_handle) => { -// SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) -// } -// } -// } - -// fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { -// let left_dock = this.left_dock.read(cx); -// let left_visible = left_dock.is_open(); -// let left_active_panel = left_dock.visible_panel().and_then(|panel| { -// Some( -// cx.view_ui_name(panel.as_any().window(), panel.id())? -// .to_string(), -// ) -// }); -// let left_dock_zoom = left_dock -// .visible_panel() -// .map(|panel| panel.is_zoomed(cx)) -// .unwrap_or(false); - -// let right_dock = this.right_dock.read(cx); -// let right_visible = right_dock.is_open(); -// let right_active_panel = right_dock.visible_panel().and_then(|panel| { -// Some( -// cx.view_ui_name(panel.as_any().window(), panel.id())? -// .to_string(), -// ) -// }); -// let right_dock_zoom = right_dock -// .visible_panel() -// .map(|panel| panel.is_zoomed(cx)) -// .unwrap_or(false); - -// let bottom_dock = this.bottom_dock.read(cx); -// let bottom_visible = bottom_dock.is_open(); -// let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { -// Some( -// cx.view_ui_name(panel.as_any().window(), panel.id())? -// .to_string(), -// ) -// }); -// let bottom_dock_zoom = bottom_dock -// .visible_panel() -// .map(|panel| panel.is_zoomed(cx)) -// .unwrap_or(false); - -// DockStructure { -// left: DockData { -// visible: left_visible, -// active_panel: left_active_panel, -// zoom: left_dock_zoom, -// }, -// right: DockData { -// visible: right_visible, -// active_panel: right_active_panel, -// zoom: right_dock_zoom, -// }, -// bottom: DockData { -// visible: bottom_visible, -// active_panel: bottom_active_panel, -// zoom: bottom_dock_zoom, -// }, -// } -// } - -// if let Some(location) = self.location(cx) { -// // Load bearing special case: -// // - with_local_workspace() relies on this to not have other stuff open -// // when you open your log -// if !location.paths().is_empty() { -// let center_group = build_serialized_pane_group(&self.center.root, cx); -// let docks = build_serialized_docks(self, cx); - -// let serialized_workspace = SerializedWorkspace { -// id: self.database_id, -// location, -// center_group, -// bounds: Default::default(), -// display: Default::default(), -// docks, -// }; - -// cx.background() -// .spawn(persistence::DB.save_workspace(serialized_workspace)) -// .detach(); -// } -// } -// } - -// pub(crate) fn load_workspace( -// workspace: WeakViewHandle, -// serialized_workspace: SerializedWorkspace, -// paths_to_open: Vec>, -// cx: &mut AppContext, -// ) -> Task>>>> { -// cx.spawn(|mut cx| async move { -// let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { -// ( -// workspace.project().clone(), -// workspace.last_active_center_pane.clone(), -// ) -// })?; - -// let mut center_group = None; -// let mut center_items = None; -// // Traverse the splits tree and add to things -// if let Some((group, active_pane, items)) = serialized_workspace -// .center_group -// .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) -// .await -// { -// center_items = Some(items); -// center_group = Some((group, active_pane)) -// } - -// let mut items_by_project_path = cx.read(|cx| { -// center_items -// .unwrap_or_default() -// .into_iter() -// .filter_map(|item| { -// let item = item?; -// let project_path = item.project_path(cx)?; -// Some((project_path, item)) -// }) -// .collect::>() -// }); - -// let opened_items = paths_to_open -// .into_iter() -// .map(|path_to_open| { -// path_to_open -// .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) -// }) -// .collect::>(); - -// // Remove old panes from workspace panes list -// workspace.update(&mut cx, |workspace, cx| { -// if let Some((center_group, active_pane)) = center_group { -// workspace.remove_panes(workspace.center.root.clone(), cx); - -// // Swap workspace center group -// workspace.center = PaneGroup::with_root(center_group); - -// // Change the focus to the workspace first so that we retrigger focus in on the pane. -// cx.focus_self(); - -// if let Some(active_pane) = active_pane { -// cx.focus(&active_pane); -// } else { -// cx.focus(workspace.panes.last().unwrap()); -// } -// } else { -// let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); -// if let Some(old_center_handle) = old_center_handle { -// cx.focus(&old_center_handle) -// } else { -// cx.focus_self() -// } -// } - -// let docks = serialized_workspace.docks; -// workspace.left_dock.update(cx, |dock, cx| { -// dock.set_open(docks.left.visible, cx); -// if let Some(active_panel) = docks.left.active_panel { -// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { -// dock.activate_panel(ix, cx); -// } -// } -// dock.active_panel() -// .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); -// if docks.left.visible && docks.left.zoom { -// cx.focus_self() -// } -// }); -// // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something -// workspace.right_dock.update(cx, |dock, cx| { -// dock.set_open(docks.right.visible, cx); -// if let Some(active_panel) = docks.right.active_panel { -// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { -// dock.activate_panel(ix, cx); -// } -// } -// dock.active_panel() -// .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); - -// if docks.right.visible && docks.right.zoom { -// cx.focus_self() -// } -// }); -// workspace.bottom_dock.update(cx, |dock, cx| { -// dock.set_open(docks.bottom.visible, cx); -// if let Some(active_panel) = docks.bottom.active_panel { -// if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { -// dock.activate_panel(ix, cx); -// } -// } - -// dock.active_panel() -// .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); - -// if docks.bottom.visible && docks.bottom.zoom { -// cx.focus_self() -// } -// }); - -// cx.notify(); -// })?; - -// // Serialize ourself to make sure our timestamps and any pane / item changes are replicated -// workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; - -// Ok(opened_items) -// }) -// } - -// #[cfg(any(test, feature = "test-support"))] -// pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { -// use node_runtime::FakeNodeRuntime; - -// let client = project.read(cx).client(); -// let user_store = project.read(cx).user_store(); - -// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); -// let app_state = Arc::new(AppState { -// languages: project.read(cx).languages().clone(), -// workspace_store, -// client, -// user_store, -// fs: project.read(cx).fs().clone(), -// build_window_options: |_, _, _| Default::default(), -// initialize_workspace: |_, _, _, _| Task::ready(Ok(())), -// node_runtime: FakeNodeRuntime::new(), -// }); -// Self::new(0, project, app_state, cx) -// } - -// fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { -// let dock = match position { -// DockPosition::Left => &self.left_dock, -// DockPosition::Right => &self.right_dock, -// DockPosition::Bottom => &self.bottom_dock, -// }; -// let active_panel = dock.read(cx).visible_panel()?; -// let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { -// dock.read(cx).render_placeholder(cx) -// } else { -// ChildView::new(dock, cx).into_any() -// }; - -// Some( -// element -// .constrained() -// .dynamically(move |constraint, _, cx| match position { -// DockPosition::Left | DockPosition::Right => SizeConstraint::new( -// Vector2F::new(20., constraint.min.y()), -// Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), -// ), -// DockPosition::Bottom => SizeConstraint::new( -// Vector2F::new(constraint.min.x(), 20.), -// Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), -// ), -// }) -// .into_any(), -// ) -// } -// } - -// fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { -// ZED_WINDOW_POSITION -// .zip(*ZED_WINDOW_SIZE) -// .map(|(position, size)| { -// WindowBounds::Fixed(RectF::new( -// cx.platform().screens()[0].bounds().origin() + position, -// size, -// )) -// }) -// } - -// async fn open_items( -// serialized_workspace: Option, -// workspace: &WeakViewHandle, -// mut project_paths_to_open: Vec<(PathBuf, Option)>, -// app_state: Arc, -// mut cx: AsyncAppContext, -// ) -> Result>>>> { -// let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - -// if let Some(serialized_workspace) = serialized_workspace { -// let workspace = workspace.clone(); -// let restored_items = cx -// .update(|cx| { -// Workspace::load_workspace( -// workspace, -// serialized_workspace, -// project_paths_to_open -// .iter() -// .map(|(_, project_path)| project_path) -// .cloned() -// .collect(), -// cx, -// ) -// }) -// .await?; - -// let restored_project_paths = cx.read(|cx| { -// restored_items -// .iter() -// .filter_map(|item| item.as_ref()?.project_path(cx)) -// .collect::>() -// }); - -// for restored_item in restored_items { -// opened_items.push(restored_item.map(Ok)); -// } - -// project_paths_to_open -// .iter_mut() -// .for_each(|(_, project_path)| { -// if let Some(project_path_to_open) = project_path { -// if restored_project_paths.contains(project_path_to_open) { -// *project_path = None; -// } -// } -// }); -// } else { -// for _ in 0..project_paths_to_open.len() { -// opened_items.push(None); -// } -// } -// assert!(opened_items.len() == project_paths_to_open.len()); - -// let tasks = -// project_paths_to_open -// .into_iter() -// .enumerate() -// .map(|(i, (abs_path, project_path))| { -// let workspace = workspace.clone(); -// cx.spawn(|mut cx| { -// let fs = app_state.fs.clone(); -// async move { -// let file_project_path = project_path?; -// if fs.is_file(&abs_path).await { -// Some(( -// i, -// workspace -// .update(&mut cx, |workspace, cx| { -// workspace.open_path(file_project_path, None, true, cx) -// }) -// .log_err()? -// .await, -// )) -// } else { -// None -// } -// } -// }) -// }); - -// for maybe_opened_path in futures::future::join_all(tasks.into_iter()) -// .await -// .into_iter() -// { -// if let Some((i, path_open_result)) = maybe_opened_path { -// opened_items[i] = Some(path_open_result); -// } -// } - -// Ok(opened_items) -// } - -// fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { -// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; -// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; -// const MESSAGE_ID: usize = 2; - -// if workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// return; -// } - -// if db::kvp::KEY_VALUE_STORE -// .read_kvp(NEW_DOCK_HINT_KEY) -// .ok() -// .flatten() -// .is_some() -// { -// if !workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// cx.update(|cx| { -// cx.update_global::(|tracker, _| { -// let entry = tracker -// .entry(TypeId::of::()) -// .or_default(); -// if !entry.contains(&MESSAGE_ID) { -// entry.push(MESSAGE_ID); -// } -// }); -// }); -// } - -// return; -// } - -// cx.spawn(|_| async move { -// db::kvp::KEY_VALUE_STORE -// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) -// .await -// .ok(); -// }) -// .detach(); - -// workspace -// .update(cx, |workspace, cx| { -// workspace.show_notification_once(2, cx, |cx| { -// cx.add_view(|_| { -// MessageNotification::new_element(|text, _| { -// Text::new( -// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", -// text, -// ) -// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { -// let code_span_background_color = settings::get::(cx) -// .theme -// .editor -// .document_highlight_read_background; - -// cx.scene().push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }) -// }) -// .into_any() -// }) -// .with_click_message("Read more about the new panel system") -// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) -// }) -// }) -// }) -// .ok(); -// } +impl Workspace { + // pub fn new( + // workspace_id: WorkspaceId, + // project: ModelHandle, + // app_state: Arc, + // cx: &mut ViewContext, + // ) -> Self { + // cx.observe(&project, |_, _, cx| cx.notify()).detach(); + // cx.subscribe(&project, move |this, _, event, cx| { + // match event { + // project::Event::RemoteIdChanged(_) => { + // this.update_window_title(cx); + // } + + // project::Event::CollaboratorLeft(peer_id) => { + // this.collaborator_left(*peer_id, cx); + // } + + // project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { + // this.update_window_title(cx); + // this.serialize_workspace(cx); + // } + + // project::Event::DisconnectedFromHost => { + // this.update_window_edited(cx); + // cx.blur(); + // } + + // project::Event::Closed => { + // cx.remove_window(); + // } + + // project::Event::DeletedEntry(entry_id) => { + // for pane in this.panes.iter() { + // pane.update(cx, |pane, cx| { + // pane.handle_deleted_project_item(*entry_id, cx) + // }); + // } + // } + + // project::Event::Notification(message) => this.show_notification(0, cx, |cx| { + // cx.add_view(|_| MessageNotification::new(message.clone())) + // }), + + // _ => {} + // } + // cx.notify() + // }) + // .detach(); + + // let weak_handle = cx.weak_handle(); + // let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); + + // let center_pane = cx.add_view(|cx| { + // Pane::new( + // weak_handle.clone(), + // project.clone(), + // pane_history_timestamp.clone(), + // cx, + // ) + // }); + // cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); + // cx.focus(¢er_pane); + // cx.emit(Event::PaneAdded(center_pane.clone())); + + // app_state.workspace_store.update(cx, |store, _| { + // store.workspaces.insert(weak_handle.clone()); + // }); + + // let mut current_user = app_state.user_store.read(cx).watch_current_user(); + // let mut connection_status = app_state.client.status(); + // let _observe_current_user = cx.spawn(|this, mut cx| async move { + // current_user.recv().await; + // connection_status.recv().await; + // let mut stream = + // Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); + + // while stream.recv().await.is_some() { + // this.update(&mut cx, |_, cx| cx.notify())?; + // } + // anyhow::Ok(()) + // }); + + // // All leader updates are enqueued and then processed in a single task, so + // // that each asynchronous operation can be run in order. + // let (leader_updates_tx, mut leader_updates_rx) = + // mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); + // let _apply_leader_updates = cx.spawn(|this, mut cx| async move { + // while let Some((leader_id, update)) = leader_updates_rx.next().await { + // Self::process_leader_update(&this, leader_id, update, &mut cx) + // .await + // .log_err(); + // } + + // Ok(()) + // }); + + // cx.emit_global(WorkspaceCreated(weak_handle.clone())); + + // let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left)); + // let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom)); + // let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right)); + // let left_dock_buttons = + // cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + // let bottom_dock_buttons = + // cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); + // let right_dock_buttons = + // cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); + // let status_bar = cx.add_view(|cx| { + // let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); + // status_bar.add_left_item(left_dock_buttons, cx); + // status_bar.add_right_item(right_dock_buttons, cx); + // status_bar.add_right_item(bottom_dock_buttons, cx); + // status_bar + // }); + + // cx.update_default_global::, _, _>(|drag_and_drop, _| { + // drag_and_drop.register_container(weak_handle.clone()); + // }); + + // let mut active_call = None; + // if cx.has_global::>() { + // let call = cx.global::>().clone(); + // let mut subscriptions = Vec::new(); + // subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); + // active_call = Some((call, subscriptions)); + // } + + // let subscriptions = vec![ + // cx.observe_fullscreen(|_, _, cx| cx.notify()), + // cx.observe_window_activation(Self::on_window_activation_changed), + // cx.observe_window_bounds(move |_, mut bounds, display, cx| { + // // Transform fixed bounds to be stored in terms of the containing display + // if let WindowBounds::Fixed(mut window_bounds) = bounds { + // if let Some(screen) = cx.platform().screen_by_id(display) { + // let screen_bounds = screen.bounds(); + // window_bounds + // .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); + // window_bounds + // .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); + // bounds = WindowBounds::Fixed(window_bounds); + // } + // } + + // cx.background() + // .spawn(DB.set_window_bounds(workspace_id, bounds, display)) + // .detach_and_log_err(cx); + // }), + // cx.observe(&left_dock, |this, _, cx| { + // this.serialize_workspace(cx); + // cx.notify(); + // }), + // cx.observe(&bottom_dock, |this, _, cx| { + // this.serialize_workspace(cx); + // cx.notify(); + // }), + // cx.observe(&right_dock, |this, _, cx| { + // this.serialize_workspace(cx); + // cx.notify(); + // }), + // ]; + + // cx.defer(|this, cx| this.update_window_title(cx)); + // Workspace { + // weak_self: weak_handle.clone(), + // modal: None, + // zoomed: None, + // zoomed_position: None, + // center: PaneGroup::new(center_pane.clone()), + // panes: vec![center_pane.clone()], + // panes_by_item: Default::default(), + // active_pane: center_pane.clone(), + // last_active_center_pane: Some(center_pane.downgrade()), + // last_active_view_id: None, + // status_bar, + // titlebar_item: None, + // notifications: Default::default(), + // left_dock, + // bottom_dock, + // right_dock, + // project: project.clone(), + // follower_states: Default::default(), + // last_leaders_by_pane: Default::default(), + // window_edited: false, + // active_call, + // database_id: workspace_id, + // app_state, + // _observe_current_user, + // _apply_leader_updates, + // _schedule_serialize: None, + // leader_updates_tx, + // subscriptions, + // pane_history_timestamp, + // } + // } + + // fn new_local( + // abs_paths: Vec, + // app_state: Arc, + // requesting_window: Option>, + // cx: &mut AppContext, + // ) -> Task<( + // WeakViewHandle, + // Vec, anyhow::Error>>>, + // )> { + // let project_handle = Project::local( + // app_state.client.clone(), + // app_state.node_runtime.clone(), + // app_state.user_store.clone(), + // app_state.languages.clone(), + // app_state.fs.clone(), + // cx, + // ); + + // cx.spawn(|mut cx| async move { + // let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + + // let paths_to_open = Arc::new(abs_paths); + + // // Get project paths for all of the abs_paths + // let mut worktree_roots: HashSet> = Default::default(); + // let mut project_paths: Vec<(PathBuf, Option)> = + // Vec::with_capacity(paths_to_open.len()); + // for path in paths_to_open.iter().cloned() { + // if let Some((worktree, project_entry)) = cx + // .update(|cx| { + // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) + // }) + // .await + // .log_err() + // { + // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); + // project_paths.push((path, Some(project_entry))); + // } else { + // project_paths.push((path, None)); + // } + // } + + // let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { + // serialized_workspace.id + // } else { + // DB.next_id().await.unwrap_or(0) + // }; + + // let window = if let Some(window) = requesting_window { + // window.replace_root(&mut cx, |cx| { + // Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + // }); + // window + // } else { + // { + // let window_bounds_override = window_bounds_env_override(&cx); + // let (bounds, display) = if let Some(bounds) = window_bounds_override { + // (Some(bounds), None) + // } else { + // serialized_workspace + // .as_ref() + // .and_then(|serialized_workspace| { + // let display = serialized_workspace.display?; + // let mut bounds = serialized_workspace.bounds?; + + // // Stored bounds are relative to the containing display. + // // So convert back to global coordinates if that screen still exists + // if let WindowBounds::Fixed(mut window_bounds) = bounds { + // if let Some(screen) = cx.platform().screen_by_id(display) { + // let screen_bounds = screen.bounds(); + // window_bounds.set_origin_x( + // window_bounds.origin_x() + screen_bounds.origin_x(), + // ); + // window_bounds.set_origin_y( + // window_bounds.origin_y() + screen_bounds.origin_y(), + // ); + // bounds = WindowBounds::Fixed(window_bounds); + // } else { + // // Screen no longer exists. Return none here. + // return None; + // } + // } + + // Some((bounds, display)) + // }) + // .unzip() + // }; + + // // Use the serialized workspace to construct the new window + // cx.add_window( + // (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), + // |cx| { + // Workspace::new( + // workspace_id, + // project_handle.clone(), + // app_state.clone(), + // cx, + // ) + // }, + // ) + // } + // }; + + // // We haven't yielded the main thread since obtaining the window handle, + // // so the window exists. + // let workspace = window.root(&cx).unwrap(); + + // (app_state.initialize_workspace)( + // workspace.downgrade(), + // serialized_workspace.is_some(), + // app_state.clone(), + // cx.clone(), + // ) + // .await + // .log_err(); + + // window.update(&mut cx, |cx| cx.activate_window()); + + // let workspace = workspace.downgrade(); + // notify_if_database_failed(&workspace, &mut cx); + // let opened_items = open_items( + // serialized_workspace, + // &workspace, + // project_paths, + // app_state, + // cx, + // ) + // .await + // .unwrap_or_default(); + + // (workspace, opened_items) + // }) + // } + + // pub fn weak_handle(&self) -> WeakViewHandle { + // self.weak_self.clone() + // } + + // pub fn left_dock(&self) -> &ViewHandle { + // &self.left_dock + // } + + // pub fn bottom_dock(&self) -> &ViewHandle { + // &self.bottom_dock + // } + + // pub fn right_dock(&self) -> &ViewHandle { + // &self.right_dock + // } + + // pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) + // where + // T::Event: std::fmt::Debug, + // { + // self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) + // } + + // pub fn add_panel_with_extra_event_handler( + // &mut self, + // panel: ViewHandle, + // cx: &mut ViewContext, + // handler: F, + // ) where + // T::Event: std::fmt::Debug, + // F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, + // { + // let dock = match panel.position(cx) { + // DockPosition::Left => &self.left_dock, + // DockPosition::Bottom => &self.bottom_dock, + // DockPosition::Right => &self.right_dock, + // }; + + // self.subscriptions.push(cx.subscribe(&panel, { + // let mut dock = dock.clone(); + // let mut prev_position = panel.position(cx); + // move |this, panel, event, cx| { + // if T::should_change_position_on_event(event) { + // let new_position = panel.read(cx).position(cx); + // let mut was_visible = false; + // dock.update(cx, |dock, cx| { + // prev_position = new_position; + + // was_visible = dock.is_open() + // && dock + // .visible_panel() + // .map_or(false, |active_panel| active_panel.id() == panel.id()); + // dock.remove_panel(&panel, cx); + // }); + + // if panel.is_zoomed(cx) { + // this.zoomed_position = Some(new_position); + // } + + // dock = match panel.read(cx).position(cx) { + // DockPosition::Left => &this.left_dock, + // DockPosition::Bottom => &this.bottom_dock, + // DockPosition::Right => &this.right_dock, + // } + // .clone(); + // dock.update(cx, |dock, cx| { + // dock.add_panel(panel.clone(), cx); + // if was_visible { + // dock.set_open(true, cx); + // dock.activate_panel(dock.panels_len() - 1, cx); + // } + // }); + // } else if T::should_zoom_in_on_event(event) { + // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); + // if !panel.has_focus(cx) { + // cx.focus(&panel); + // } + // this.zoomed = Some(panel.downgrade().into_any()); + // this.zoomed_position = Some(panel.read(cx).position(cx)); + // } else if T::should_zoom_out_on_event(event) { + // dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); + // if this.zoomed_position == Some(prev_position) { + // this.zoomed = None; + // this.zoomed_position = None; + // } + // cx.notify(); + // } else if T::is_focus_event(event) { + // let position = panel.read(cx).position(cx); + // this.dismiss_zoomed_items_to_reveal(Some(position), cx); + // if panel.is_zoomed(cx) { + // this.zoomed = Some(panel.downgrade().into_any()); + // this.zoomed_position = Some(position); + // } else { + // this.zoomed = None; + // this.zoomed_position = None; + // } + // this.update_active_view_for_followers(cx); + // cx.notify(); + // } else { + // handler(this, &panel, event, cx) + // } + // } + // })); + + // dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); + // } + + // pub fn status_bar(&self) -> &ViewHandle { + // &self.status_bar + // } + + // pub fn app_state(&self) -> &Arc { + // &self.app_state + // } + + // pub fn user_store(&self) -> &ModelHandle { + // &self.app_state.user_store + // } + + pub fn project(&self) -> &Handle { + &self.project + } + + // pub fn recent_navigation_history( + // &self, + // limit: Option, + // cx: &AppContext, + // ) -> Vec<(ProjectPath, Option)> { + // let mut abs_paths_opened: HashMap> = HashMap::default(); + // let mut history: HashMap, usize)> = HashMap::default(); + // for pane in &self.panes { + // let pane = pane.read(cx); + // pane.nav_history() + // .for_each_entry(cx, |entry, (project_path, fs_path)| { + // if let Some(fs_path) = &fs_path { + // abs_paths_opened + // .entry(fs_path.clone()) + // .or_default() + // .insert(project_path.clone()); + // } + // let timestamp = entry.timestamp; + // match history.entry(project_path) { + // hash_map::Entry::Occupied(mut entry) => { + // let (_, old_timestamp) = entry.get(); + // if ×tamp > old_timestamp { + // entry.insert((fs_path, timestamp)); + // } + // } + // hash_map::Entry::Vacant(entry) => { + // entry.insert((fs_path, timestamp)); + // } + // } + // }); + // } + + // history + // .into_iter() + // .sorted_by_key(|(_, (_, timestamp))| *timestamp) + // .map(|(project_path, (fs_path, _))| (project_path, fs_path)) + // .rev() + // .filter(|(history_path, abs_path)| { + // let latest_project_path_opened = abs_path + // .as_ref() + // .and_then(|abs_path| abs_paths_opened.get(abs_path)) + // .and_then(|project_paths| { + // project_paths + // .iter() + // .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id)) + // }); + + // match latest_project_path_opened { + // Some(latest_project_path_opened) => latest_project_path_opened == history_path, + // None => true, + // } + // }) + // .take(limit.unwrap_or(usize::MAX)) + // .collect() + // } + + // fn navigate_history( + // &mut self, + // pane: WeakViewHandle, + // mode: NavigationMode, + // cx: &mut ViewContext, + // ) -> Task> { + // let to_load = if let Some(pane) = pane.upgrade(cx) { + // cx.focus(&pane); + + // pane.update(cx, |pane, cx| { + // loop { + // // Retrieve the weak item handle from the history. + // let entry = pane.nav_history_mut().pop(mode, cx)?; + + // // If the item is still present in this pane, then activate it. + // if let Some(index) = entry + // .item + // .upgrade(cx) + // .and_then(|v| pane.index_for_item(v.as_ref())) + // { + // let prev_active_item_index = pane.active_item_index(); + // pane.nav_history_mut().set_mode(mode); + // pane.activate_item(index, true, true, cx); + // pane.nav_history_mut().set_mode(NavigationMode::Normal); + + // let mut navigated = prev_active_item_index != pane.active_item_index(); + // if let Some(data) = entry.data { + // navigated |= pane.active_item()?.navigate(data, cx); + // } + + // if navigated { + // break None; + // } + // } + // // If the item is no longer present in this pane, then retrieve its + // // project path in order to reopen it. + // else { + // break pane + // .nav_history() + // .path_for_item(entry.item.id()) + // .map(|(project_path, _)| (project_path, entry)); + // } + // } + // }) + // } else { + // None + // }; + + // if let Some((project_path, entry)) = to_load { + // // If the item was no longer present, then load it again from its previous path. + // let task = self.load_path(project_path, cx); + // cx.spawn(|workspace, mut cx| async move { + // let task = task.await; + // let mut navigated = false; + // if let Some((project_entry_id, build_item)) = task.log_err() { + // let prev_active_item_id = pane.update(&mut cx, |pane, _| { + // pane.nav_history_mut().set_mode(mode); + // pane.active_item().map(|p| p.id()) + // })?; + + // pane.update(&mut cx, |pane, cx| { + // let item = pane.open_item(project_entry_id, true, cx, build_item); + // navigated |= Some(item.id()) != prev_active_item_id; + // pane.nav_history_mut().set_mode(NavigationMode::Normal); + // if let Some(data) = entry.data { + // navigated |= item.navigate(data, cx); + // } + // })?; + // } + + // if !navigated { + // workspace + // .update(&mut cx, |workspace, cx| { + // Self::navigate_history(workspace, pane, mode, cx) + // })? + // .await?; + // } + + // Ok(()) + // }) + // } else { + // Task::ready(Ok(())) + // } + // } + + // pub fn go_back( + // &mut self, + // pane: WeakViewHandle, + // cx: &mut ViewContext, + // ) -> Task> { + // self.navigate_history(pane, NavigationMode::GoingBack, cx) + // } + + // pub fn go_forward( + // &mut self, + // pane: WeakViewHandle, + // cx: &mut ViewContext, + // ) -> Task> { + // self.navigate_history(pane, NavigationMode::GoingForward, cx) + // } + + // pub fn reopen_closed_item(&mut self, cx: &mut ViewContext) -> Task> { + // self.navigate_history( + // self.active_pane().downgrade(), + // NavigationMode::ReopeningClosedItem, + // cx, + // ) + // } + + // pub fn client(&self) -> &Client { + // &self.app_state.client + // } + + // pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext) { + // self.titlebar_item = Some(item); + // cx.notify(); + // } + + // pub fn titlebar_item(&self) -> Option { + // self.titlebar_item.clone() + // } + + // /// Call the given callback with a workspace whose project is local. + // /// + // /// If the given workspace has a local project, then it will be passed + // /// to the callback. Otherwise, a new empty window will be created. + // pub fn with_local_workspace( + // &mut self, + // cx: &mut ViewContext, + // callback: F, + // ) -> Task> + // where + // T: 'static, + // F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, + // { + // if self.project.read(cx).is_local() { + // Task::Ready(Some(Ok(callback(self, cx)))) + // } else { + // let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); + // cx.spawn(|_vh, mut cx| async move { + // let (workspace, _) = task.await; + // workspace.update(&mut cx, callback) + // }) + // } + // } + + // pub fn worktrees<'a>( + // &self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.project.read(cx).worktrees(cx) + // } + + // pub fn visible_worktrees<'a>( + // &self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.project.read(cx).visible_worktrees(cx) + // } + + // pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { + // let futures = self + // .worktrees(cx) + // .filter_map(|worktree| worktree.read(cx).as_local()) + // .map(|worktree| worktree.scan_complete()) + // .collect::>(); + // async move { + // for future in futures { + // future.await; + // } + // } + // } + + // pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { + // cx.spawn(|mut cx| async move { + // let window = cx + // .windows() + // .into_iter() + // .find(|window| window.is_active(&cx).unwrap_or(false)); + // if let Some(window) = window { + // //This can only get called when the window's project connection has been lost + // //so we don't need to prompt the user for anything and instead just close the window + // window.remove(&mut cx); + // } + // }) + // .detach(); + // } + + // pub fn close( + // &mut self, + // _: &CloseWindow, + // cx: &mut ViewContext, + // ) -> Option>> { + // let window = cx.window(); + // let prepare = self.prepare_to_close(false, cx); + // Some(cx.spawn(|_, mut cx| async move { + // if prepare.await? { + // window.remove(&mut cx); + // } + // Ok(()) + // })) + // } + + // pub fn prepare_to_close( + // &mut self, + // quitting: bool, + // cx: &mut ViewContext, + // ) -> Task> { + // let active_call = self.active_call().cloned(); + // let window = cx.window(); + + // cx.spawn(|this, mut cx| async move { + // let workspace_count = cx + // .windows() + // .into_iter() + // .filter(|window| window.root_is::()) + // .count(); + + // if let Some(active_call) = active_call { + // if !quitting + // && workspace_count == 1 + // && active_call.read_with(&cx, |call, _| call.room().is_some()) + // { + // let answer = window.prompt( + // PromptLevel::Warning, + // "Do you want to leave the current call?", + // &["Close window and hang up", "Cancel"], + // &mut cx, + // ); + + // if let Some(mut answer) = answer { + // if answer.next().await == Some(1) { + // return anyhow::Ok(false); + // } else { + // active_call + // .update(&mut cx, |call, cx| call.hang_up(cx)) + // .await + // .log_err(); + // } + // } + // } + // } + + // Ok(this + // .update(&mut cx, |this, cx| { + // this.save_all_internal(SaveIntent::Close, cx) + // })? + // .await?) + // }) + // } + + // fn save_all( + // &mut self, + // action: &SaveAll, + // cx: &mut ViewContext, + // ) -> Option>> { + // let save_all = + // self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx); + // Some(cx.foreground().spawn(async move { + // save_all.await?; + // Ok(()) + // })) + // } + + // fn save_all_internal( + // &mut self, + // mut save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // if self.project.read(cx).is_read_only() { + // return Task::ready(Ok(true)); + // } + // let dirty_items = self + // .panes + // .iter() + // .flat_map(|pane| { + // pane.read(cx).items().filter_map(|item| { + // if item.is_dirty(cx) { + // Some((pane.downgrade(), item.boxed_clone())) + // } else { + // None + // } + // }) + // }) + // .collect::>(); + + // let project = self.project.clone(); + // cx.spawn(|workspace, mut cx| async move { + // // Override save mode and display "Save all files" prompt + // if save_intent == SaveIntent::Close && dirty_items.len() > 1 { + // let mut answer = workspace.update(&mut cx, |_, cx| { + // let prompt = Pane::file_names_for_prompt( + // &mut dirty_items.iter().map(|(_, handle)| handle), + // dirty_items.len(), + // cx, + // ); + // cx.prompt( + // PromptLevel::Warning, + // &prompt, + // &["Save all", "Discard all", "Cancel"], + // ) + // })?; + // match answer.next().await { + // Some(0) => save_intent = SaveIntent::SaveAll, + // Some(1) => save_intent = SaveIntent::Skip, + // _ => {} + // } + // } + // for (pane, item) in dirty_items { + // let (singleton, project_entry_ids) = + // cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); + // if singleton || !project_entry_ids.is_empty() { + // if let Some(ix) = + // pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))? + // { + // if !Pane::save_item( + // project.clone(), + // &pane, + // ix, + // &*item, + // save_intent, + // &mut cx, + // ) + // .await? + // { + // return Ok(false); + // } + // } + // } + // } + // Ok(true) + // }) + // } + + // pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { + // let mut paths = cx.prompt_for_paths(PathPromptOptions { + // files: true, + // directories: true, + // multiple: true, + // }); + + // Some(cx.spawn(|this, mut cx| async move { + // if let Some(paths) = paths.recv().await.flatten() { + // if let Some(task) = this + // .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) + // .log_err() + // { + // task.await? + // } + // } + // Ok(()) + // })) + // } + + // pub fn open_workspace_for_paths( + // &mut self, + // paths: Vec, + // cx: &mut ViewContext, + // ) -> Task> { + // let window = cx.window().downcast::(); + // let is_remote = self.project.read(cx).is_remote(); + // let has_worktree = self.project.read(cx).worktrees(cx).next().is_some(); + // let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); + // let close_task = if is_remote || has_worktree || has_dirty_items { + // None + // } else { + // Some(self.prepare_to_close(false, cx)) + // }; + // let app_state = self.app_state.clone(); + + // cx.spawn(|_, mut cx| async move { + // let window_to_replace = if let Some(close_task) = close_task { + // if !close_task.await? { + // return Ok(()); + // } + // window + // } else { + // None + // }; + // cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx)) + // .await?; + // Ok(()) + // }) + // } + + #[allow(clippy::type_complexity)] + pub fn open_paths( + &mut self, + mut abs_paths: Vec, + visible: bool, + cx: &mut ViewContext, + ) -> Task, anyhow::Error>>>> { + log::info!("open paths {:?}", abs_paths); + + let fs = self.app_state.fs.clone(); + + // Sort the paths to ensure we add worktrees for parents before their children. + abs_paths.sort_unstable(); + cx.spawn(|this, mut cx| async move { + let mut tasks = Vec::with_capacity(abs_paths.len()); + for abs_path in &abs_paths { + let project_path = match this + .update(&mut cx, |this, cx| { + Workspace::project_path_for_path( + this.project.clone(), + abs_path, + visible, + cx, + ) + }) + .log_err() + { + Some(project_path) => project_path.await.log_err(), + None => None, + }; + + let this = this.clone(); + let task = cx.spawn(|mut cx| { + let fs = fs.clone(); + let abs_path = abs_path.clone(); + async move { + let (worktree, project_path) = project_path?; + if fs.is_file(&abs_path).await { + Some( + this.update(&mut cx, |this, cx| { + this.open_path(project_path, None, true, cx) + }) + .log_err()? + .await, + ) + } else { + this.update(&mut cx, |workspace, cx| { + let worktree = worktree.read(cx); + let worktree_abs_path = worktree.abs_path(); + let entry_id = if abs_path == worktree_abs_path.as_ref() { + worktree.root_entry() + } else { + abs_path + .strip_prefix(worktree_abs_path.as_ref()) + .ok() + .and_then(|relative_path| { + worktree.entry_for_path(relative_path) + }) + } + .map(|entry| entry.id); + if let Some(entry_id) = entry_id { + workspace.project.update(cx, |_, cx| { + cx.emit(project2::Event::ActiveEntryChanged(Some( + entry_id, + ))); + }) + } + }) + .log_err()?; + None + } + } + }); + tasks.push(task); + } + + futures::future::join_all(tasks).await + }) + } + + // fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { + // let mut paths = cx.prompt_for_paths(PathPromptOptions { + // files: false, + // directories: true, + // multiple: true, + // }); + // cx.spawn(|this, mut cx| async move { + // if let Some(paths) = paths.recv().await.flatten() { + // let results = this + // .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? + // .await; + // for result in results.into_iter().flatten() { + // result.log_err(); + // } + // } + // anyhow::Ok(()) + // }) + // .detach_and_log_err(cx); + // } + + fn project_path_for_path( + project: Handle, + abs_path: &Path, + visible: bool, + cx: &mut AppContext, + ) -> Task, ProjectPath)>> { + let entry = project.update(cx, |project, cx| { + project.find_or_create_local_worktree(abs_path, visible, cx) + }); + cx.spawn(|cx| async move { + let (worktree, path) = entry.await?; + let worktree_id = worktree.update(&mut cx, |t, _| t.id())?; + Ok(( + worktree, + ProjectPath { + worktree_id, + path: path.into(), + }, + )) + }) + } + + // /// Returns the modal that was toggled closed if it was open. + // pub fn toggle_modal( + // &mut self, + // cx: &mut ViewContext, + // add_view: F, + // ) -> Option> + // where + // V: 'static + Modal, + // F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, + // { + // cx.notify(); + // // Whatever modal was visible is getting clobbered. If its the same type as V, then return + // // it. Otherwise, create a new modal and set it as active. + // if let Some(already_open_modal) = self + // .dismiss_modal(cx) + // .and_then(|modal| modal.downcast::()) + // { + // cx.focus_self(); + // Some(already_open_modal) + // } else { + // let modal = add_view(self, cx); + // cx.subscribe(&modal, |this, _, event, cx| { + // if V::dismiss_on_event(event) { + // this.dismiss_modal(cx); + // } + // }) + // .detach(); + // let previously_focused_view_id = cx.focused_view_id(); + // cx.focus(&modal); + // self.modal = Some(ActiveModal { + // view: Box::new(modal), + // previously_focused_view_id, + // }); + // None + // } + // } + + // pub fn modal(&self) -> Option> { + // self.modal + // .as_ref() + // .and_then(|modal| modal.view.as_any().clone().downcast::()) + // } + + // pub fn dismiss_modal(&mut self, cx: &mut ViewContext) -> Option { + // if let Some(modal) = self.modal.take() { + // if let Some(previously_focused_view_id) = modal.previously_focused_view_id { + // if modal.view.has_focus(cx) { + // cx.window_context().focus(Some(previously_focused_view_id)); + // } + // } + // cx.notify(); + // Some(modal.view.as_any().clone()) + // } else { + // None + // } + // } + + // pub fn items<'a>( + // &'a self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.panes.iter().flat_map(|pane| pane.read(cx).items()) + // } + + // pub fn item_of_type(&self, cx: &AppContext) -> Option> { + // self.items_of_type(cx).max_by_key(|item| item.id()) + // } + + // pub fn items_of_type<'a, T: Item>( + // &'a self, + // cx: &'a AppContext, + // ) -> impl 'a + Iterator> { + // self.panes + // .iter() + // .flat_map(|pane| pane.read(cx).items_of_type()) + // } + + // pub fn active_item(&self, cx: &AppContext) -> Option> { + // self.active_pane().read(cx).active_item() + // } + + // fn active_project_path(&self, cx: &ViewContext) -> Option { + // self.active_item(cx).and_then(|item| item.project_path(cx)) + // } + + // pub fn save_active_item( + // &mut self, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Task> { + // let project = self.project.clone(); + // let pane = self.active_pane(); + // let item_ix = pane.read(cx).active_item_index(); + // let item = pane.read(cx).active_item(); + // let pane = pane.downgrade(); + + // cx.spawn(|_, mut cx| async move { + // if let Some(item) = item { + // Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx) + // .await + // .map(|_| ()) + // } else { + // Ok(()) + // } + // }) + // } + + // pub fn close_inactive_items_and_panes( + // &mut self, + // _: &CloseInactiveTabsAndPanes, + // cx: &mut ViewContext, + // ) -> Option>> { + // self.close_all_internal(true, SaveIntent::Close, cx) + // } + + // pub fn close_all_items_and_panes( + // &mut self, + // action: &CloseAllItemsAndPanes, + // cx: &mut ViewContext, + // ) -> Option>> { + // self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) + // } + + // fn close_all_internal( + // &mut self, + // retain_active_pane: bool, + // save_intent: SaveIntent, + // cx: &mut ViewContext, + // ) -> Option>> { + // let current_pane = self.active_pane(); + + // let mut tasks = Vec::new(); + + // if retain_active_pane { + // if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| { + // pane.close_inactive_items(&CloseInactiveItems, cx) + // }) { + // tasks.push(current_pane_close); + // }; + // } + + // for pane in self.panes() { + // if retain_active_pane && pane.id() == current_pane.id() { + // continue; + // } + + // if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| { + // pane.close_all_items( + // &CloseAllItems { + // save_intent: Some(save_intent), + // }, + // cx, + // ) + // }) { + // tasks.push(close_pane_items) + // } + // } + + // if tasks.is_empty() { + // None + // } else { + // Some(cx.spawn(|_, _| async move { + // for task in tasks { + // task.await? + // } + // Ok(()) + // })) + // } + // } + + // pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { + // let dock = match dock_side { + // DockPosition::Left => &self.left_dock, + // DockPosition::Bottom => &self.bottom_dock, + // DockPosition::Right => &self.right_dock, + // }; + // let mut focus_center = false; + // let mut reveal_dock = false; + // dock.update(cx, |dock, cx| { + // let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); + // let was_visible = dock.is_open() && !other_is_zoomed; + // dock.set_open(!was_visible, cx); + + // if let Some(active_panel) = dock.active_panel() { + // if was_visible { + // if active_panel.has_focus(cx) { + // focus_center = true; + // } + // } else { + // cx.focus(active_panel.as_any()); + // reveal_dock = true; + // } + // } + // }); + + // if reveal_dock { + // self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); + // } + + // if focus_center { + // cx.focus_self(); + // } + + // cx.notify(); + // self.serialize_workspace(cx); + // } + + // pub fn close_all_docks(&mut self, cx: &mut ViewContext) { + // let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + + // for dock in docks { + // dock.update(cx, |dock, cx| { + // dock.set_open(false, cx); + // }); + // } + + // cx.focus_self(); + // cx.notify(); + // self.serialize_workspace(cx); + // } + + // /// Transfer focus to the panel of the given type. + // pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { + // self.focus_or_unfocus_panel::(cx, |_, _| true)? + // .as_any() + // .clone() + // .downcast() + // } + + // /// Focus the panel of the given type if it isn't already focused. If it is + // /// already focused, then transfer focus back to the workspace center. + // pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { + // self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); + // } + + // /// Focus or unfocus the given panel type, depending on the given callback. + // fn focus_or_unfocus_panel( + // &mut self, + // cx: &mut ViewContext, + // should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, + // ) -> Option> { + // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + // if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { + // let mut focus_center = false; + // let mut reveal_dock = false; + // let panel = dock.update(cx, |dock, cx| { + // dock.activate_panel(panel_index, cx); + + // let panel = dock.active_panel().cloned(); + // if let Some(panel) = panel.as_ref() { + // if should_focus(&**panel, cx) { + // dock.set_open(true, cx); + // cx.focus(panel.as_any()); + // reveal_dock = true; + // } else { + // // if panel.is_zoomed(cx) { + // // dock.set_open(false, cx); + // // } + // focus_center = true; + // } + // } + // panel + // }); + + // if focus_center { + // cx.focus_self(); + // } + + // self.serialize_workspace(cx); + // cx.notify(); + // return panel; + // } + // } + // None + // } + + // pub fn panel(&self, cx: &WindowContext) -> Option> { + // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + // let dock = dock.read(cx); + // if let Some(panel) = dock.panel::() { + // return Some(panel); + // } + // } + // None + // } + + // fn zoom_out(&mut self, cx: &mut ViewContext) { + // for pane in &self.panes { + // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + // } + + // self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + // self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + // self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); + // self.zoomed = None; + // self.zoomed_position = None; + + // cx.notify(); + // } + + // #[cfg(any(test, feature = "test-support"))] + // pub fn zoomed_view(&self, cx: &AppContext) -> Option { + // self.zoomed.and_then(|view| view.upgrade(cx)) + // } + + // fn dismiss_zoomed_items_to_reveal( + // &mut self, + // dock_to_reveal: Option, + // cx: &mut ViewContext, + // ) { + // // If a center pane is zoomed, unzoom it. + // for pane in &self.panes { + // if pane != &self.active_pane || dock_to_reveal.is_some() { + // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + // } + // } + + // // If another dock is zoomed, hide it. + // let mut focus_center = false; + // for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { + // dock.update(cx, |dock, cx| { + // if Some(dock.position()) != dock_to_reveal { + // if let Some(panel) = dock.active_panel() { + // if panel.is_zoomed(cx) { + // focus_center |= panel.has_focus(cx); + // dock.set_open(false, cx); + // } + // } + // } + // }); + // } + + // if focus_center { + // cx.focus_self(); + // } + + // if self.zoomed_position != dock_to_reveal { + // self.zoomed = None; + // self.zoomed_position = None; + // } + + // cx.notify(); + // } + + // fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { + // let pane = cx.add_view(|cx| { + // Pane::new( + // self.weak_handle(), + // self.project.clone(), + // self.pane_history_timestamp.clone(), + // cx, + // ) + // }); + // cx.subscribe(&pane, Self::handle_pane_event).detach(); + // self.panes.push(pane.clone()); + // cx.focus(&pane); + // cx.emit(Event::PaneAdded(pane.clone())); + // pane + // } + + // pub fn add_item_to_center( + // &mut self, + // item: Box, + // cx: &mut ViewContext, + // ) -> bool { + // if let Some(center_pane) = self.last_active_center_pane.clone() { + // if let Some(center_pane) = center_pane.upgrade(cx) { + // center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); + // true + // } else { + // false + // } + // } else { + // false + // } + // } + + // pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { + // self.active_pane + // .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); + // } + + // pub fn split_item( + // &mut self, + // split_direction: SplitDirection, + // item: Box, + // cx: &mut ViewContext, + // ) { + // let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); + // new_pane.update(cx, move |new_pane, cx| { + // new_pane.add_item(item, true, true, None, cx) + // }) + // } + + // pub fn open_abs_path( + // &mut self, + // abs_path: PathBuf, + // visible: bool, + // cx: &mut ViewContext, + // ) -> Task>> { + // cx.spawn(|workspace, mut cx| async move { + // let open_paths_task_result = workspace + // .update(&mut cx, |workspace, cx| { + // workspace.open_paths(vec![abs_path.clone()], visible, cx) + // }) + // .with_context(|| format!("open abs path {abs_path:?} task spawn"))? + // .await; + // anyhow::ensure!( + // open_paths_task_result.len() == 1, + // "open abs path {abs_path:?} task returned incorrect number of results" + // ); + // match open_paths_task_result + // .into_iter() + // .next() + // .expect("ensured single task result") + // { + // Some(open_result) => { + // open_result.with_context(|| format!("open abs path {abs_path:?} task join")) + // } + // None => anyhow::bail!("open abs path {abs_path:?} task returned None"), + // } + // }) + // } + + // pub fn split_abs_path( + // &mut self, + // abs_path: PathBuf, + // visible: bool, + // cx: &mut ViewContext, + // ) -> Task>> { + // let project_path_task = + // Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); + // cx.spawn(|this, mut cx| async move { + // let (_, path) = project_path_task.await?; + // this.update(&mut cx, |this, cx| this.split_path(path, cx))? + // .await + // }) + // } + + pub fn open_path( + &mut self, + path: impl Into, + pane: Option>, + focus_item: bool, + cx: &mut ViewContext, + ) -> Task, anyhow::Error>> { + let pane = pane.unwrap_or_else(|| { + self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }) + }); + + let task = self.load_path(path.into(), cx); + cx.spawn(|_, mut cx| async move { + let (project_entry_id, build_item) = task.await?; + pane.update(&mut cx, |pane, cx| { + pane.open_item(project_entry_id, focus_item, cx, build_item) + }) + }) + } + + // pub fn split_path( + // &mut self, + // path: impl Into, + // cx: &mut ViewContext, + // ) -> Task, anyhow::Error>> { + // let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { + // self.panes + // .first() + // .expect("There must be an active pane") + // .downgrade() + // }); + + // if let Member::Pane(center_pane) = &self.center.root { + // if center_pane.read(cx).items_len() == 0 { + // return self.open_path(path, Some(pane), true, cx); + // } + // } + + // let task = self.load_path(path.into(), cx); + // cx.spawn(|this, mut cx| async move { + // let (project_entry_id, build_item) = task.await?; + // this.update(&mut cx, move |this, cx| -> Option<_> { + // let pane = pane.upgrade(cx)?; + // let new_pane = this.split_pane(pane, SplitDirection::Right, cx); + // new_pane.update(cx, |new_pane, cx| { + // Some(new_pane.open_item(project_entry_id, true, cx, build_item)) + // }) + // }) + // .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? + // }) + // } + + pub(crate) fn load_path( + &mut self, + path: ProjectPath, + cx: &mut ViewContext, + ) -> Task< + Result<( + ProjectEntryId, + impl 'static + FnOnce(&mut ViewContext) -> Box, + )>, + > { + let project = self.project().clone(); + let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); + cx.spawn(|_, mut cx| async move { + let (project_entry_id, project_item) = project_item.await?; + let build_item = cx.update(|cx| { + cx.default_global::() + .get(&project_item.model_type()) + .ok_or_else(|| anyhow!("no item builder for project item")) + .cloned() + })?; + let build_item = + move |cx: &mut ViewContext| build_item(project, project_item, cx); + Ok((project_entry_id, build_item)) + }) + } + + // pub fn open_project_item( + // &mut self, + // project_item: ModelHandle, + // cx: &mut ViewContext, + // ) -> ViewHandle + // where + // T: ProjectItem, + // { + // use project::Item as _; + + // let entry_id = project_item.read(cx).entry_id(cx); + // if let Some(item) = entry_id + // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + // .and_then(|item| item.downcast()) + // { + // self.activate_item(&item, cx); + // return item; + // } + + // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + // self.add_item(Box::new(item.clone()), cx); + // item + // } + + // pub fn split_project_item( + // &mut self, + // project_item: ModelHandle, + // cx: &mut ViewContext, + // ) -> ViewHandle + // where + // T: ProjectItem, + // { + // use project::Item as _; + + // let entry_id = project_item.read(cx).entry_id(cx); + // if let Some(item) = entry_id + // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + // .and_then(|item| item.downcast()) + // { + // self.activate_item(&item, cx); + // return item; + // } + + // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + // self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); + // item + // } + + // pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + // if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { + // self.active_pane.update(cx, |pane, cx| { + // pane.add_item(Box::new(shared_screen), false, true, None, cx) + // }); + // } + // } + + // pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { + // let result = self.panes.iter().find_map(|pane| { + // pane.read(cx) + // .index_for_item(item) + // .map(|ix| (pane.clone(), ix)) + // }); + // if let Some((pane, ix)) = result { + // pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); + // true + // } else { + // false + // } + // } + + // fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { + // let panes = self.center.panes(); + // if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { + // cx.focus(&pane); + // } else { + // self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); + // } + // } + + // pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { + // let panes = self.center.panes(); + // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + // let next_ix = (ix + 1) % panes.len(); + // let next_pane = panes[next_ix].clone(); + // cx.focus(&next_pane); + // } + // } + + // pub fn activate_previous_pane(&mut self, cx: &mut ViewContext) { + // let panes = self.center.panes(); + // if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) { + // let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1); + // let prev_pane = panes[prev_ix].clone(); + // cx.focus(&prev_pane); + // } + // } + + // pub fn activate_pane_in_direction( + // &mut self, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) { + // if let Some(pane) = self.find_pane_in_direction(direction, cx) { + // cx.focus(pane); + // } + // } + + // pub fn swap_pane_in_direction( + // &mut self, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) { + // if let Some(to) = self + // .find_pane_in_direction(direction, cx) + // .map(|pane| pane.clone()) + // { + // self.center.swap(&self.active_pane.clone(), &to); + // cx.notify(); + // } + // } + + // fn find_pane_in_direction( + // &mut self, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> Option<&ViewHandle> { + // let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { + // return None; + // }; + // let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); + // let center = match cursor { + // Some(cursor) if bounding_box.contains_point(cursor) => cursor, + // _ => bounding_box.center(), + // }; + + // let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.; + + // let target = match direction { + // SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()), + // SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()), + // SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next), + // SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next), + // }; + // self.center.pane_at_pixel_position(target) + // } + + // fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { + // if self.active_pane != pane { + // self.active_pane = pane.clone(); + // self.status_bar.update(cx, |status_bar, cx| { + // status_bar.set_active_pane(&self.active_pane, cx); + // }); + // self.active_item_path_changed(cx); + // self.last_active_center_pane = Some(pane.downgrade()); + // } + + // self.dismiss_zoomed_items_to_reveal(None, cx); + // if pane.read(cx).is_zoomed() { + // self.zoomed = Some(pane.downgrade().into_any()); + // } else { + // self.zoomed = None; + // } + // self.zoomed_position = None; + // self.update_active_view_for_followers(cx); + + // cx.notify(); + // } + + // fn handle_pane_event( + // &mut self, + // pane: ViewHandle, + // event: &pane::Event, + // cx: &mut ViewContext, + // ) { + // match event { + // pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), + // pane::Event::Split(direction) => { + // self.split_and_clone(pane, *direction, cx); + // } + // pane::Event::Remove => self.remove_pane(pane, cx), + // pane::Event::ActivateItem { local } => { + // if *local { + // self.unfollow(&pane, cx); + // } + // if &pane == self.active_pane() { + // self.active_item_path_changed(cx); + // } + // } + // pane::Event::ChangeItemTitle => { + // if pane == self.active_pane { + // self.active_item_path_changed(cx); + // } + // self.update_window_edited(cx); + // } + // pane::Event::RemoveItem { item_id } => { + // self.update_window_edited(cx); + // if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { + // if entry.get().id() == pane.id() { + // entry.remove(); + // } + // } + // } + // pane::Event::Focus => { + // self.handle_pane_focused(pane.clone(), cx); + // } + // pane::Event::ZoomIn => { + // if pane == self.active_pane { + // pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + // if pane.read(cx).has_focus() { + // self.zoomed = Some(pane.downgrade().into_any()); + // self.zoomed_position = None; + // } + // cx.notify(); + // } + // } + // pane::Event::ZoomOut => { + // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + // if self.zoomed_position.is_none() { + // self.zoomed = None; + // } + // cx.notify(); + // } + // } + + // self.serialize_workspace(cx); + // } + + // pub fn split_pane( + // &mut self, + // pane_to_split: ViewHandle, + // split_direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> ViewHandle { + // let new_pane = self.add_pane(cx); + // self.center + // .split(&pane_to_split, &new_pane, split_direction) + // .unwrap(); + // cx.notify(); + // new_pane + // } + + // pub fn split_and_clone( + // &mut self, + // pane: ViewHandle, + // direction: SplitDirection, + // cx: &mut ViewContext, + // ) -> Option> { + // let item = pane.read(cx).active_item()?; + // let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { + // let new_pane = self.add_pane(cx); + // new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); + // self.center.split(&pane, &new_pane, direction).unwrap(); + // Some(new_pane) + // } else { + // None + // }; + // cx.notify(); + // maybe_pane_handle + // } + + // pub fn split_pane_with_item( + // &mut self, + // pane_to_split: WeakViewHandle, + // split_direction: SplitDirection, + // from: WeakViewHandle, + // item_id_to_move: usize, + // cx: &mut ViewContext, + // ) { + // let Some(pane_to_split) = pane_to_split.upgrade(cx) else { + // return; + // }; + // let Some(from) = from.upgrade(cx) else { + // return; + // }; + + // let new_pane = self.add_pane(cx); + // self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); + // self.center + // .split(&pane_to_split, &new_pane, split_direction) + // .unwrap(); + // cx.notify(); + // } + + // pub fn split_pane_with_project_entry( + // &mut self, + // pane_to_split: WeakViewHandle, + // split_direction: SplitDirection, + // project_entry: ProjectEntryId, + // cx: &mut ViewContext, + // ) -> Option>> { + // let pane_to_split = pane_to_split.upgrade(cx)?; + // let new_pane = self.add_pane(cx); + // self.center + // .split(&pane_to_split, &new_pane, split_direction) + // .unwrap(); + + // let path = self.project.read(cx).path_for_entry(project_entry, cx)?; + // let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); + // Some(cx.foreground().spawn(async move { + // task.await?; + // Ok(()) + // })) + // } + + // pub fn move_item( + // &mut self, + // source: ViewHandle, + // destination: ViewHandle, + // item_id_to_move: usize, + // destination_index: usize, + // cx: &mut ViewContext, + // ) { + // let item_to_move = source + // .read(cx) + // .items() + // .enumerate() + // .find(|(_, item_handle)| item_handle.id() == item_id_to_move); + + // if item_to_move.is_none() { + // log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); + // return; + // } + // let (item_ix, item_handle) = item_to_move.unwrap(); + // let item_handle = item_handle.clone(); + + // if source != destination { + // // Close item from previous pane + // source.update(cx, |source, cx| { + // source.remove_item(item_ix, false, cx); + // }); + // } + + // // This automatically removes duplicate items in the pane + // destination.update(cx, |destination, cx| { + // destination.add_item(item_handle, true, true, Some(destination_index), cx); + // cx.focus_self(); + // }); + // } + + // fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { + // if self.center.remove(&pane).unwrap() { + // self.force_remove_pane(&pane, cx); + // self.unfollow(&pane, cx); + // self.last_leaders_by_pane.remove(&pane.downgrade()); + // for removed_item in pane.read(cx).items() { + // self.panes_by_item.remove(&removed_item.id()); + // } + + // cx.notify(); + // } else { + // self.active_item_path_changed(cx); + // } + // } + + // pub fn panes(&self) -> &[ViewHandle] { + // &self.panes + // } + + // pub fn active_pane(&self) -> &ViewHandle { + // &self.active_pane + // } + + // fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + // self.follower_states.retain(|_, state| { + // if state.leader_id == peer_id { + // for item in state.items_by_leader_view_id.values() { + // item.set_leader_peer_id(None, cx); + // } + // false + // } else { + // true + // } + // }); + // cx.notify(); + // } + + // fn start_following( + // &mut self, + // leader_id: PeerId, + // cx: &mut ViewContext, + // ) -> Option>> { + // let pane = self.active_pane().clone(); + + // self.last_leaders_by_pane + // .insert(pane.downgrade(), leader_id); + // self.unfollow(&pane, cx); + // self.follower_states.insert( + // pane.clone(), + // FollowerState { + // leader_id, + // active_view_id: None, + // items_by_leader_view_id: Default::default(), + // }, + // ); + // cx.notify(); + + // let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + // let project_id = self.project.read(cx).remote_id(); + // let request = self.app_state.client.request(proto::Follow { + // room_id, + // project_id, + // leader_id: Some(leader_id), + // }); + + // Some(cx.spawn(|this, mut cx| async move { + // let response = request.await?; + // this.update(&mut cx, |this, _| { + // let state = this + // .follower_states + // .get_mut(&pane) + // .ok_or_else(|| anyhow!("following interrupted"))?; + // state.active_view_id = if let Some(active_view_id) = response.active_view_id { + // Some(ViewId::from_proto(active_view_id)?) + // } else { + // None + // }; + // Ok::<_, anyhow::Error>(()) + // })??; + // Self::add_views_from_leader( + // this.clone(), + // leader_id, + // vec![pane], + // response.views, + // &mut cx, + // ) + // .await?; + // this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?; + // Ok(()) + // })) + // } + + // pub fn follow_next_collaborator( + // &mut self, + // _: &FollowNextCollaborator, + // cx: &mut ViewContext, + // ) -> Option>> { + // let collaborators = self.project.read(cx).collaborators(); + // let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) { + // let mut collaborators = collaborators.keys().copied(); + // for peer_id in collaborators.by_ref() { + // if peer_id == leader_id { + // break; + // } + // } + // collaborators.next() + // } else if let Some(last_leader_id) = + // self.last_leaders_by_pane.get(&self.active_pane.downgrade()) + // { + // if collaborators.contains_key(last_leader_id) { + // Some(*last_leader_id) + // } else { + // None + // } + // } else { + // None + // }; + + // let pane = self.active_pane.clone(); + // let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next()) + // else { + // return None; + // }; + // if Some(leader_id) == self.unfollow(&pane, cx) { + // return None; + // } + // self.follow(leader_id, cx) + // } + + // pub fn follow( + // &mut self, + // leader_id: PeerId, + // cx: &mut ViewContext, + // ) -> Option>> { + // let room = ActiveCall::global(cx).read(cx).room()?.read(cx); + // let project = self.project.read(cx); + + // let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else { + // return None; + // }; + + // let other_project_id = match remote_participant.location { + // call::ParticipantLocation::External => None, + // call::ParticipantLocation::UnsharedProject => None, + // call::ParticipantLocation::SharedProject { project_id } => { + // if Some(project_id) == project.remote_id() { + // None + // } else { + // Some(project_id) + // } + // } + // }; + + // // if they are active in another project, follow there. + // if let Some(project_id) = other_project_id { + // let app_state = self.app_state.clone(); + // return Some(crate::join_remote_project( + // project_id, + // remote_participant.user.id, + // app_state, + // cx, + // )); + // } + + // // if you're already following, find the right pane and focus it. + // for (pane, state) in &self.follower_states { + // if leader_id == state.leader_id { + // cx.focus(pane); + // return None; + // } + // } + + // // Otherwise, follow. + // self.start_following(leader_id, cx) + // } + + // pub fn unfollow( + // &mut self, + // pane: &ViewHandle, + // cx: &mut ViewContext, + // ) -> Option { + // let state = self.follower_states.remove(pane)?; + // let leader_id = state.leader_id; + // for (_, item) in state.items_by_leader_view_id { + // item.set_leader_peer_id(None, cx); + // } + + // if self + // .follower_states + // .values() + // .all(|state| state.leader_id != state.leader_id) + // { + // let project_id = self.project.read(cx).remote_id(); + // let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + // self.app_state + // .client + // .send(proto::Unfollow { + // room_id, + // project_id, + // leader_id: Some(leader_id), + // }) + // .log_err(); + // } + + // cx.notify(); + // Some(leader_id) + // } + + // pub fn is_being_followed(&self, peer_id: PeerId) -> bool { + // self.follower_states + // .values() + // .any(|state| state.leader_id == peer_id) + // } + + // fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { + // // TODO: There should be a better system in place for this + // // (https://github.com/zed-industries/zed/issues/1290) + // let is_fullscreen = cx.window_is_fullscreen(); + // let container_theme = if is_fullscreen { + // let mut container_theme = theme.titlebar.container; + // container_theme.padding.left = container_theme.padding.right; + // container_theme + // } else { + // theme.titlebar.container + // }; + + // enum TitleBar {} + // MouseEventHandler::new::(0, cx, |_, cx| { + // Stack::new() + // .with_children( + // self.titlebar_item + // .as_ref() + // .map(|item| ChildView::new(item, cx)), + // ) + // .contained() + // .with_style(container_theme) + // }) + // .on_click(MouseButton::Left, |event, _, cx| { + // if event.click_count == 2 { + // cx.zoom_window(); + // } + // }) + // .constrained() + // .with_height(theme.titlebar.height) + // .into_any_named("titlebar") + // } + + // fn active_item_path_changed(&mut self, cx: &mut ViewContext) { + // let active_entry = self.active_project_path(cx); + // self.project + // .update(cx, |project, cx| project.set_active_path(active_entry, cx)); + // self.update_window_title(cx); + // } + + // fn update_window_title(&mut self, cx: &mut ViewContext) { + // let project = self.project().read(cx); + // let mut title = String::new(); + + // if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { + // let filename = path + // .path + // .file_name() + // .map(|s| s.to_string_lossy()) + // .or_else(|| { + // Some(Cow::Borrowed( + // project + // .worktree_for_id(path.worktree_id, cx)? + // .read(cx) + // .root_name(), + // )) + // }); + + // if let Some(filename) = filename { + // title.push_str(filename.as_ref()); + // title.push_str(" — "); + // } + // } + + // for (i, name) in project.worktree_root_names(cx).enumerate() { + // if i > 0 { + // title.push_str(", "); + // } + // title.push_str(name); + // } + + // if title.is_empty() { + // title = "empty project".to_string(); + // } + + // if project.is_remote() { + // title.push_str(" ↙"); + // } else if project.is_shared() { + // title.push_str(" ↗"); + // } + + // cx.set_window_title(&title); + // } + + // fn update_window_edited(&mut self, cx: &mut ViewContext) { + // let is_edited = !self.project.read(cx).is_read_only() + // && self + // .items(cx) + // .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); + // if is_edited != self.window_edited { + // self.window_edited = is_edited; + // cx.set_window_edited(self.window_edited) + // } + // } + + // fn render_disconnected_overlay( + // &self, + // cx: &mut ViewContext, + // ) -> Option> { + // if self.project.read(cx).is_read_only() { + // enum DisconnectedOverlay {} + // Some( + // MouseEventHandler::new::(0, cx, |_, cx| { + // let theme = &theme::current(cx); + // Label::new( + // "Your connection to the remote project has been lost.", + // theme.workspace.disconnected_overlay.text.clone(), + // ) + // .aligned() + // .contained() + // .with_style(theme.workspace.disconnected_overlay.container) + // }) + // .with_cursor_style(CursorStyle::Arrow) + // .capture_all() + // .into_any_named("disconnected overlay"), + // ) + // } else { + // None + // } + // } + + // fn render_notifications( + // &self, + // theme: &theme::Workspace, + // cx: &AppContext, + // ) -> Option> { + // if self.notifications.is_empty() { + // None + // } else { + // Some( + // Flex::column() + // .with_children(self.notifications.iter().map(|(_, _, notification)| { + // ChildView::new(notification.as_any(), cx) + // .contained() + // .with_style(theme.notification) + // })) + // .constrained() + // .with_width(theme.notifications.width) + // .contained() + // .with_style(theme.notifications.container) + // .aligned() + // .bottom() + // .right() + // .into_any(), + // ) + // } + // } + + // // RPC handlers + + // fn handle_follow( + // &mut self, + // follower_project_id: Option, + // cx: &mut ViewContext, + // ) -> proto::FollowResponse { + // let client = &self.app_state.client; + // let project_id = self.project.read(cx).remote_id(); + + // let active_view_id = self.active_item(cx).and_then(|i| { + // Some( + // i.to_followable_item_handle(cx)? + // .remote_id(client, cx)? + // .to_proto(), + // ) + // }); + + // cx.notify(); + + // self.last_active_view_id = active_view_id.clone(); + // proto::FollowResponse { + // active_view_id, + // views: self + // .panes() + // .iter() + // .flat_map(|pane| { + // let leader_id = self.leader_for_pane(pane); + // pane.read(cx).items().filter_map({ + // let cx = &cx; + // move |item| { + // let item = item.to_followable_item_handle(cx)?; + // if (project_id.is_none() || project_id != follower_project_id) + // && item.is_project_item(cx) + // { + // return None; + // } + // let id = item.remote_id(client, cx)?.to_proto(); + // let variant = item.to_state_proto(cx)?; + // Some(proto::View { + // id: Some(id), + // leader_id, + // variant: Some(variant), + // }) + // } + // }) + // }) + // .collect(), + // } + // } + + // fn handle_update_followers( + // &mut self, + // leader_id: PeerId, + // message: proto::UpdateFollowers, + // _cx: &mut ViewContext, + // ) { + // self.leader_updates_tx + // .unbounded_send((leader_id, message)) + // .ok(); + // } + + // async fn process_leader_update( + // this: &WeakViewHandle, + // leader_id: PeerId, + // update: proto::UpdateFollowers, + // cx: &mut AsyncAppContext, + // ) -> Result<()> { + // match update.variant.ok_or_else(|| anyhow!("invalid update"))? { + // proto::update_followers::Variant::UpdateActiveView(update_active_view) => { + // this.update(cx, |this, _| { + // for (_, state) in &mut this.follower_states { + // if state.leader_id == leader_id { + // state.active_view_id = + // if let Some(active_view_id) = update_active_view.id.clone() { + // Some(ViewId::from_proto(active_view_id)?) + // } else { + // None + // }; + // } + // } + // anyhow::Ok(()) + // })??; + // } + // proto::update_followers::Variant::UpdateView(update_view) => { + // let variant = update_view + // .variant + // .ok_or_else(|| anyhow!("missing update view variant"))?; + // let id = update_view + // .id + // .ok_or_else(|| anyhow!("missing update view id"))?; + // let mut tasks = Vec::new(); + // this.update(cx, |this, cx| { + // let project = this.project.clone(); + // for (_, state) in &mut this.follower_states { + // if state.leader_id == leader_id { + // let view_id = ViewId::from_proto(id.clone())?; + // if let Some(item) = state.items_by_leader_view_id.get(&view_id) { + // tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); + // } + // } + // } + // anyhow::Ok(()) + // })??; + // try_join_all(tasks).await.log_err(); + // } + // proto::update_followers::Variant::CreateView(view) => { + // let panes = this.read_with(cx, |this, _| { + // this.follower_states + // .iter() + // .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) + // .cloned() + // .collect() + // })?; + // Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?; + // } + // } + // this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?; + // Ok(()) + // } + + // async fn add_views_from_leader( + // this: WeakViewHandle, + // leader_id: PeerId, + // panes: Vec>, + // views: Vec, + // cx: &mut AsyncAppContext, + // ) -> Result<()> { + // let this = this + // .upgrade(cx) + // .ok_or_else(|| anyhow!("workspace dropped"))?; + + // let item_builders = cx.update(|cx| { + // cx.default_global::() + // .values() + // .map(|b| b.0) + // .collect::>() + // }); + + // let mut item_tasks_by_pane = HashMap::default(); + // for pane in panes { + // let mut item_tasks = Vec::new(); + // let mut leader_view_ids = Vec::new(); + // for view in &views { + // let Some(id) = &view.id else { continue }; + // let id = ViewId::from_proto(id.clone())?; + // let mut variant = view.variant.clone(); + // if variant.is_none() { + // Err(anyhow!("missing view variant"))?; + // } + // for build_item in &item_builders { + // let task = cx + // .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); + // if let Some(task) = task { + // item_tasks.push(task); + // leader_view_ids.push(id); + // break; + // } else { + // assert!(variant.is_some()); + // } + // } + // } + + // item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids)); + // } + + // for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { + // let items = futures::future::try_join_all(item_tasks).await?; + // this.update(cx, |this, cx| { + // let state = this.follower_states.get_mut(&pane)?; + // for (id, item) in leader_view_ids.into_iter().zip(items) { + // item.set_leader_peer_id(Some(leader_id), cx); + // state.items_by_leader_view_id.insert(id, item); + // } + + // Some(()) + // }); + // } + // Ok(()) + // } + + // fn update_active_view_for_followers(&mut self, cx: &AppContext) { + // let mut is_project_item = true; + // let mut update = proto::UpdateActiveView::default(); + // if self.active_pane.read(cx).has_focus() { + // let item = self + // .active_item(cx) + // .and_then(|item| item.to_followable_item_handle(cx)); + // if let Some(item) = item { + // is_project_item = item.is_project_item(cx); + // update = proto::UpdateActiveView { + // id: item + // .remote_id(&self.app_state.client, cx) + // .map(|id| id.to_proto()), + // leader_id: self.leader_for_pane(&self.active_pane), + // }; + // } + // } + + // if update.id != self.last_active_view_id { + // self.last_active_view_id = update.id.clone(); + // self.update_followers( + // is_project_item, + // proto::update_followers::Variant::UpdateActiveView(update), + // cx, + // ); + // } + // } + + // fn update_followers( + // &self, + // project_only: bool, + // update: proto::update_followers::Variant, + // cx: &AppContext, + // ) -> Option<()> { + // let project_id = if project_only { + // self.project.read(cx).remote_id() + // } else { + // None + // }; + // self.app_state().workspace_store.read_with(cx, |store, cx| { + // store.update_followers(project_id, update, cx) + // }) + // } + + // pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { + // self.follower_states.get(pane).map(|state| state.leader_id) + // } + + // fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + // cx.notify(); + + // let call = self.active_call()?; + // let room = call.read(cx).room()?.read(cx); + // let participant = room.remote_participant_for_peer_id(leader_id)?; + // let mut items_to_activate = Vec::new(); + + // let leader_in_this_app; + // let leader_in_this_project; + // match participant.location { + // call::ParticipantLocation::SharedProject { project_id } => { + // leader_in_this_app = true; + // leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); + // } + // call::ParticipantLocation::UnsharedProject => { + // leader_in_this_app = true; + // leader_in_this_project = false; + // } + // call::ParticipantLocation::External => { + // leader_in_this_app = false; + // leader_in_this_project = false; + // } + // }; + + // for (pane, state) in &self.follower_states { + // if state.leader_id != leader_id { + // continue; + // } + // if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + // if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + // if leader_in_this_project || !item.is_project_item(cx) { + // items_to_activate.push((pane.clone(), item.boxed_clone())); + // } + // } else { + // log::warn!( + // "unknown view id {:?} for leader {:?}", + // active_view_id, + // leader_id + // ); + // } + // continue; + // } + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + // } + + // for (pane, item) in items_to_activate { + // let pane_was_focused = pane.read(cx).has_focus(); + // if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + // pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + // } else { + // pane.update(cx, |pane, cx| { + // pane.add_item(item.boxed_clone(), false, false, None, cx) + // }); + // } + + // if pane_was_focused { + // pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + // } + // } + + // None + // } + + // fn shared_screen_for_peer( + // &self, + // peer_id: PeerId, + // pane: &ViewHandle, + // cx: &mut ViewContext, + // ) -> Option> { + // let call = self.active_call()?; + // let room = call.read(cx).room()?.read(cx); + // let participant = room.remote_participant_for_peer_id(peer_id)?; + // let track = participant.video_tracks.values().next()?.clone(); + // let user = participant.user.clone(); + + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Some(item); + // } + // } + + // Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + // } + + // pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { + // if active { + // self.update_active_view_for_followers(cx); + // cx.background() + // .spawn(persistence::DB.update_timestamp(self.database_id())) + // .detach(); + // } else { + // for pane in &self.panes { + // pane.update(cx, |pane, cx| { + // if let Some(item) = pane.active_item() { + // item.workspace_deactivated(cx); + // } + // if matches!( + // settings::get::(cx).autosave, + // AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange + // ) { + // for item in pane.items() { + // Pane::autosave_item(item.as_ref(), self.project.clone(), cx) + // .detach_and_log_err(cx); + // } + // } + // }); + // } + // } + // } + + // fn active_call(&self) -> Option<&ModelHandle> { + // self.active_call.as_ref().map(|(call, _)| call) + // } + + // fn on_active_call_event( + // &mut self, + // _: ModelHandle, + // event: &call::room::Event, + // cx: &mut ViewContext, + // ) { + // match event { + // call::room::Event::ParticipantLocationChanged { participant_id } + // | call::room::Event::RemoteVideoTracksChanged { participant_id } => { + // self.leader_updated(*participant_id, cx); + // } + // _ => {} + // } + // } + + // pub fn database_id(&self) -> WorkspaceId { + // self.database_id + // } + + // fn location(&self, cx: &AppContext) -> Option { + // let project = self.project().read(cx); + + // if project.is_local() { + // Some( + // project + // .visible_worktrees(cx) + // .map(|worktree| worktree.read(cx).abs_path()) + // .collect::>() + // .into(), + // ) + // } else { + // None + // } + // } + + // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { + // match member { + // Member::Axis(PaneAxis { members, .. }) => { + // for child in members.iter() { + // self.remove_panes(child.clone(), cx) + // } + // } + // Member::Pane(pane) => { + // self.force_remove_pane(&pane, cx); + // } + // } + // } + + // fn force_remove_pane(&mut self, pane: &ViewHandle, cx: &mut ViewContext) { + // self.panes.retain(|p| p != pane); + // cx.focus(self.panes.last().unwrap()); + // if self.last_active_center_pane == Some(pane.downgrade()) { + // self.last_active_center_pane = None; + // } + // cx.notify(); + // } + + // fn schedule_serialize(&mut self, cx: &mut ViewContext) { + // self._schedule_serialize = Some(cx.spawn(|this, cx| async move { + // cx.background().timer(Duration::from_millis(100)).await; + // this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) + // .ok(); + // })); + // } + + // fn serialize_workspace(&self, cx: &ViewContext) { + // fn serialize_pane_handle( + // pane_handle: &ViewHandle, + // cx: &AppContext, + // ) -> SerializedPane { + // let (items, active) = { + // let pane = pane_handle.read(cx); + // let active_item_id = pane.active_item().map(|item| item.id()); + // ( + // pane.items() + // .filter_map(|item_handle| { + // Some(SerializedItem { + // kind: Arc::from(item_handle.serialized_item_kind()?), + // item_id: item_handle.id(), + // active: Some(item_handle.id()) == active_item_id, + // }) + // }) + // .collect::>(), + // pane.has_focus(), + // ) + // }; + + // SerializedPane::new(items, active) + // } + + // fn build_serialized_pane_group( + // pane_group: &Member, + // cx: &AppContext, + // ) -> SerializedPaneGroup { + // match pane_group { + // Member::Axis(PaneAxis { + // axis, + // members, + // flexes, + // bounding_boxes: _, + // }) => SerializedPaneGroup::Group { + // axis: *axis, + // children: members + // .iter() + // .map(|member| build_serialized_pane_group(member, cx)) + // .collect::>(), + // flexes: Some(flexes.borrow().clone()), + // }, + // Member::Pane(pane_handle) => { + // SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) + // } + // } + // } + + // fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { + // let left_dock = this.left_dock.read(cx); + // let left_visible = left_dock.is_open(); + // let left_active_panel = left_dock.visible_panel().and_then(|panel| { + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) + // }); + // let left_dock_zoom = left_dock + // .visible_panel() + // .map(|panel| panel.is_zoomed(cx)) + // .unwrap_or(false); + + // let right_dock = this.right_dock.read(cx); + // let right_visible = right_dock.is_open(); + // let right_active_panel = right_dock.visible_panel().and_then(|panel| { + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) + // }); + // let right_dock_zoom = right_dock + // .visible_panel() + // .map(|panel| panel.is_zoomed(cx)) + // .unwrap_or(false); + + // let bottom_dock = this.bottom_dock.read(cx); + // let bottom_visible = bottom_dock.is_open(); + // let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) + // }); + // let bottom_dock_zoom = bottom_dock + // .visible_panel() + // .map(|panel| panel.is_zoomed(cx)) + // .unwrap_or(false); + + // DockStructure { + // left: DockData { + // visible: left_visible, + // active_panel: left_active_panel, + // zoom: left_dock_zoom, + // }, + // right: DockData { + // visible: right_visible, + // active_panel: right_active_panel, + // zoom: right_dock_zoom, + // }, + // bottom: DockData { + // visible: bottom_visible, + // active_panel: bottom_active_panel, + // zoom: bottom_dock_zoom, + // }, + // } + // } + + // if let Some(location) = self.location(cx) { + // // Load bearing special case: + // // - with_local_workspace() relies on this to not have other stuff open + // // when you open your log + // if !location.paths().is_empty() { + // let center_group = build_serialized_pane_group(&self.center.root, cx); + // let docks = build_serialized_docks(self, cx); + + // let serialized_workspace = SerializedWorkspace { + // id: self.database_id, + // location, + // center_group, + // bounds: Default::default(), + // display: Default::default(), + // docks, + // }; + + // cx.background() + // .spawn(persistence::DB.save_workspace(serialized_workspace)) + // .detach(); + // } + // } + // } + + // pub(crate) fn load_workspace( + // workspace: WeakViewHandle, + // serialized_workspace: SerializedWorkspace, + // paths_to_open: Vec>, + // cx: &mut AppContext, + // ) -> Task>>>> { + // cx.spawn(|mut cx| async move { + // let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { + // ( + // workspace.project().clone(), + // workspace.last_active_center_pane.clone(), + // ) + // })?; + + // let mut center_group = None; + // let mut center_items = None; + // // Traverse the splits tree and add to things + // if let Some((group, active_pane, items)) = serialized_workspace + // .center_group + // .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) + // .await + // { + // center_items = Some(items); + // center_group = Some((group, active_pane)) + // } + + // let mut items_by_project_path = cx.read(|cx| { + // center_items + // .unwrap_or_default() + // .into_iter() + // .filter_map(|item| { + // let item = item?; + // let project_path = item.project_path(cx)?; + // Some((project_path, item)) + // }) + // .collect::>() + // }); + + // let opened_items = paths_to_open + // .into_iter() + // .map(|path_to_open| { + // path_to_open + // .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) + // }) + // .collect::>(); + + // // Remove old panes from workspace panes list + // workspace.update(&mut cx, |workspace, cx| { + // if let Some((center_group, active_pane)) = center_group { + // workspace.remove_panes(workspace.center.root.clone(), cx); + + // // Swap workspace center group + // workspace.center = PaneGroup::with_root(center_group); + + // // Change the focus to the workspace first so that we retrigger focus in on the pane. + // cx.focus_self(); + + // if let Some(active_pane) = active_pane { + // cx.focus(&active_pane); + // } else { + // cx.focus(workspace.panes.last().unwrap()); + // } + // } else { + // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); + // if let Some(old_center_handle) = old_center_handle { + // cx.focus(&old_center_handle) + // } else { + // cx.focus_self() + // } + // } + + // let docks = serialized_workspace.docks; + // workspace.left_dock.update(cx, |dock, cx| { + // dock.set_open(docks.left.visible, cx); + // if let Some(active_panel) = docks.left.active_panel { + // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + // dock.activate_panel(ix, cx); + // } + // } + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); + // if docks.left.visible && docks.left.zoom { + // cx.focus_self() + // } + // }); + // // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something + // workspace.right_dock.update(cx, |dock, cx| { + // dock.set_open(docks.right.visible, cx); + // if let Some(active_panel) = docks.right.active_panel { + // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + // dock.activate_panel(ix, cx); + // } + // } + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); + + // if docks.right.visible && docks.right.zoom { + // cx.focus_self() + // } + // }); + // workspace.bottom_dock.update(cx, |dock, cx| { + // dock.set_open(docks.bottom.visible, cx); + // if let Some(active_panel) = docks.bottom.active_panel { + // if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) { + // dock.activate_panel(ix, cx); + // } + // } + + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); + + // if docks.bottom.visible && docks.bottom.zoom { + // cx.focus_self() + // } + // }); + + // cx.notify(); + // })?; + + // // Serialize ourself to make sure our timestamps and any pane / item changes are replicated + // workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; + + // Ok(opened_items) + // }) + // } + + // #[cfg(any(test, feature = "test-support"))] + // pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { + // use node_runtime::FakeNodeRuntime; + + // let client = project.read(cx).client(); + // let user_store = project.read(cx).user_store(); + + // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); + // let app_state = Arc::new(AppState { + // languages: project.read(cx).languages().clone(), + // workspace_store, + // client, + // user_store, + // fs: project.read(cx).fs().clone(), + // build_window_options: |_, _, _| Default::default(), + // initialize_workspace: |_, _, _, _| Task::ready(Ok(())), + // node_runtime: FakeNodeRuntime::new(), + // }); + // Self::new(0, project, app_state, cx) + // } + + // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { + // let dock = match position { + // DockPosition::Left => &self.left_dock, + // DockPosition::Right => &self.right_dock, + // DockPosition::Bottom => &self.bottom_dock, + // }; + // let active_panel = dock.read(cx).visible_panel()?; + // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { + // dock.read(cx).render_placeholder(cx) + // } else { + // ChildView::new(dock, cx).into_any() + // }; + + // Some( + // element + // .constrained() + // .dynamically(move |constraint, _, cx| match position { + // DockPosition::Left | DockPosition::Right => SizeConstraint::new( + // Vector2F::new(20., constraint.min.y()), + // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), + // ), + // DockPosition::Bottom => SizeConstraint::new( + // Vector2F::new(constraint.min.x(), 20.), + // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), + // ), + // }) + // .into_any(), + // ) + // } + // } + + // fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { + // ZED_WINDOW_POSITION + // .zip(*ZED_WINDOW_SIZE) + // .map(|(position, size)| { + // WindowBounds::Fixed(RectF::new( + // cx.platform().screens()[0].bounds().origin() + position, + // size, + // )) + // }) + // } + + // async fn open_items( + // serialized_workspace: Option, + // workspace: &WeakViewHandle, + // mut project_paths_to_open: Vec<(PathBuf, Option)>, + // app_state: Arc, + // mut cx: AsyncAppContext, + // ) -> Result>>>> { + // let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); + + // if let Some(serialized_workspace) = serialized_workspace { + // let workspace = workspace.clone(); + // let restored_items = cx + // .update(|cx| { + // Workspace::load_workspace( + // workspace, + // serialized_workspace, + // project_paths_to_open + // .iter() + // .map(|(_, project_path)| project_path) + // .cloned() + // .collect(), + // cx, + // ) + // }) + // .await?; + + // let restored_project_paths = cx.read(|cx| { + // restored_items + // .iter() + // .filter_map(|item| item.as_ref()?.project_path(cx)) + // .collect::>() + // }); + + // for restored_item in restored_items { + // opened_items.push(restored_item.map(Ok)); + // } + + // project_paths_to_open + // .iter_mut() + // .for_each(|(_, project_path)| { + // if let Some(project_path_to_open) = project_path { + // if restored_project_paths.contains(project_path_to_open) { + // *project_path = None; + // } + // } + // }); + // } else { + // for _ in 0..project_paths_to_open.len() { + // opened_items.push(None); + // } + // } + // assert!(opened_items.len() == project_paths_to_open.len()); + + // let tasks = + // project_paths_to_open + // .into_iter() + // .enumerate() + // .map(|(i, (abs_path, project_path))| { + // let workspace = workspace.clone(); + // cx.spawn(|mut cx| { + // let fs = app_state.fs.clone(); + // async move { + // let file_project_path = project_path?; + // if fs.is_file(&abs_path).await { + // Some(( + // i, + // workspace + // .update(&mut cx, |workspace, cx| { + // workspace.open_path(file_project_path, None, true, cx) + // }) + // .log_err()? + // .await, + // )) + // } else { + // None + // } + // } + // }) + // }); + + // for maybe_opened_path in futures::future::join_all(tasks.into_iter()) + // .await + // .into_iter() + // { + // if let Some((i, path_open_result)) = maybe_opened_path { + // opened_items[i] = Some(path_open_result); + // } + // } + + // Ok(opened_items) + // } + + // fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { + // const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; + // const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; + // const MESSAGE_ID: usize = 2; + + // if workspace + // .read_with(cx, |workspace, cx| { + // workspace.has_shown_notification_once::(MESSAGE_ID, cx) + // }) + // .unwrap_or(false) + // { + // return; + // } + + // if db::kvp::KEY_VALUE_STORE + // .read_kvp(NEW_DOCK_HINT_KEY) + // .ok() + // .flatten() + // .is_some() + // { + // if !workspace + // .read_with(cx, |workspace, cx| { + // workspace.has_shown_notification_once::(MESSAGE_ID, cx) + // }) + // .unwrap_or(false) + // { + // cx.update(|cx| { + // cx.update_global::(|tracker, _| { + // let entry = tracker + // .entry(TypeId::of::()) + // .or_default(); + // if !entry.contains(&MESSAGE_ID) { + // entry.push(MESSAGE_ID); + // } + // }); + // }); + // } + + // return; + // } + + // cx.spawn(|_| async move { + // db::kvp::KEY_VALUE_STORE + // .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) + // .await + // .ok(); + // }) + // .detach(); + + // workspace + // .update(cx, |workspace, cx| { + // workspace.show_notification_once(2, cx, |cx| { + // cx.add_view(|_| { + // MessageNotification::new_element(|text, _| { + // Text::new( + // "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", + // text, + // ) + // .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { + // let code_span_background_color = settings::get::(cx) + // .theme + // .editor + // .document_highlight_read_background; + + // cx.scene().push_quad(gpui::Quad { + // bounds, + // background: Some(code_span_background_color), + // border: Default::default(), + // corner_radii: (2.0).into(), + // }) + // }) + // .into_any() + // }) + // .with_click_message("Read more about the new panel system") + // .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) + // }) + // }) + // }) + // .ok(); +} // fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { // const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; @@ -4321,16 +4321,20 @@ pub async fn activate_workspace_for_project( // } use client2::{proto::PeerId, Client, UserStore}; -use collections::HashSet; +use collections::{HashMap, HashSet}; use gpui2::{ - AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, WeakHandle, WindowBounds, - WindowHandle, WindowOptions, + AnyHandle, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View, ViewContext, + WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions, }; -use item::ItemHandle; +use item::{ItemHandle, ProjectItem}; use language2::LanguageRegistry; use node_runtime::NodeRuntime; -use project2::Project; -use std::{path::PathBuf, sync::Arc}; +use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; +use std::{ + any::TypeId, + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; #[allow(clippy::type_complexity)] @@ -4341,7 +4345,7 @@ pub fn open_paths( cx: &mut AppContext, ) -> Task< anyhow::Result<( - WeakHandle, + WindowHandle, Vec, anyhow::Error>>>, )>, > { @@ -4357,18 +4361,18 @@ pub fn open_paths( if let Some(existing) = existing { Ok(( existing.clone(), - existing - .update(&mut cx, |workspace, cx| { - workspace.open_paths(abs_paths, true, cx) - })? - .await, + cx.update_window_root(&existing, |workspace, cx| { + workspace.open_paths(abs_paths, true, cx) + })? + .await, )) } else { - Ok(cx - .update(|cx| { - Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) - }) - .await) + todo!() + // Ok(cx + // .update(|cx| { + // Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) + // }) + // .await) } }) } From 89bcbe3eebd7ce3840e47d71c33e569c1a40f0c8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 16:43:01 +0100 Subject: [PATCH 06/66] WIP --- crates/workspace2/src/item.rs | 652 +++++------ crates/workspace2/src/pane.rs | 43 +- crates/workspace2/src/pane_group.rs | 1070 ++++++++++--------- crates/workspace2/src/persistence.rs | 733 ++++++------- crates/workspace2/src/persistence/model.rs | 32 +- crates/workspace2/src/toolbar.rs | 166 ++- crates/workspace2/src/workspace2.rs | 295 ++--- crates/workspace2/src/workspace_settings.rs | 6 +- 8 files changed, 1504 insertions(+), 1493 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 06592bffac..d359427053 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -8,6 +8,7 @@ use client2::{ proto::{self, PeerId, ViewId}, Client, }; +use settings2::Settings; use theme2::Theme; // use client2::{ // proto::{self, PeerId}, @@ -16,8 +17,8 @@ use theme2::Theme; // use gpui2::geometry::vector::Vector2F; // use gpui2::AnyWindowHandle; // use gpui2::{ -// fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View, -// ViewContext, ViewHandle, WeakViewHandle, WindowContext, +// fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Handle, Task, View, +// ViewContext, View, WeakViewHandle, WindowContext, // }; // use project2::{Project, ProjectEntryId, ProjectPath}; // use schemars::JsonSchema; @@ -97,7 +98,7 @@ pub struct BreadcrumbText { pub highlights: Option, HighlightStyle)>>, } -pub trait Item: EventEmitter { +pub trait Item: EventEmitter + Sized { // fn deactivated(&mut self, _: &mut ViewContext) {} // fn workspace_deactivated(&mut self, _: &mut ViewContext) {} // fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { @@ -138,14 +139,14 @@ pub trait Item: EventEmitter { // } // fn save( // &mut self, - // _project: ModelHandle, + // _project: Handle, // _cx: &mut ViewContext, // ) -> Task> { // unimplemented!("save() must be implemented if can_save() returns true") // } // fn save_as( // &mut self, - // _project: ModelHandle, + // _project: Handle, // _abs_path: PathBuf, // _cx: &mut ViewContext, // ) -> Task> { @@ -153,7 +154,7 @@ pub trait Item: EventEmitter { // } // fn reload( // &mut self, - // _project: ModelHandle, + // _project: Handle, // _cx: &mut ViewContext, // ) -> Task> { // unimplemented!("reload() must be implemented if can_save() returns true") @@ -171,7 +172,7 @@ pub trait Item: EventEmitter { // fn act_as_type<'a>( // &'a self, // type_id: TypeId, - // self_handle: &'a ViewHandle, + // self_handle: &'a View, // _: &'a AppContext, // ) -> Option<&AnyViewHandle> { // if TypeId::of::() == type_id { @@ -181,7 +182,7 @@ pub trait Item: EventEmitter { // } // } - // fn as_searchable(&self, _: &ViewHandle) -> Option> { + // fn as_searchable(&self, _: &View) -> Option> { // None // } @@ -200,12 +201,12 @@ pub trait Item: EventEmitter { // } // fn deserialize( - // _project: ModelHandle, + // _project: Handle, // _workspace: WeakViewHandle, // _workspace_id: WorkspaceId, // _item_id: ItemId, // _cx: &mut ViewContext, - // ) -> Task>> { + // ) -> Task>> { // unimplemented!( // "deserialize() must be implemented if serialized_item_kind() returns Some(_)" // ) @@ -218,27 +219,36 @@ pub trait Item: EventEmitter { // } } -use core::fmt; use std::{ - any::{Any, TypeId}, + any::Any, borrow::Cow, + cell::RefCell, ops::Range, path::PathBuf, - sync::Arc, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, }; use gpui2::{ - AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, - Point, Task, View, ViewContext, WindowContext, + AnyElement, 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, + pane::{self, Pane}, + searchable::SearchableItemHandle, + workspace_settings::{AutosaveSetting, WorkspaceSettings}, + DelayedDebouncedEditAction, FollowableItemBuilders, ToolbarItemLocation, Workspace, + WorkspaceId, }; -pub trait ItemHandle: 'static + fmt::Debug + Send { +pub trait ItemHandle: 'static + Send { fn subscribe_to_item_events( &self, cx: &mut WindowContext, @@ -305,355 +315,347 @@ pub trait WeakItemHandle { // todo!() // impl dyn ItemHandle { -// pub fn downcast(&self) -> Option> { +// pub fn downcast(&self) -> Option> { // self.as_any().clone().downcast() // } -// pub fn act_as(&self, cx: &AppContext) -> Option> { +// 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) -// } -// }) -// } +impl ItemHandle for View { + 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_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_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 tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement { + self.read(cx).tab_content(detail, cx) + } -// fn dragged_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, cx: &AppContext) -> AnyElement { + self.read(cx).tab_content(detail, 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_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_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 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 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 is_singleton(&self, cx: &AppContext) -> bool { + self.read(cx).is_singleton(cx) + } -// fn boxed_clone(&self) -> Box { -// Box::new(self.clone()) -// } + 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 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); -// }); + fn added_to_pane( + &self, + workspace: &mut Workspace, + pane: View, + 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 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)); + 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; -// }; + 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 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 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, -// ); -// } -// }); -// } -// } + 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; -// } + 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::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) -// }); -// } -// } + ItemEvent::Edit => { + let autosave = WorkspaceSettings::get_global(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(); + cx.observe_focus(self, move |workspace, item, focused, cx| { + if !focused + && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange + { + Pane::autosave_item(&item, workspace.project.clone(), cx) + .detach_and_log_err(cx); + } + }) + .detach(); -// let item_id = self.id(); -// cx.observe_release(self, move |workspace, _, _| { -// workspace.panes_by_item.remove(&item_id); -// event_subscription.take(); -// }) -// .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); -// }); -// } + cx.defer(|workspace, cx| { + workspace.serialize_workspace(cx); + }); + } -// fn deactivated(&self, cx: &mut WindowContext) { -// self.update(cx, |this, cx| this.deactivated(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 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 navigate(&self, data: Box, cx: &mut WindowContext) -> bool { + self.update(cx, |this, cx| this.navigate(data, cx)) + } -// fn id(&self) -> usize { -// self.id() -// } + fn id(&self) -> usize { + self.id() + } -// fn window(&self) -> AnyWindowHandle { -// AnyViewHandle::window(self) -// } + fn window(&self) -> AnyWindowHandle { + todo!() + // AnyViewHandle::window(self) + } -// fn as_any(&self) -> &AnyViewHandle { -// self -// } + // todo!() + // fn as_any(&self) -> &AnyViewHandle { + // self + // } -// fn is_dirty(&self, cx: &AppContext) -> bool { -// self.read(cx).is_dirty(cx) -// } + 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 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 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(&self, project: Handle, 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 save_as( + &self, + project: Handle, + 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 reload(&self, project: Handle, 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) -// } + // todo!() + // 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 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 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 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 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 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 serialized_item_kind(&self) -> Option<&'static str> { + T::serialized_item_kind() + } -// fn show_toolbar(&self, cx: &AppContext) -> bool { -// self.read(cx).show_toolbar() -// } + 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) -// } -// } + 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 { @@ -747,7 +749,7 @@ pub trait FollowableItemHandle: ItemHandle { fn is_project_item(&self, cx: &AppContext) -> bool; } -// impl FollowableItemHandle for ViewHandle { +// impl FollowableItemHandle for View { // fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { // self.read(cx).remote_id().or_else(|| { // client.peer_id().map(|creator| ViewId { @@ -780,7 +782,7 @@ pub trait FollowableItemHandle: ItemHandle { // fn apply_update_proto( // &self, -// project: &ModelHandle, +// project: &Handle, // message: proto::update_view::Variant, // cx: &mut WindowContext, // ) -> Task> { @@ -805,8 +807,8 @@ pub trait FollowableItemHandle: ItemHandle { // 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, +// elements::Empty, AnyElement, AppContext, Element, Entity, Handle, Task, View, +// ViewContext, View, WeakViewHandle, // }; // use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId}; // use smallvec::SmallVec; @@ -827,7 +829,7 @@ pub trait FollowableItemHandle: ItemHandle { // pub is_dirty: bool, // pub is_singleton: bool, // pub has_conflict: bool, -// pub project_items: Vec>, +// pub project_items: Vec>, // pub nav_history: Option, // pub tab_descriptions: Option>, // pub tab_detail: Cell>, @@ -872,7 +874,7 @@ pub trait FollowableItemHandle: ItemHandle { // } // impl TestProjectItem { -// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> ModelHandle { +// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Handle { // let entry_id = Some(ProjectEntryId::from_proto(id)); // let project_path = Some(ProjectPath { // worktree_id: WorktreeId::from_usize(0), @@ -884,7 +886,7 @@ pub trait FollowableItemHandle: ItemHandle { // }) // } -// pub fn new_untitled(cx: &mut AppContext) -> ModelHandle { +// pub fn new_untitled(cx: &mut AppContext) -> Handle { // cx.add_model(|_| Self { // project_path: None, // entry_id: None, @@ -937,7 +939,7 @@ pub trait FollowableItemHandle: ItemHandle { // self // } -// pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { +// pub fn with_project_items(mut self, items: &[Handle]) -> Self { // self.project_items.clear(); // self.project_items.extend(items.iter().cloned()); // self @@ -1048,7 +1050,7 @@ pub trait FollowableItemHandle: ItemHandle { // fn save( // &mut self, -// _: ModelHandle, +// _: Handle, // _: &mut ViewContext, // ) -> Task> { // self.save_count += 1; @@ -1058,7 +1060,7 @@ pub trait FollowableItemHandle: ItemHandle { // fn save_as( // &mut self, -// _: ModelHandle, +// _: Handle, // _: std::path::PathBuf, // _: &mut ViewContext, // ) -> Task> { @@ -1069,7 +1071,7 @@ pub trait FollowableItemHandle: ItemHandle { // fn reload( // &mut self, -// _: ModelHandle, +// _: Handle, // _: &mut ViewContext, // ) -> Task> { // self.reload_count += 1; @@ -1086,12 +1088,12 @@ pub trait FollowableItemHandle: ItemHandle { // } // fn deserialize( -// _project: ModelHandle, +// _project: Handle, // _workspace: WeakViewHandle, // workspace_id: WorkspaceId, // _item_id: ItemId, // cx: &mut ViewContext, -// ) -> Task>> { +// ) -> 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 44420714c7..e0eb1b7ec2 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -29,7 +29,7 @@ // WindowContext, // }; // use project2::{Project, ProjectEntryId, ProjectPath}; -// use serde::Deserialize; +use serde::Deserialize; // use std::{ // any::Any, // cell::RefCell, @@ -44,24 +44,24 @@ // 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(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); @@ -159,7 +159,10 @@ pub enum Event { ZoomOut, } -use crate::item::{ItemHandle, WeakItemHandle}; +use crate::{ + item::{ItemHandle, WeakItemHandle}, + SplitDirection, +}; use collections::{HashMap, VecDeque}; use gpui2::{Handle, ViewContext, WeakView}; use project2::{Project, ProjectEntryId, ProjectPath}; diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index fce913128a..f226f7fc43 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,8 +1,8 @@ -use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace}; +use crate::{AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, Result}; -use call2::{ActiveCall, ParticipantLocation}; +use call2::ActiveCall; use collections::HashMap; -use gpui2::{Bounds, Handle, Pixels, Point, View, ViewContext}; +use gpui2::{size, AnyElement, AnyView, Bounds, Handle, Pixels, Point, View, ViewContext}; use project2::Project; use serde::Deserialize; use std::{cell::RefCell, rc::Rc, sync::Arc}; @@ -12,7 +12,7 @@ const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; -enum Axis { +pub enum Axis { Vertical, Horizontal, } @@ -96,7 +96,7 @@ impl PaneGroup { follower_states: &HashMap, FollowerState>, active_call: Option<&Handle>, active_pane: &View, - zoomed: Option<&AnyViewHandle>, + zoomed: Option<&AnyView>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -159,136 +159,138 @@ impl Member { follower_states: &HashMap, FollowerState>, active_call: Option<&Handle>, active_pane: &View, - zoomed: Option<&AnyViewHandle>, + zoomed: Option<&AnyView>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - enum FollowIntoExternalProject {} + todo!() - match self { - Member::Pane(pane) => { - let pane_element = if Some(&**pane) == zoomed { - Empty::new().into_any() - } else { - ChildView::new(pane, cx).into_any() - }; + // enum FollowIntoExternalProject {} - let leader = follower_states.get(pane).and_then(|state| { - let room = active_call?.read(cx).room()?.read(cx); - room.remote_participant_for_peer_id(state.leader_id) - }); + // match self { + // Member::Pane(pane) => { + // let pane_element = if Some(&**pane) == zoomed { + // Empty::new().into_any() + // } else { + // ChildView::new(pane, cx).into_any() + // }; - let mut leader_border = Border::default(); - let mut leader_status_box = None; - if let Some(leader) = &leader { - let leader_color = theme - .editor - .selection_style_for_room_participant(leader.participant_index.0) - .cursor; - leader_border = Border::all(theme.workspace.leader_border_width, leader_color); - leader_border - .color - .fade_out(1. - theme.workspace.leader_border_opacity); - leader_border.overlay = true; + // let leader = follower_states.get(pane).and_then(|state| { + // let room = active_call?.read(cx).room()?.read(cx); + // room.remote_participant_for_peer_id(state.leader_id) + // }); - leader_status_box = match leader.location { - ParticipantLocation::SharedProject { - project_id: leader_project_id, - } => { - if Some(leader_project_id) == project.read(cx).remote_id() { - None - } else { - let leader_user = leader.user.clone(); - let leader_user_id = leader.user.id; - Some( - MouseEventHandler::new::( - pane.id(), - cx, - |_, _| { - Label::new( - format!( - "Follow {} to their active project", - leader_user.github_login, - ), - theme - .workspace - .external_location_message - .text - .clone(), - ) - .contained() - .with_style( - theme.workspace.external_location_message.container, - ) - }, - ) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, this, cx| { - crate::join_remote_project( - leader_project_id, - leader_user_id, - this.app_state().clone(), - cx, - ) - .detach_and_log_err(cx); - }) - .aligned() - .bottom() - .right() - .into_any(), - ) - } - } - ParticipantLocation::UnsharedProject => Some( - Label::new( - format!( - "{} is viewing an unshared Zed project", - leader.user.github_login - ), - theme.workspace.external_location_message.text.clone(), - ) - .contained() - .with_style(theme.workspace.external_location_message.container) - .aligned() - .bottom() - .right() - .into_any(), - ), - ParticipantLocation::External => Some( - Label::new( - format!( - "{} is viewing a window outside of Zed", - leader.user.github_login - ), - theme.workspace.external_location_message.text.clone(), - ) - .contained() - .with_style(theme.workspace.external_location_message.container) - .aligned() - .bottom() - .right() - .into_any(), - ), - }; - } + // let mut leader_border = Border::default(); + // let mut leader_status_box = None; + // if let Some(leader) = &leader { + // let leader_color = theme + // .editor + // .selection_style_for_room_participant(leader.participant_index.0) + // .cursor; + // leader_border = Border::all(theme.workspace.leader_border_width, leader_color); + // leader_border + // .color + // .fade_out(1. - theme.workspace.leader_border_opacity); + // leader_border.overlay = true; - Stack::new() - .with_child(pane_element.contained().with_border(leader_border)) - .with_children(leader_status_box) - .into_any() - } - Member::Axis(axis) => axis.render( - project, - basis + 1, - theme, - follower_states, - active_call, - active_pane, - zoomed, - app_state, - cx, - ), - } + // leader_status_box = match leader.location { + // ParticipantLocation::SharedProject { + // project_id: leader_project_id, + // } => { + // if Some(leader_project_id) == project.read(cx).remote_id() { + // None + // } else { + // let leader_user = leader.user.clone(); + // let leader_user_id = leader.user.id; + // Some( + // MouseEventHandler::new::( + // pane.id(), + // cx, + // |_, _| { + // Label::new( + // format!( + // "Follow {} to their active project", + // leader_user.github_login, + // ), + // theme + // .workspace + // .external_location_message + // .text + // .clone(), + // ) + // .contained() + // .with_style( + // theme.workspace.external_location_message.container, + // ) + // }, + // ) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, this, cx| { + // crate::join_remote_project( + // leader_project_id, + // leader_user_id, + // this.app_state().clone(), + // cx, + // ) + // .detach_and_log_err(cx); + // }) + // .aligned() + // .bottom() + // .right() + // .into_any(), + // ) + // } + // } + // ParticipantLocation::UnsharedProject => Some( + // Label::new( + // format!( + // "{} is viewing an unshared Zed project", + // leader.user.github_login + // ), + // theme.workspace.external_location_message.text.clone(), + // ) + // .contained() + // .with_style(theme.workspace.external_location_message.container) + // .aligned() + // .bottom() + // .right() + // .into_any(), + // ), + // ParticipantLocation::External => Some( + // Label::new( + // format!( + // "{} is viewing a window outside of Zed", + // leader.user.github_login + // ), + // theme.workspace.external_location_message.text.clone(), + // ) + // .contained() + // .with_style(theme.workspace.external_location_message.container) + // .aligned() + // .bottom() + // .right() + // .into_any(), + // ), + // }; + // } + + // Stack::new() + // .with_child(pane_element.contained().with_border(leader_border)) + // .with_children(leader_status_box) + // .into_any() + // } + // Member::Axis(axis) => axis.render( + // project, + // basis + 1, + // theme, + // follower_states, + // active_call, + // active_pane, + // zoomed, + // app_state, + // cx, + // ), + // } } fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View>) { @@ -308,7 +310,7 @@ pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, pub flexes: Rc>>, - pub bounding_boxes: Rc>>>, + pub bounding_boxes: Rc>>>>, } impl PaneAxis { @@ -428,7 +430,7 @@ impl PaneAxis { } } - fn bounding_box_for_pane(&self, pane: &View) -> Option { + fn bounding_box_for_pane(&self, pane: &View) -> Option> { debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); for (idx, member) in self.members.iter().enumerate() { @@ -448,14 +450,14 @@ impl PaneAxis { None } - fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&View> { + fn pane_at_pixel_position(&self, coordinate: Point) -> Option<&View> { debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); let bounding_boxes = self.bounding_boxes.borrow(); for (idx, member) in self.members.iter().enumerate() { if let Some(coordinates) = bounding_boxes[idx] { - if coordinates.contains_point(coordinate) { + if coordinates.contains_point(&coordinate) { return match member { Member::Pane(found) => Some(found), Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), @@ -474,59 +476,60 @@ impl PaneAxis { follower_states: &HashMap, FollowerState>, active_call: Option<&Handle>, active_pane: &View, - zoomed: Option<&AnyViewHandle>, + zoomed: Option<&AnyView>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { debug_assert!(self.members.len() == self.flexes.borrow().len()); - let mut pane_axis = PaneAxisElement::new( - self.axis, - basis, - self.flexes.clone(), - self.bounding_boxes.clone(), - ); - let mut active_pane_ix = None; + todo!() + // let mut pane_axis = PaneAxisElement::new( + // self.axis, + // basis, + // self.flexes.clone(), + // self.bounding_boxes.clone(), + // ); + // let mut active_pane_ix = None; - let mut members = self.members.iter().enumerate().peekable(); - while let Some((ix, member)) = members.next() { - let last = members.peek().is_none(); + // let mut members = self.members.iter().enumerate().peekable(); + // while let Some((ix, member)) = members.next() { + // let last = members.peek().is_none(); - if member.contains(active_pane) { - active_pane_ix = Some(ix); - } + // if member.contains(active_pane) { + // active_pane_ix = Some(ix); + // } - let mut member = member.render( - project, - (basis + ix) * 10, - theme, - follower_states, - active_call, - active_pane, - zoomed, - app_state, - cx, - ); + // let mut member = member.render( + // project, + // (basis + ix) * 10, + // theme, + // follower_states, + // active_call, + // active_pane, + // zoomed, + // app_state, + // cx, + // ); - if !last { - let mut border = theme.workspace.pane_divider; - border.left = false; - border.right = false; - border.top = false; - border.bottom = false; + // if !last { + // let mut border = theme.workspace.pane_divider; + // border.left = false; + // border.right = false; + // border.top = false; + // border.bottom = false; - match self.axis { - Axis::Vertical => border.bottom = true, - Axis::Horizontal => border.right = true, - } + // match self.axis { + // Axis::Vertical => border.bottom = true, + // Axis::Horizontal => border.right = true, + // } - member = member.contained().with_border(border).into_any(); - } + // member = member.contained().with_border(border).into_any(); + // } - pane_axis = pane_axis.with_child(member.into_any()); - } - pane_axis.set_active_pane(active_pane_ix); - pane_axis.into_any() + // pane_axis = pane_axis.with_child(member.into_any()); + // } + // pane_axis.set_active_pane(active_pane_ix); + // pane_axis.into_any() } } @@ -543,7 +546,7 @@ impl SplitDirection { [Self::Up, Self::Down, Self::Left, Self::Right] } - pub fn edge(&self, rect: RectF) -> f32 { + pub fn edge(&self, rect: Bounds) -> f32 { match self { Self::Up => rect.min_y(), Self::Down => rect.max_y(), @@ -552,19 +555,24 @@ impl SplitDirection { } } - // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection - pub fn along_edge(&self, rect: RectF, size: f32) -> RectF { + pub fn along_edge(&self, bounds: Bounds, length: Pixels) -> Bounds { match self { - Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)), - Self::Down => RectF::new( - rect.lower_left() - Vector2F::new(0., size), - Vector2F::new(rect.width(), size), - ), - Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())), - Self::Right => RectF::new( - rect.upper_right() - Vector2F::new(size, 0.), - Vector2F::new(size, rect.height()), - ), + Self::Up => Bounds { + origin: bounds.origin(), + size: size(bounds.width(), length), + }, + Self::Down => Bounds { + origin: size(bounds.min_x(), bounds.max_y() - length), + size: size(bounds.width(), length), + }, + Self::Left => Bounds { + origin: bounds.origin(), + size: size(length, bounds.height()), + }, + Self::Right => Bounds { + origin: size(bounds.max_x() - length, bounds.min_y()), + size: size(length, bounds.height()), + }, } } @@ -583,403 +591,403 @@ impl SplitDirection { } } -mod element { - use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; +// mod element { +// // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; - use gpui::{ - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, - json::{self, ToJson}, - platform::{CursorStyle, MouseButton}, - scene::MouseDrag, - AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt, - SizeConstraint, Vector2FExt, ViewContext, - }; +// // use gpui::{ +// // geometry::{ +// // rect::Bounds, +// // vector::{vec2f, Vector2F}, +// // }, +// // json::{self, ToJson}, +// // platform::{CursorStyle, MouseButton}, +// // scene::MouseDrag, +// // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, BoundsExt, +// // SizeConstraint, Vector2FExt, ViewContext, +// // }; - use crate::{ - pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, - Workspace, WorkspaceSettings, - }; +// use crate::{ +// pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, +// Workspace, WorkspaceSettings, +// }; - pub struct PaneAxisElement { - axis: Axis, - basis: usize, - active_pane_ix: Option, - flexes: Rc>>, - children: Vec>, - bounding_boxes: Rc>>>, - } +// pub struct PaneAxisElement { +// axis: Axis, +// basis: usize, +// active_pane_ix: Option, +// flexes: Rc>>, +// children: Vec>, +// bounding_boxes: Rc>>>>, +// } - impl PaneAxisElement { - pub fn new( - axis: Axis, - basis: usize, - flexes: Rc>>, - bounding_boxes: Rc>>>, - ) -> Self { - Self { - axis, - basis, - flexes, - bounding_boxes, - active_pane_ix: None, - children: Default::default(), - } - } +// impl PaneAxisElement { +// pub fn new( +// axis: Axis, +// basis: usize, +// flexes: Rc>>, +// bounding_boxes: Rc>>>>, +// ) -> Self { +// Self { +// axis, +// basis, +// flexes, +// bounding_boxes, +// active_pane_ix: None, +// children: Default::default(), +// } +// } - pub fn set_active_pane(&mut self, active_pane_ix: Option) { - self.active_pane_ix = active_pane_ix; - } +// pub fn set_active_pane(&mut self, active_pane_ix: Option) { +// self.active_pane_ix = active_pane_ix; +// } - fn layout_children( - &mut self, - active_pane_magnification: f32, - constraint: SizeConstraint, - remaining_space: &mut f32, - remaining_flex: &mut f32, - cross_axis_max: &mut f32, - view: &mut Workspace, - cx: &mut ViewContext, - ) { - let flexes = self.flexes.borrow(); - let cross_axis = self.axis.invert(); - for (ix, child) in self.children.iter_mut().enumerate() { - let flex = if active_pane_magnification != 1. { - if let Some(active_pane_ix) = self.active_pane_ix { - if ix == active_pane_ix { - active_pane_magnification - } else { - 1. - } - } else { - 1. - } - } else { - flexes[ix] - }; +// fn layout_children( +// &mut self, +// active_pane_magnification: f32, +// constraint: SizeConstraint, +// remaining_space: &mut f32, +// remaining_flex: &mut f32, +// cross_axis_max: &mut f32, +// view: &mut Workspace, +// cx: &mut ViewContext, +// ) { +// let flexes = self.flexes.borrow(); +// let cross_axis = self.axis.invert(); +// for (ix, child) in self.children.iter_mut().enumerate() { +// let flex = if active_pane_magnification != 1. { +// if let Some(active_pane_ix) = self.active_pane_ix { +// if ix == active_pane_ix { +// active_pane_magnification +// } else { +// 1. +// } +// } else { +// 1. +// } +// } else { +// flexes[ix] +// }; - let child_size = if *remaining_flex == 0.0 { - *remaining_space - } else { - let space_per_flex = *remaining_space / *remaining_flex; - space_per_flex * flex - }; +// let child_size = if *remaining_flex == 0.0 { +// *remaining_space +// } else { +// let space_per_flex = *remaining_space / *remaining_flex; +// space_per_flex * flex +// }; - let child_constraint = match self.axis { - Axis::Horizontal => SizeConstraint::new( - vec2f(child_size, constraint.min.y()), - vec2f(child_size, constraint.max.y()), - ), - Axis::Vertical => SizeConstraint::new( - vec2f(constraint.min.x(), child_size), - vec2f(constraint.max.x(), child_size), - ), - }; - let child_size = child.layout(child_constraint, view, cx); - *remaining_space -= child_size.along(self.axis); - *remaining_flex -= flex; - *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); - } - } +// let child_constraint = match self.axis { +// Axis::Horizontal => SizeConstraint::new( +// vec2f(child_size, constraint.min.y()), +// vec2f(child_size, constraint.max.y()), +// ), +// Axis::Vertical => SizeConstraint::new( +// vec2f(constraint.min.x(), child_size), +// vec2f(constraint.max.x(), child_size), +// ), +// }; +// let child_size = child.layout(child_constraint, view, cx); +// *remaining_space -= child_size.along(self.axis); +// *remaining_flex -= flex; +// *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); +// } +// } - fn handle_resize( - flexes: Rc>>, - axis: Axis, - preceding_ix: usize, - child_start: Vector2F, - drag_bounds: RectF, - ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { - let size = move |ix, flexes: &[f32]| { - drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) - }; +// fn handle_resize( +// flexes: Rc>>, +// axis: Axis, +// preceding_ix: usize, +// child_start: Vector2F, +// drag_bounds: Bounds, +// ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { +// let size = move |ix, flexes: &[f32]| { +// drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) +// }; - move |drag, workspace: &mut Workspace, cx| { - if drag.end { - // TODO: Clear cascading resize state - return; - } - let min_size = match axis { - Axis::Horizontal => HORIZONTAL_MIN_SIZE, - Axis::Vertical => VERTICAL_MIN_SIZE, - }; - let mut flexes = flexes.borrow_mut(); +// move |drag, workspace: &mut Workspace, cx| { +// if drag.end { +// // TODO: Clear cascading resize state +// return; +// } +// let min_size = match axis { +// Axis::Horizontal => HORIZONTAL_MIN_SIZE, +// Axis::Vertical => VERTICAL_MIN_SIZE, +// }; +// let mut flexes = flexes.borrow_mut(); - // Don't allow resizing to less than the minimum size, if elements are already too small - if min_size - 1. > size(preceding_ix, flexes.as_slice()) { - return; - } +// // Don't allow resizing to less than the minimum size, if elements are already too small +// if min_size - 1. > size(preceding_ix, flexes.as_slice()) { +// return; +// } - let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) - - size(preceding_ix, flexes.as_slice()); +// let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) +// - size(preceding_ix, flexes.as_slice()); - let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { - let flex_change = pixel_dx / drag_bounds.length_along(axis); - let current_target_flex = flexes[target_ix] + flex_change; - let next_target_flex = - flexes[(target_ix as isize + next) as usize] - flex_change; - (current_target_flex, next_target_flex) - }; +// let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { +// let flex_change = pixel_dx / drag_bounds.length_along(axis); +// let current_target_flex = flexes[target_ix] + flex_change; +// let next_target_flex = +// flexes[(target_ix as isize + next) as usize] - flex_change; +// (current_target_flex, next_target_flex) +// }; - let mut successors = from_fn({ - let forward = proposed_current_pixel_change > 0.; - let mut ix_offset = 0; - let len = flexes.len(); - move || { - let result = if forward { - (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) - } else { - (preceding_ix as isize - ix_offset as isize >= 0) - .then(|| preceding_ix - ix_offset) - }; +// let mut successors = from_fn({ +// let forward = proposed_current_pixel_change > 0.; +// let mut ix_offset = 0; +// let len = flexes.len(); +// move || { +// let result = if forward { +// (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) +// } else { +// (preceding_ix as isize - ix_offset as isize >= 0) +// .then(|| preceding_ix - ix_offset) +// }; - ix_offset += 1; +// ix_offset += 1; - result - } - }); +// result +// } +// }); - while proposed_current_pixel_change.abs() > 0. { - let Some(current_ix) = successors.next() else { - break; - }; +// while proposed_current_pixel_change.abs() > 0. { +// let Some(current_ix) = successors.next() else { +// break; +// }; - let next_target_size = f32::max( - size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, - min_size, - ); +// let next_target_size = f32::max( +// size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, +// min_size, +// ); - let current_target_size = f32::max( - size(current_ix, flexes.as_slice()) - + size(current_ix + 1, flexes.as_slice()) - - next_target_size, - min_size, - ); +// let current_target_size = f32::max( +// size(current_ix, flexes.as_slice()) +// + size(current_ix + 1, flexes.as_slice()) +// - next_target_size, +// min_size, +// ); - let current_pixel_change = - current_target_size - size(current_ix, flexes.as_slice()); +// let current_pixel_change = +// current_target_size - size(current_ix, flexes.as_slice()); - let (current_target_flex, next_target_flex) = - flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); +// let (current_target_flex, next_target_flex) = +// flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); - flexes[current_ix] = current_target_flex; - flexes[current_ix + 1] = next_target_flex; +// flexes[current_ix] = current_target_flex; +// flexes[current_ix + 1] = next_target_flex; - proposed_current_pixel_change -= current_pixel_change; - } +// proposed_current_pixel_change -= current_pixel_change; +// } - workspace.schedule_serialize(cx); - cx.notify(); - } - } - } +// workspace.schedule_serialize(cx); +// cx.notify(); +// } +// } +// } - impl Extend> for PaneAxisElement { - fn extend>>(&mut self, children: T) { - self.children.extend(children); - } - } +// impl Extend> for PaneAxisElement { +// fn extend>>(&mut self, children: T) { +// self.children.extend(children); +// } +// } - impl Element for PaneAxisElement { - type LayoutState = f32; - type PaintState = (); +// impl Element for PaneAxisElement { +// type LayoutState = f32; +// type PaintState = (); - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut Workspace, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState) { - debug_assert!(self.children.len() == self.flexes.borrow().len()); +// fn layout( +// &mut self, +// constraint: SizeConstraint, +// view: &mut Workspace, +// cx: &mut ViewContext, +// ) -> (Vector2F, Self::LayoutState) { +// debug_assert!(self.children.len() == self.flexes.borrow().len()); - let active_pane_magnification = - settings::get::(cx).active_pane_magnification; +// let active_pane_magnification = +// settings::get::(cx).active_pane_magnification; - let mut remaining_flex = 0.; +// let mut remaining_flex = 0.; - if active_pane_magnification != 1. { - let active_pane_flex = self - .active_pane_ix - .map(|_| active_pane_magnification) - .unwrap_or(1.); - remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; - } else { - for flex in self.flexes.borrow().iter() { - remaining_flex += flex; - } - } +// if active_pane_magnification != 1. { +// let active_pane_flex = self +// .active_pane_ix +// .map(|_| active_pane_magnification) +// .unwrap_or(1.); +// remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; +// } else { +// for flex in self.flexes.borrow().iter() { +// remaining_flex += flex; +// } +// } - let mut cross_axis_max: f32 = 0.0; - let mut remaining_space = constraint.max_along(self.axis); +// let mut cross_axis_max: f32 = 0.0; +// let mut remaining_space = constraint.max_along(self.axis); - if remaining_space.is_infinite() { - panic!("flex contains flexible children but has an infinite constraint along the flex axis"); - } +// if remaining_space.is_infinite() { +// panic!("flex contains flexible children but has an infinite constraint along the flex axis"); +// } - self.layout_children( - active_pane_magnification, - constraint, - &mut remaining_space, - &mut remaining_flex, - &mut cross_axis_max, - view, - cx, - ); +// self.layout_children( +// active_pane_magnification, +// constraint, +// &mut remaining_space, +// &mut remaining_flex, +// &mut cross_axis_max, +// view, +// cx, +// ); - let mut size = match self.axis { - Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), - Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), - }; +// let mut size = match self.axis { +// Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), +// Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), +// }; - if constraint.min.x().is_finite() { - size.set_x(size.x().max(constraint.min.x())); - } - if constraint.min.y().is_finite() { - size.set_y(size.y().max(constraint.min.y())); - } +// if constraint.min.x().is_finite() { +// size.set_x(size.x().max(constraint.min.x())); +// } +// if constraint.min.y().is_finite() { +// size.set_y(size.y().max(constraint.min.y())); +// } - if size.x() > constraint.max.x() { - size.set_x(constraint.max.x()); - } - if size.y() > constraint.max.y() { - size.set_y(constraint.max.y()); - } +// if size.x() > constraint.max.x() { +// size.set_x(constraint.max.x()); +// } +// if size.y() > constraint.max.y() { +// size.set_y(constraint.max.y()); +// } - (size, remaining_space) - } +// (size, remaining_space) +// } - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - remaining_space: &mut Self::LayoutState, - view: &mut Workspace, - cx: &mut ViewContext, - ) -> Self::PaintState { - let can_resize = settings::get::(cx).active_pane_magnification == 1.; - let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); +// fn paint( +// &mut self, +// bounds: Bounds, +// visible_bounds: Bounds, +// remaining_space: &mut Self::LayoutState, +// view: &mut Workspace, +// cx: &mut ViewContext, +// ) -> Self::PaintState { +// let can_resize = settings::get::(cx).active_pane_magnification == 1.; +// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - let overflowing = *remaining_space < 0.; - if overflowing { - cx.scene().push_layer(Some(visible_bounds)); - } +// let overflowing = *remaining_space < 0.; +// if overflowing { +// cx.scene().push_layer(Some(visible_bounds)); +// } - let mut child_origin = bounds.origin(); +// let mut child_origin = bounds.origin(); - let mut bounding_boxes = self.bounding_boxes.borrow_mut(); - bounding_boxes.clear(); +// let mut bounding_boxes = self.bounding_boxes.borrow_mut(); +// bounding_boxes.clear(); - let mut children_iter = self.children.iter_mut().enumerate().peekable(); - while let Some((ix, child)) = children_iter.next() { - let child_start = child_origin.clone(); - child.paint(child_origin, visible_bounds, view, cx); +// let mut children_iter = self.children.iter_mut().enumerate().peekable(); +// while let Some((ix, child)) = children_iter.next() { +// let child_start = child_origin.clone(); +// child.paint(child_origin, visible_bounds, view, cx); - bounding_boxes.push(Some(RectF::new(child_origin, child.size()))); +// bounding_boxes.push(Some(Bounds::new(child_origin, child.size()))); - match self.axis { - Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), - Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), - } +// match self.axis { +// Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), +// Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), +// } - if can_resize && children_iter.peek().is_some() { - cx.scene().push_stacking_context(None, None); +// if can_resize && children_iter.peek().is_some() { +// cx.scene().push_stacking_context(None, None); - let handle_origin = match self.axis { - Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), - Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), - }; +// let handle_origin = match self.axis { +// Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), +// Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), +// }; - let handle_bounds = match self.axis { - Axis::Horizontal => RectF::new( - handle_origin, - vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), - ), - Axis::Vertical => RectF::new( - handle_origin, - vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), - ), - }; +// let handle_bounds = match self.axis { +// Axis::Horizontal => Bounds::new( +// handle_origin, +// vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), +// ), +// Axis::Vertical => Bounds::new( +// handle_origin, +// vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), +// ), +// }; - let style = match self.axis { - Axis::Horizontal => CursorStyle::ResizeLeftRight, - Axis::Vertical => CursorStyle::ResizeUpDown, - }; +// let style = match self.axis { +// Axis::Horizontal => CursorStyle::ResizeLeftRight, +// Axis::Vertical => CursorStyle::ResizeUpDown, +// }; - cx.scene().push_cursor_region(CursorRegion { - bounds: handle_bounds, - style, - }); +// cx.scene().push_cursor_region(CursorRegion { +// bounds: handle_bounds, +// style, +// }); - enum ResizeHandle {} - let mut mouse_region = MouseRegion::new::( - cx.view_id(), - self.basis + ix, - handle_bounds, - ); - mouse_region = mouse_region - .on_drag( - MouseButton::Left, - Self::handle_resize( - self.flexes.clone(), - self.axis, - ix, - child_start, - visible_bounds.clone(), - ), - ) - .on_click(MouseButton::Left, { - let flexes = self.flexes.clone(); - move |e, v: &mut Workspace, cx| { - if e.click_count >= 2 { - let mut borrow = flexes.borrow_mut(); - *borrow = vec![1.; borrow.len()]; - v.schedule_serialize(cx); - cx.notify(); - } - } - }); - cx.scene().push_mouse_region(mouse_region); +// enum ResizeHandle {} +// let mut mouse_region = MouseRegion::new::( +// cx.view_id(), +// self.basis + ix, +// handle_bounds, +// ); +// mouse_region = mouse_region +// .on_drag( +// MouseButton::Left, +// Self::handle_resize( +// self.flexes.clone(), +// self.axis, +// ix, +// child_start, +// visible_bounds.clone(), +// ), +// ) +// .on_click(MouseButton::Left, { +// let flexes = self.flexes.clone(); +// move |e, v: &mut Workspace, cx| { +// if e.click_count >= 2 { +// let mut borrow = flexes.borrow_mut(); +// *borrow = vec![1.; borrow.len()]; +// v.schedule_serialize(cx); +// cx.notify(); +// } +// } +// }); +// cx.scene().push_mouse_region(mouse_region); - cx.scene().pop_stacking_context(); - } - } +// cx.scene().pop_stacking_context(); +// } +// } - if overflowing { - cx.scene().pop_layer(); - } - } +// if overflowing { +// cx.scene().pop_layer(); +// } +// } - fn rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &Workspace, - cx: &ViewContext, - ) -> Option { - self.children - .iter() - .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) - } +// fn rect_for_text_range( +// &self, +// range_utf16: Range, +// _: Bounds, +// _: Bounds, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// view: &Workspace, +// cx: &ViewContext, +// ) -> Option> { +// self.children +// .iter() +// .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) +// } - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &Workspace, - cx: &ViewContext, - ) -> json::Value { - serde_json::json!({ - "type": "PaneAxis", - "bounds": bounds.to_json(), - "axis": self.axis.to_json(), - "flexes": *self.flexes.borrow(), - "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() - }) - } - } -} +// fn debug( +// &self, +// bounds: Bounds, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// view: &Workspace, +// cx: &ViewContext, +// ) -> json::Value { +// serde_json::json!({ +// "type": "PaneAxis", +// "bounds": bounds.to_json(), +// "axis": self.axis.to_json(), +// "flexes": *self.flexes.borrow(), +// "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() +// }) +// } +// } +// } diff --git a/crates/workspace2/src/persistence.rs b/crates/workspace2/src/persistence.rs index 2a4062c079..435518271d 100644 --- a/crates/workspace2/src/persistence.rs +++ b/crates/workspace2/src/persistence.rs @@ -5,13 +5,13 @@ pub mod model; use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; -use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::{platform::WindowBounds, Axis}; +use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; +use gpui2::WindowBounds; use util::{unzip_option, ResultExt}; use uuid::Uuid; -use crate::WorkspaceId; +use crate::{Axis, WorkspaceId}; use model::{ GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, @@ -549,424 +549,425 @@ impl WorkspaceDb { } } -#[cfg(test)] -mod tests { - use super::*; - use db::open_test_db; +// todo!() +// #[cfg(test)] +// mod tests { +// use super::*; +// use db::open_test_db; - #[gpui::test] - async fn test_next_id_stability() { - env_logger::try_init().ok(); +// #[gpui::test] +// async fn test_next_id_stability() { +// env_logger::try_init().ok(); - let db = WorkspaceDb(open_test_db("test_next_id_stability").await); +// let db = WorkspaceDb(open_test_db("test_next_id_stability").await); - db.write(|conn| { - conn.migrate( - "test_table", - &[sql!( - CREATE TABLE test_table( - text TEXT, - workspace_id INTEGER, - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ) STRICT; - )], - ) - .unwrap(); - }) - .await; +// db.write(|conn| { +// conn.migrate( +// "test_table", +// &[sql!( +// CREATE TABLE test_table( +// text TEXT, +// workspace_id INTEGER, +// FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) +// ON DELETE CASCADE +// ) STRICT; +// )], +// ) +// .unwrap(); +// }) +// .await; - let id = db.next_id().await.unwrap(); - // Assert the empty row got inserted - assert_eq!( - Some(id), - db.select_row_bound::(sql!( - SELECT workspace_id FROM workspaces WHERE workspace_id = ? - )) - .unwrap()(id) - .unwrap() - ); +// let id = db.next_id().await.unwrap(); +// // Assert the empty row got inserted +// assert_eq!( +// Some(id), +// db.select_row_bound::(sql!( +// SELECT workspace_id FROM workspaces WHERE workspace_id = ? +// )) +// .unwrap()(id) +// .unwrap() +// ); - db.write(move |conn| { - conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) - .unwrap()(("test-text-1", id)) - .unwrap() - }) - .await; +// db.write(move |conn| { +// conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) +// .unwrap()(("test-text-1", id)) +// .unwrap() +// }) +// .await; - let test_text_1 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(1) - .unwrap() - .unwrap(); - assert_eq!(test_text_1, "test-text-1"); - } +// let test_text_1 = db +// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) +// .unwrap()(1) +// .unwrap() +// .unwrap(); +// assert_eq!(test_text_1, "test-text-1"); +// } - #[gpui::test] - async fn test_workspace_id_stability() { - env_logger::try_init().ok(); +// #[gpui::test] +// async fn test_workspace_id_stability() { +// env_logger::try_init().ok(); - let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); +// let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); - db.write(|conn| { - conn.migrate( - "test_table", - &[sql!( - CREATE TABLE test_table( - text TEXT, - workspace_id INTEGER, - FOREIGN KEY(workspace_id) - REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ) STRICT;)], - ) - }) - .await - .unwrap(); +// db.write(|conn| { +// conn.migrate( +// "test_table", +// &[sql!( +// CREATE TABLE test_table( +// text TEXT, +// workspace_id INTEGER, +// FOREIGN KEY(workspace_id) +// REFERENCES workspaces(workspace_id) +// ON DELETE CASCADE +// ) STRICT;)], +// ) +// }) +// .await +// .unwrap(); - let mut workspace_1 = SerializedWorkspace { - id: 1, - location: (["/tmp", "/tmp2"]).into(), - center_group: Default::default(), - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - }; +// let mut workspace_1 = SerializedWorkspace { +// id: 1, +// location: (["/tmp", "/tmp2"]).into(), +// center_group: Default::default(), +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// }; - let workspace_2 = SerializedWorkspace { - id: 2, - location: (["/tmp"]).into(), - center_group: Default::default(), - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - }; +// let workspace_2 = SerializedWorkspace { +// id: 2, +// location: (["/tmp"]).into(), +// center_group: Default::default(), +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// }; - db.save_workspace(workspace_1.clone()).await; +// db.save_workspace(workspace_1.clone()).await; - db.write(|conn| { - conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) - .unwrap()(("test-text-1", 1)) - .unwrap(); - }) - .await; +// db.write(|conn| { +// conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) +// .unwrap()(("test-text-1", 1)) +// .unwrap(); +// }) +// .await; - db.save_workspace(workspace_2.clone()).await; +// db.save_workspace(workspace_2.clone()).await; - db.write(|conn| { - conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) - .unwrap()(("test-text-2", 2)) - .unwrap(); - }) - .await; +// db.write(|conn| { +// conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) +// .unwrap()(("test-text-2", 2)) +// .unwrap(); +// }) +// .await; - workspace_1.location = (["/tmp", "/tmp3"]).into(); - db.save_workspace(workspace_1.clone()).await; - db.save_workspace(workspace_1).await; - db.save_workspace(workspace_2).await; +// workspace_1.location = (["/tmp", "/tmp3"]).into(); +// db.save_workspace(workspace_1.clone()).await; +// db.save_workspace(workspace_1).await; +// db.save_workspace(workspace_2).await; - let test_text_2 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(2) - .unwrap() - .unwrap(); - assert_eq!(test_text_2, "test-text-2"); +// let test_text_2 = db +// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) +// .unwrap()(2) +// .unwrap() +// .unwrap(); +// assert_eq!(test_text_2, "test-text-2"); - let test_text_1 = db - .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) - .unwrap()(1) - .unwrap() - .unwrap(); - assert_eq!(test_text_1, "test-text-1"); - } +// let test_text_1 = db +// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) +// .unwrap()(1) +// .unwrap() +// .unwrap(); +// assert_eq!(test_text_1, "test-text-1"); +// } - fn group(axis: gpui::Axis, children: Vec) -> SerializedPaneGroup { - SerializedPaneGroup::Group { - axis, - flexes: None, - children, - } - } +// fn group(axis: gpui::Axis, children: Vec) -> SerializedPaneGroup { +// SerializedPaneGroup::Group { +// axis, +// flexes: None, +// children, +// } +// } - #[gpui::test] - async fn test_full_workspace_serialization() { - env_logger::try_init().ok(); +// #[gpui::test] +// async fn test_full_workspace_serialization() { +// env_logger::try_init().ok(); - let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); +// let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - // ----------------- - // | 1,2 | 5,6 | - // | - - - | | - // | 3,4 | | - // ----------------- - let center_group = group( - gpui::Axis::Horizontal, - vec![ - group( - gpui::Axis::Vertical, - vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 5, false), - SerializedItem::new("Terminal", 6, true), - ], - false, - )), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 7, true), - SerializedItem::new("Terminal", 8, false), - ], - false, - )), - ], - ), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 9, false), - SerializedItem::new("Terminal", 10, true), - ], - false, - )), - ], - ); +// // ----------------- +// // | 1,2 | 5,6 | +// // | - - - | | +// // | 3,4 | | +// // ----------------- +// let center_group = group( +// gpui::Axis::Horizontal, +// vec![ +// group( +// gpui::Axis::Vertical, +// vec![ +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 5, false), +// SerializedItem::new("Terminal", 6, true), +// ], +// false, +// )), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 7, true), +// SerializedItem::new("Terminal", 8, false), +// ], +// false, +// )), +// ], +// ), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 9, false), +// SerializedItem::new("Terminal", 10, true), +// ], +// false, +// )), +// ], +// ); - let workspace = SerializedWorkspace { - id: 5, - location: (["/tmp", "/tmp2"]).into(), - center_group, - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - }; +// let workspace = SerializedWorkspace { +// id: 5, +// location: (["/tmp", "/tmp2"]).into(), +// center_group, +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// }; - db.save_workspace(workspace.clone()).await; - let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); +// db.save_workspace(workspace.clone()).await; +// let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - assert_eq!(workspace, round_trip_workspace.unwrap()); +// assert_eq!(workspace, round_trip_workspace.unwrap()); - // Test guaranteed duplicate IDs - db.save_workspace(workspace.clone()).await; - db.save_workspace(workspace.clone()).await; +// // Test guaranteed duplicate IDs +// db.save_workspace(workspace.clone()).await; +// db.save_workspace(workspace.clone()).await; - let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); - assert_eq!(workspace, round_trip_workspace.unwrap()); - } +// let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); +// assert_eq!(workspace, round_trip_workspace.unwrap()); +// } - #[gpui::test] - async fn test_workspace_assignment() { - env_logger::try_init().ok(); +// #[gpui::test] +// async fn test_workspace_assignment() { +// env_logger::try_init().ok(); - let db = WorkspaceDb(open_test_db("test_basic_functionality").await); +// let db = WorkspaceDb(open_test_db("test_basic_functionality").await); - let workspace_1 = SerializedWorkspace { - id: 1, - location: (["/tmp", "/tmp2"]).into(), - center_group: Default::default(), - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - }; +// let workspace_1 = SerializedWorkspace { +// id: 1, +// location: (["/tmp", "/tmp2"]).into(), +// center_group: Default::default(), +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// }; - let mut workspace_2 = SerializedWorkspace { - id: 2, - location: (["/tmp"]).into(), - center_group: Default::default(), - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - }; +// let mut workspace_2 = SerializedWorkspace { +// id: 2, +// location: (["/tmp"]).into(), +// center_group: Default::default(), +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// }; - db.save_workspace(workspace_1.clone()).await; - db.save_workspace(workspace_2.clone()).await; +// db.save_workspace(workspace_1.clone()).await; +// db.save_workspace(workspace_2.clone()).await; - // Test that paths are treated as a set - assert_eq!( - db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), - workspace_1 - ); - assert_eq!( - db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), - workspace_1 - ); +// // Test that paths are treated as a set +// assert_eq!( +// db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), +// workspace_1 +// ); +// assert_eq!( +// db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), +// workspace_1 +// ); - // Make sure that other keys work - assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); - assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); +// // Make sure that other keys work +// assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); +// assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); - // Test 'mutate' case of updating a pre-existing id - workspace_2.location = (["/tmp", "/tmp2"]).into(); +// // Test 'mutate' case of updating a pre-existing id +// workspace_2.location = (["/tmp", "/tmp2"]).into(); - db.save_workspace(workspace_2.clone()).await; - assert_eq!( - db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), - workspace_2 - ); +// db.save_workspace(workspace_2.clone()).await; +// assert_eq!( +// db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), +// workspace_2 +// ); - // Test other mechanism for mutating - let mut workspace_3 = SerializedWorkspace { - id: 3, - location: (&["/tmp", "/tmp2"]).into(), - center_group: Default::default(), - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - }; +// // Test other mechanism for mutating +// let mut workspace_3 = SerializedWorkspace { +// id: 3, +// location: (&["/tmp", "/tmp2"]).into(), +// center_group: Default::default(), +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// }; - db.save_workspace(workspace_3.clone()).await; - assert_eq!( - db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), - workspace_3 - ); +// db.save_workspace(workspace_3.clone()).await; +// assert_eq!( +// db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), +// workspace_3 +// ); - // Make sure that updating paths differently also works - workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); - db.save_workspace(workspace_3.clone()).await; - assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None); - assert_eq!( - db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"]) - .unwrap(), - workspace_3 - ); - } +// // Make sure that updating paths differently also works +// workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); +// db.save_workspace(workspace_3.clone()).await; +// assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None); +// assert_eq!( +// db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"]) +// .unwrap(), +// workspace_3 +// ); +// } - use crate::persistence::model::SerializedWorkspace; - use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; +// use crate::persistence::model::SerializedWorkspace; +// use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; - fn default_workspace>( - workspace_id: &[P], - center_group: &SerializedPaneGroup, - ) -> SerializedWorkspace { - SerializedWorkspace { - id: 4, - location: workspace_id.into(), - center_group: center_group.clone(), - bounds: Default::default(), - display: Default::default(), - docks: Default::default(), - } - } +// fn default_workspace>( +// workspace_id: &[P], +// center_group: &SerializedPaneGroup, +// ) -> SerializedWorkspace { +// SerializedWorkspace { +// id: 4, +// location: workspace_id.into(), +// center_group: center_group.clone(), +// bounds: Default::default(), +// display: Default::default(), +// docks: Default::default(), +// } +// } - #[gpui::test] - async fn test_simple_split() { - env_logger::try_init().ok(); +// #[gpui::test] +// async fn test_simple_split() { +// env_logger::try_init().ok(); - let db = WorkspaceDb(open_test_db("simple_split").await); +// let db = WorkspaceDb(open_test_db("simple_split").await); - // ----------------- - // | 1,2 | 5,6 | - // | - - - | | - // | 3,4 | | - // ----------------- - let center_pane = group( - gpui::Axis::Horizontal, - vec![ - group( - gpui::Axis::Vertical, - vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 2, true), - ], - false, - )), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 4, false), - SerializedItem::new("Terminal", 3, true), - ], - true, - )), - ], - ), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 5, true), - SerializedItem::new("Terminal", 6, false), - ], - false, - )), - ], - ); +// // ----------------- +// // | 1,2 | 5,6 | +// // | - - - | | +// // | 3,4 | | +// // ----------------- +// let center_pane = group( +// gpui::Axis::Horizontal, +// vec![ +// group( +// gpui::Axis::Vertical, +// vec![ +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 1, false), +// SerializedItem::new("Terminal", 2, true), +// ], +// false, +// )), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 4, false), +// SerializedItem::new("Terminal", 3, true), +// ], +// true, +// )), +// ], +// ), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 5, true), +// SerializedItem::new("Terminal", 6, false), +// ], +// false, +// )), +// ], +// ); - let workspace = default_workspace(&["/tmp"], ¢er_pane); +// let workspace = default_workspace(&["/tmp"], ¢er_pane); - db.save_workspace(workspace.clone()).await; +// db.save_workspace(workspace.clone()).await; - let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); +// let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - assert_eq!(workspace.center_group, new_workspace.center_group); - } +// assert_eq!(workspace.center_group, new_workspace.center_group); +// } - #[gpui::test] - async fn test_cleanup_panes() { - env_logger::try_init().ok(); +// #[gpui::test] +// async fn test_cleanup_panes() { +// env_logger::try_init().ok(); - let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); +// let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - let center_pane = group( - gpui::Axis::Horizontal, - vec![ - group( - gpui::Axis::Vertical, - vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 2, true), - ], - false, - )), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 4, false), - SerializedItem::new("Terminal", 3, true), - ], - true, - )), - ], - ), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 5, false), - SerializedItem::new("Terminal", 6, true), - ], - false, - )), - ], - ); +// let center_pane = group( +// gpui::Axis::Horizontal, +// vec![ +// group( +// gpui::Axis::Vertical, +// vec![ +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 1, false), +// SerializedItem::new("Terminal", 2, true), +// ], +// false, +// )), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 4, false), +// SerializedItem::new("Terminal", 3, true), +// ], +// true, +// )), +// ], +// ), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 5, false), +// SerializedItem::new("Terminal", 6, true), +// ], +// false, +// )), +// ], +// ); - let id = &["/tmp"]; +// let id = &["/tmp"]; - let mut workspace = default_workspace(id, ¢er_pane); +// let mut workspace = default_workspace(id, ¢er_pane); - db.save_workspace(workspace.clone()).await; +// db.save_workspace(workspace.clone()).await; - workspace.center_group = group( - gpui::Axis::Vertical, - vec![ - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 1, false), - SerializedItem::new("Terminal", 2, true), - ], - false, - )), - SerializedPaneGroup::Pane(SerializedPane::new( - vec![ - SerializedItem::new("Terminal", 4, true), - SerializedItem::new("Terminal", 3, false), - ], - true, - )), - ], - ); +// workspace.center_group = group( +// gpui::Axis::Vertical, +// vec![ +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 1, false), +// SerializedItem::new("Terminal", 2, true), +// ], +// false, +// )), +// SerializedPaneGroup::Pane(SerializedPane::new( +// vec![ +// SerializedItem::new("Terminal", 4, true), +// SerializedItem::new("Terminal", 3, false), +// ], +// true, +// )), +// ], +// ); - db.save_workspace(workspace.clone()).await; +// db.save_workspace(workspace.clone()).await; - let new_workspace = db.workspace_for_roots(id).unwrap(); +// let new_workspace = db.workspace_for_roots(id).unwrap(); - assert_eq!(workspace.center_group, new_workspace.center_group); - } -} +// assert_eq!(workspace.center_group, new_workspace.center_group); +// } +// } diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 5f4c29cd5b..2e28dabffb 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -1,14 +1,14 @@ -use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; +use crate::{ + item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, +}; use anyhow::{Context, Result}; use async_recursion::async_recursion; -use db::sqlez::{ +use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui::{ - platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, -}; -use project::Project; +use gpui2::{AsyncAppContext, Handle, Task, View, WeakView, WindowBounds}; +use project2::Project; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -151,15 +151,11 @@ impl SerializedPaneGroup { #[async_recursion(?Send)] pub(crate) async fn deserialize( self, - project: &ModelHandle, + project: &Handle, workspace_id: WorkspaceId, - workspace: &WeakViewHandle, + workspace: &WeakView, cx: &mut AsyncAppContext, - ) -> Option<( - Member, - Option>, - Vec>>, - )> { + ) -> Option<(Member, Option>, Vec>>)> { match self { SerializedPaneGroup::Group { axis, @@ -208,10 +204,10 @@ impl SerializedPaneGroup { .read_with(cx, |pane, _| pane.items_len() != 0) .log_err()? { - let pane = pane.upgrade(cx)?; + let pane = pane.upgrade()?; Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) } else { - let pane = pane.upgrade(cx)?; + let pane = pane.upgrade()?; workspace .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx)) .log_err()?; @@ -235,10 +231,10 @@ impl SerializedPane { pub async fn deserialize_to( &self, - project: &ModelHandle, - pane: &WeakViewHandle, + project: &Handle, + pane: &WeakView, workspace_id: WorkspaceId, - workspace: &WeakViewHandle, + workspace: &WeakView, cx: &mut AsyncAppContext, ) -> Result>>> { let mut items = Vec::new(); diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index c3f4bb9723..4357c6a49d 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -1,10 +1,7 @@ use crate::ItemHandle; -use gpui::{ - elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle, - WindowContext, -}; +use gpui2::{AppContext, EventEmitter, View, ViewContext, WindowContext}; -pub trait ToolbarItemView: View { +pub trait ToolbarItemView: EventEmitter + Sized { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, @@ -32,7 +29,7 @@ pub trait ToolbarItemView: View { trait ToolbarItemViewHandle { fn id(&self) -> usize; - fn as_any(&self) -> &AnyViewHandle; + // fn as_any(&self) -> &AnyViewHandle; todo!() fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, @@ -57,84 +54,81 @@ pub struct Toolbar { items: Vec<(Box, ToolbarItemLocation)>, } -impl Entity for Toolbar { - type Event = (); -} +// todo!() +// impl View for Toolbar { +// fn ui_name() -> &'static str { +// "Toolbar" +// } -impl View for Toolbar { - fn ui_name() -> &'static str { - "Toolbar" - } +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = &theme::current(cx).workspace.toolbar; - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &theme::current(cx).workspace.toolbar; +// let mut primary_left_items = Vec::new(); +// let mut primary_right_items = Vec::new(); +// let mut secondary_item = None; +// let spacing = theme.item_spacing; +// let mut primary_items_row_count = 1; - let mut primary_left_items = Vec::new(); - let mut primary_right_items = Vec::new(); - let mut secondary_item = None; - let spacing = theme.item_spacing; - let mut primary_items_row_count = 1; +// for (item, position) in &self.items { +// match *position { +// ToolbarItemLocation::Hidden => {} - for (item, position) in &self.items { - match *position { - ToolbarItemLocation::Hidden => {} +// ToolbarItemLocation::PrimaryLeft { flex } => { +// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); +// let left_item = ChildView::new(item.as_any(), cx).aligned(); +// if let Some((flex, expanded)) = flex { +// primary_left_items.push(left_item.flex(flex, expanded).into_any()); +// } else { +// primary_left_items.push(left_item.into_any()); +// } +// } - ToolbarItemLocation::PrimaryLeft { flex } => { - primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); - let left_item = ChildView::new(item.as_any(), cx).aligned(); - if let Some((flex, expanded)) = flex { - primary_left_items.push(left_item.flex(flex, expanded).into_any()); - } else { - primary_left_items.push(left_item.into_any()); - } - } +// ToolbarItemLocation::PrimaryRight { flex } => { +// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); +// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float(); +// if let Some((flex, expanded)) = flex { +// primary_right_items.push(right_item.flex(flex, expanded).into_any()); +// } else { +// primary_right_items.push(right_item.into_any()); +// } +// } - ToolbarItemLocation::PrimaryRight { flex } => { - primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); - let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float(); - if let Some((flex, expanded)) = flex { - primary_right_items.push(right_item.flex(flex, expanded).into_any()); - } else { - primary_right_items.push(right_item.into_any()); - } - } +// ToolbarItemLocation::Secondary => { +// secondary_item = Some( +// ChildView::new(item.as_any(), cx) +// .constrained() +// .with_height(theme.height * item.row_count(cx) as f32) +// .into_any(), +// ); +// } +// } +// } - ToolbarItemLocation::Secondary => { - secondary_item = Some( - ChildView::new(item.as_any(), cx) - .constrained() - .with_height(theme.height * item.row_count(cx) as f32) - .into_any(), - ); - } - } - } +// let container_style = theme.container; +// let height = theme.height * primary_items_row_count as f32; - let container_style = theme.container; - let height = theme.height * primary_items_row_count as f32; +// let mut primary_items = Flex::row().with_spacing(spacing); +// primary_items.extend(primary_left_items); +// primary_items.extend(primary_right_items); - let mut primary_items = Flex::row().with_spacing(spacing); - primary_items.extend(primary_left_items); - primary_items.extend(primary_right_items); +// let mut toolbar = Flex::column(); +// if !primary_items.is_empty() { +// toolbar.add_child(primary_items.constrained().with_height(height)); +// } +// if let Some(secondary_item) = secondary_item { +// toolbar.add_child(secondary_item); +// } - let mut toolbar = Flex::column(); - if !primary_items.is_empty() { - toolbar.add_child(primary_items.constrained().with_height(height)); - } - if let Some(secondary_item) = secondary_item { - toolbar.add_child(secondary_item); - } - - if toolbar.is_empty() { - toolbar.into_any_named("toolbar") - } else { - toolbar - .contained() - .with_style(container_style) - .into_any_named("toolbar") - } - } -} +// if toolbar.is_empty() { +// toolbar.into_any_named("toolbar") +// } else { +// toolbar +// .contained() +// .with_style(container_style) +// .into_any_named("toolbar") +// } +// } +// } // <<<<<<< HEAD // ======= @@ -206,7 +200,7 @@ impl Toolbar { cx.notify(); } - pub fn add_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + pub fn add_item(&mut self, item: View, cx: &mut ViewContext) where T: 'static + ToolbarItemView, { @@ -252,7 +246,7 @@ impl Toolbar { } } - pub fn item_of_type(&self) -> Option> { + pub fn item_of_type(&self) -> Option> { self.items .iter() .find_map(|(item, _)| item.as_any().clone().downcast()) @@ -263,14 +257,15 @@ impl Toolbar { } } -impl ToolbarItemViewHandle for ViewHandle { +impl ToolbarItemViewHandle for View { fn id(&self) -> usize { self.id() } - fn as_any(&self) -> &AnyViewHandle { - self - } + // todo!() + // fn as_any(&self) -> &AnyViewHandle { + // self + // } fn set_active_pane_item( &self, @@ -294,8 +289,9 @@ impl ToolbarItemViewHandle for ViewHandle { } } -impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { - fn from(val: &dyn ToolbarItemViewHandle) -> Self { - val.as_any().clone() - } -} +// todo!() +// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { +// fn from(val: &dyn ToolbarItemViewHandle) -> Self { +// val.as_any().clone() +// } +// } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3436a6805e..3731094b26 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3,12 +3,12 @@ pub mod item; // pub mod notifications; pub mod pane; pub mod pane_group; -// mod persistence; +mod persistence; pub mod searchable; // pub mod shared_screen; // mod status_bar; mod toolbar; -// mod workspace_settings; +mod workspace_settings; use anyhow::{anyhow, Result}; // use call2::ActiveCall; @@ -36,13 +36,14 @@ use anyhow::{anyhow, Result}; // }, // AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext, // Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, -// ViewHandle, WeakViewHandle, WindowContext, WindowHandle, +// View, WeakViewHandle, WindowContext, WindowHandle, // }; // use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; // use itertools::Itertools; // use language2::{LanguageRegistry, Rope}; // use node_runtime::NodeRuntime;// // +use futures::channel::oneshot; // use crate::{ // notifications::{simple_message_notification::MessageNotification, NotificationTracker}, // persistence::model::{ @@ -91,7 +92,7 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; // fn has_focus(&self, cx: &WindowContext) -> bool; // } -// impl ModalHandle for ViewHandle { +// impl ModalHandle for View { // fn as_any(&self) -> &AnyViewHandle { // self // } @@ -376,61 +377,61 @@ pub fn register_project_item(cx: &mut AppContext) { }); } -// type FollowableItemBuilder = fn( -// ViewHandle, -// ViewHandle, -// ViewId, -// &mut Option, -// &mut AppContext, -// ) -> Option>>>; -// type FollowableItemBuilders = HashMap< -// TypeId, -// ( -// FollowableItemBuilder, -// fn(&AnyViewHandle) -> Box, -// ), -// >; -// pub fn register_followable_item(cx: &mut AppContext) { -// cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { -// builders.insert( -// TypeId::of::(), -// ( -// |pane, workspace, id, state, cx| { -// I::from_state_proto(pane, workspace, id, state, cx).map(|task| { -// cx.foreground() -// .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) -// }) -// }, -// |this| Box::new(this.clone().downcast::().unwrap()), -// ), -// ); -// }); -// } +type FollowableItemBuilder = fn( + View, + View, + ViewId, + &mut Option, + &mut AppContext, +) -> Option>>>; +type FollowableItemBuilders = HashMap< + TypeId, + ( + FollowableItemBuilder, + fn(&AnyView) -> Box, + ), +>; +pub fn register_followable_item(cx: &mut AppContext) { + cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { + builders.insert( + TypeId::of::(), + ( + |pane, workspace, id, state, cx| { + I::from_state_proto(pane, workspace, id, state, cx).map(|task| { + cx.foreground() + .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) + }) + }, + |this| Box::new(this.clone().downcast::().unwrap()), + ), + ); + }); +} -// type ItemDeserializers = HashMap< -// Arc, -// fn( -// ModelHandle, -// WeakViewHandle, -// WorkspaceId, -// ItemId, -// &mut ViewContext, -// ) -> Task>>, -// >; -// pub fn register_deserializable_item(cx: &mut AppContext) { -// cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| { -// if let Some(serialized_item_kind) = I::serialized_item_kind() { -// deserializers.insert( -// Arc::from(serialized_item_kind), -// |project, workspace, workspace_id, item_id, cx| { -// let task = I::deserialize(project, workspace, workspace_id, item_id, cx); -// cx.foreground() -// .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) -// }, -// ); -// } -// }); -// } +type ItemDeserializers = HashMap< + Arc, + fn( + Handle, + WeakView, + WorkspaceId, + ItemId, + &mut ViewContext, + ) -> Task>>, +>; +pub fn register_deserializable_item(cx: &mut AppContext) { + cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| { + if let Some(serialized_item_kind) = I::serialized_item_kind() { + deserializers.insert( + Arc::from(serialized_item_kind), + |project, workspace, workspace_id, item_id, cx| { + let task = I::deserialize(project, workspace, workspace_id, item_id, cx); + cx.foreground() + .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) + }, + ); + } + }); +} pub struct AppState { pub languages: Arc, @@ -493,54 +494,54 @@ struct Follower { // } // } -// struct DelayedDebouncedEditAction { -// task: Option>, -// cancel_channel: Option>, -// } +struct DelayedDebouncedEditAction { + task: Option>, + cancel_channel: Option>, +} -// impl DelayedDebouncedEditAction { -// fn new() -> DelayedDebouncedEditAction { -// DelayedDebouncedEditAction { -// task: None, -// cancel_channel: None, -// } -// } +impl DelayedDebouncedEditAction { + fn new() -> DelayedDebouncedEditAction { + DelayedDebouncedEditAction { + task: None, + cancel_channel: None, + } + } -// fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) -// where -// F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, -// { -// if let Some(channel) = self.cancel_channel.take() { -// _ = channel.send(()); -// } + fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) + where + F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, + { + if let Some(channel) = self.cancel_channel.take() { + _ = channel.send(()); + } -// let (sender, mut receiver) = oneshot::channel::<()>(); -// self.cancel_channel = Some(sender); + let (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; -// } + 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 => {} -// } + 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(); -// } -// })); -// } -// } + if let Some(result) = workspace + .update(&mut cx, |workspace, cx| (func)(workspace, cx)) + .log_err() + { + result.await.log_err(); + } + })); + } +} // pub enum Event { -// PaneAdded(ViewHandle), +// PaneAdded(View), // ContactRequestedJoin(u64), // } @@ -550,19 +551,19 @@ pub struct Workspace { // zoomed: Option, // zoomed_position: Option, // center: PaneGroup, - // left_dock: ViewHandle, - // bottom_dock: ViewHandle, - // right_dock: ViewHandle, + // left_dock: View, + // bottom_dock: View, + // right_dock: View, panes: Vec>, // panes_by_item: HashMap>, - // active_pane: ViewHandle, + // active_pane: View, last_active_center_pane: Option>, // last_active_view_id: Option, - // status_bar: ViewHandle, + // status_bar: View, // titlebar_item: Option, // notifications: Vec<(TypeId, usize, Box)>, project: Handle, - // follower_states: HashMap, FollowerState>, + // follower_states: HashMap, FollowerState>, // last_leaders_by_pane: HashMap, PeerId>, // window_edited: bool, // active_call: Option<(ModelHandle, Vec)>, @@ -930,19 +931,19 @@ impl Workspace { // self.weak_self.clone() // } - // pub fn left_dock(&self) -> &ViewHandle { + // pub fn left_dock(&self) -> &View { // &self.left_dock // } - // pub fn bottom_dock(&self) -> &ViewHandle { + // pub fn bottom_dock(&self) -> &View { // &self.bottom_dock // } - // pub fn right_dock(&self) -> &ViewHandle { + // pub fn right_dock(&self) -> &View { // &self.right_dock // } - // pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) + // pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) // where // T::Event: std::fmt::Debug, // { @@ -951,12 +952,12 @@ impl Workspace { // pub fn add_panel_with_extra_event_handler( // &mut self, - // panel: ViewHandle, + // panel: View, // cx: &mut ViewContext, // handler: F, // ) where // T::Event: std::fmt::Debug, - // F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, + // F: Fn(&mut Self, &View, &T::Event, &mut ViewContext) + 'static, // { // let dock = match panel.position(cx) { // DockPosition::Left => &self.left_dock, @@ -1033,7 +1034,7 @@ impl Workspace { // dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); // } - // pub fn status_bar(&self) -> &ViewHandle { + // pub fn status_bar(&self) -> &View { // &self.status_bar // } @@ -1617,10 +1618,10 @@ impl Workspace { // &mut self, // cx: &mut ViewContext, // add_view: F, - // ) -> Option> + // ) -> Option> // where // V: 'static + Modal, - // F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, + // F: FnOnce(&mut Self, &mut ViewContext) -> View, // { // cx.notify(); // // Whatever modal was visible is getting clobbered. If its the same type as V, then return @@ -1649,7 +1650,7 @@ impl Workspace { // } // } - // pub fn modal(&self) -> Option> { + // pub fn modal(&self) -> Option> { // self.modal // .as_ref() // .and_then(|modal| modal.view.as_any().clone().downcast::()) @@ -1676,14 +1677,14 @@ impl Workspace { // self.panes.iter().flat_map(|pane| pane.read(cx).items()) // } - // pub fn item_of_type(&self, cx: &AppContext) -> Option> { + // pub fn item_of_type(&self, cx: &AppContext) -> Option> { // self.items_of_type(cx).max_by_key(|item| item.id()) // } // pub fn items_of_type<'a, T: Item>( // &'a self, // cx: &'a AppContext, - // ) -> impl 'a + Iterator> { + // ) -> impl 'a + Iterator> { // self.panes // .iter() // .flat_map(|pane| pane.read(cx).items_of_type()) @@ -1834,7 +1835,7 @@ impl Workspace { // } // /// Transfer focus to the panel of the given type. - // pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { + // pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { // self.focus_or_unfocus_panel::(cx, |_, _| true)? // .as_any() // .clone() @@ -1888,7 +1889,7 @@ impl Workspace { // None // } - // pub fn panel(&self, cx: &WindowContext) -> Option> { + // pub fn panel(&self, cx: &WindowContext) -> Option> { // for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { // let dock = dock.read(cx); // if let Some(panel) = dock.panel::() { @@ -1956,7 +1957,7 @@ impl Workspace { // cx.notify(); // } - // fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { + // fn add_pane(&mut self, cx: &mut ViewContext) -> View { // let pane = cx.add_view(|cx| { // Pane::new( // self.weak_handle(), @@ -2138,7 +2139,7 @@ impl Workspace { // &mut self, // project_item: ModelHandle, // cx: &mut ViewContext, - // ) -> ViewHandle + // ) -> View // where // T: ProjectItem, // { @@ -2162,7 +2163,7 @@ impl Workspace { // &mut self, // project_item: ModelHandle, // cx: &mut ViewContext, - // ) -> ViewHandle + // ) -> View // where // T: ProjectItem, // { @@ -2259,7 +2260,7 @@ impl Workspace { // &mut self, // direction: SplitDirection, // cx: &mut ViewContext, - // ) -> Option<&ViewHandle> { + // ) -> Option<&View> { // let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else { // return None; // }; @@ -2280,7 +2281,7 @@ impl Workspace { // self.center.pane_at_pixel_position(target) // } - // fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { + // fn handle_pane_focused(&mut self, pane: View, cx: &mut ViewContext) { // if self.active_pane != pane { // self.active_pane = pane.clone(); // self.status_bar.update(cx, |status_bar, cx| { @@ -2304,7 +2305,7 @@ impl Workspace { // fn handle_pane_event( // &mut self, - // pane: ViewHandle, + // pane: View, // event: &pane::Event, // cx: &mut ViewContext, // ) { @@ -2363,10 +2364,10 @@ impl Workspace { // pub fn split_pane( // &mut self, - // pane_to_split: ViewHandle, + // pane_to_split: View, // split_direction: SplitDirection, // cx: &mut ViewContext, - // ) -> ViewHandle { + // ) -> View { // let new_pane = self.add_pane(cx); // self.center // .split(&pane_to_split, &new_pane, split_direction) @@ -2377,10 +2378,10 @@ impl Workspace { // pub fn split_and_clone( // &mut self, - // pane: ViewHandle, + // pane: View, // direction: SplitDirection, // cx: &mut ViewContext, - // ) -> Option> { + // ) -> Option> { // let item = pane.read(cx).active_item()?; // let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { // let new_pane = self.add_pane(cx); @@ -2440,8 +2441,8 @@ impl Workspace { // pub fn move_item( // &mut self, - // source: ViewHandle, - // destination: ViewHandle, + // source: View, + // destination: View, // item_id_to_move: usize, // destination_index: usize, // cx: &mut ViewContext, @@ -2473,7 +2474,7 @@ impl Workspace { // }); // } - // fn remove_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { + // fn remove_pane(&mut self, pane: View, cx: &mut ViewContext) { // if self.center.remove(&pane).unwrap() { // self.force_remove_pane(&pane, cx); // self.unfollow(&pane, cx); @@ -2488,11 +2489,11 @@ impl Workspace { // } // } - // pub fn panes(&self) -> &[ViewHandle] { + // pub fn panes(&self) -> &[View] { // &self.panes // } - // pub fn active_pane(&self) -> &ViewHandle { + // pub fn active_pane(&self) -> &View { // &self.active_pane // } @@ -2651,7 +2652,7 @@ impl Workspace { // pub fn unfollow( // &mut self, - // pane: &ViewHandle, + // pane: &View, // cx: &mut ViewContext, // ) -> Option { // let state = self.follower_states.remove(pane)?; @@ -2959,7 +2960,7 @@ impl Workspace { // async fn add_views_from_leader( // this: WeakViewHandle, // leader_id: PeerId, - // panes: Vec>, + // panes: Vec>, // views: Vec, // cx: &mut AsyncAppContext, // ) -> Result<()> { @@ -3060,7 +3061,7 @@ impl Workspace { // }) // } - // pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { + // pub fn leader_for_pane(&self, pane: &View) -> Option { // self.follower_states.get(pane).map(|state| state.leader_id) // } @@ -3133,9 +3134,9 @@ impl Workspace { // fn shared_screen_for_peer( // &self, // peer_id: PeerId, - // pane: &ViewHandle, + // pane: &View, // cx: &mut ViewContext, - // ) -> Option> { + // ) -> Option> { // let call = self.active_call()?; // let room = call.read(cx).room()?.read(cx); // let participant = room.remote_participant_for_peer_id(peer_id)?; @@ -3229,7 +3230,7 @@ impl Workspace { // } // } - // fn force_remove_pane(&mut self, pane: &ViewHandle, cx: &mut ViewContext) { + // fn force_remove_pane(&mut self, pane: &View, cx: &mut ViewContext) { // self.panes.retain(|p| p != pane); // cx.focus(self.panes.last().unwrap()); // if self.last_active_center_pane == Some(pane.downgrade()) { @@ -3248,7 +3249,7 @@ impl Workspace { // fn serialize_workspace(&self, cx: &ViewContext) { // fn serialize_pane_handle( - // pane_handle: &ViewHandle, + // pane_handle: &View, // cx: &AppContext, // ) -> SerializedPane { // let (items, active) = { @@ -4075,7 +4076,7 @@ impl Workspace { // fn file_project_paths(&self, cx: &AppContext) -> Vec; // } -// impl WorkspaceHandle for ViewHandle { +// impl WorkspaceHandle for View { // fn file_project_paths(&self, cx: &AppContext) -> Vec { // self.read(cx) // .worktrees(cx) @@ -4320,13 +4321,16 @@ pub async fn activate_workspace_for_project( // None // } -use client2::{proto::PeerId, Client, UserStore}; +use client2::{ + proto::{self, PeerId, ViewId}, + Client, UserStore, +}; use collections::{HashMap, HashSet}; use gpui2::{ - AnyHandle, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View, ViewContext, - WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions, + AnyHandle, AnyView, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View, + ViewContext, WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions, }; -use item::{ItemHandle, ProjectItem}; +use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; use node_runtime::NodeRuntime; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; @@ -4334,6 +4338,7 @@ use std::{ any::TypeId, path::{Path, PathBuf}, sync::Arc, + time::Duration, }; use util::ResultExt; diff --git a/crates/workspace2/src/workspace_settings.rs b/crates/workspace2/src/workspace_settings.rs index 6483167018..5d158e5a05 100644 --- a/crates/workspace2/src/workspace_settings.rs +++ b/crates/workspace2/src/workspace_settings.rs @@ -1,6 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::Setting; +use settings2::Settings; #[derive(Deserialize)] pub struct WorkspaceSettings { @@ -41,7 +41,7 @@ pub enum GitGutterSetting { Hide, } -impl Setting for WorkspaceSettings { +impl Settings for WorkspaceSettings { const KEY: Option<&'static str> = None; type FileContent = WorkspaceSettingsContent; @@ -49,7 +49,7 @@ impl Setting for WorkspaceSettings { fn load( default_value: &Self::FileContent, user_values: &[&Self::FileContent], - _: &gpui::AppContext, + _: &gpui2::AppContext, ) -> anyhow::Result { Self::load_via_json_merge(default_value, user_values) } From b9ce186d21e0f9d57a211fd8e1b0c36482293690 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 16:54:55 +0100 Subject: [PATCH 07/66] WIP --- crates/gpui2/src/view.rs | 7 +++++++ crates/workspace2/src/item.rs | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index e3e89b2add..897026267f 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -57,6 +57,13 @@ impl View { { cx.update_view(self, f) } + + pub fn read(&self, cx: &mut C) -> &V + where + C: VisualContext, + { + todo!() + } } impl Clone for View { diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index d359427053..aedbcd8393 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -159,9 +159,9 @@ pub trait Item: EventEmitter + Sized { // ) -> Task> { // unimplemented!("reload() must be implemented if can_save() returns true") // } - // fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { - // SmallVec::new() - // } + fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + SmallVec::new() + } // fn should_close_item_on_event(_: &Self::Event) -> bool { // false // } From 9688937468008db42e147edc7932b3f1c7a6729d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 19:33:23 +0100 Subject: [PATCH 08/66] WIP --- crates/gpui2/src/app/entity_map.rs | 25 ++++++++++-- crates/gpui2/src/gpui2.rs | 10 +++++ crates/gpui2/src/view.rs | 36 +++++++++++------ crates/gpui2/src/window.rs | 64 ++++++++++++++++++++++-------- crates/workspace2/src/item.rs | 51 +++++++++++------------- 5 files changed, 127 insertions(+), 59 deletions(-) diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index f3ae67836d..a1a070a911 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -1,4 +1,4 @@ -use crate::{AnyBox, AppContext, Context}; +use crate::{AnyBox, AppContext, Context, EntityHandle}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; @@ -329,7 +329,26 @@ impl Eq for Handle {} impl PartialEq> for Handle { fn eq(&self, other: &WeakHandle) -> bool { - self.entity_id() == other.entity_id() + self.entity_id == other.entity_id + } +} + +impl EntityHandle for Handle { + type Weak = WeakHandle; + + fn entity_id(&self) -> EntityId { + self.entity_id + } + + fn downgrade(&self) -> Self::Weak { + self.downgrade() + } + + fn upgrade_from(weak: &Self::Weak) -> Option + where + Self: Sized, + { + weak.upgrade() } } @@ -457,6 +476,6 @@ impl Eq for WeakHandle {} impl PartialEq> for WeakHandle { fn eq(&self, other: &Handle) -> bool { - self.entity_id() == other.entity_id() + self.entity_id == other.entity_id } } diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index f6fa280c76..824236d340 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -106,6 +106,16 @@ pub trait VisualContext: Context { ) -> Self::Result; } +pub trait EntityHandle { + type Weak: 'static + Send; + + fn entity_id(&self) -> EntityId; + fn downgrade(&self) -> Self::Weak; + fn upgrade_from(weak: &Self::Weak) -> Option + where + Self: Sized; +} + pub enum GlobalKey { Numeric(usize), View(EntityId), diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 897026267f..a61fd10441 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,7 +1,7 @@ use crate::{ - AnyBox, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId, - EntityId, Flatten, Handle, LayoutId, Pixels, Size, ViewContext, VisualContext, WeakHandle, - WindowContext, + AnyBox, AnyElement, AppContext, AvailableSpace, BorrowWindow, Bounds, Component, Element, + ElementId, EntityHandle, EntityId, Flatten, Handle, LayoutId, Pixels, Size, ViewContext, + VisualContext, WeakHandle, WindowContext, }; use anyhow::{Context, Result}; use parking_lot::Mutex; @@ -31,9 +31,7 @@ impl View { )), } } -} -impl View { pub fn into_any(self) -> AnyView { AnyView(Arc::new(self)) } @@ -44,9 +42,7 @@ impl View { render: Arc::downgrade(&self.render), } } -} -impl View { pub fn update( &self, cx: &mut C, @@ -58,11 +54,8 @@ impl View { cx.update_view(self, f) } - pub fn read(&self, cx: &mut C) -> &V - where - C: VisualContext, - { - todo!() + pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V { + cx.entities.read(&self.state) } } @@ -124,6 +117,25 @@ impl Element<()> for View { } } +impl EntityHandle for View { + type Weak = WeakView; + + fn entity_id(&self) -> EntityId { + self.state.entity_id + } + + fn downgrade(&self) -> Self::Weak { + self.downgrade() + } + + fn upgrade_from(weak: &Self::Weak) -> Option + where + Self: Sized, + { + weak.upgrade() + } +} + pub struct WeakView { pub(crate) state: WeakHandle, render: Weak) -> AnyElement + Send + 'static>>, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e89c713d93..0f2dcc7049 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, - EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, FontId, GlobalElementId, - GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, - Keystroke, LayoutId, MainThread, MainThreadOnly, ModelContext, 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, View, VisualContext, WeakHandle, WeakView, - WindowOptions, SUBPIXEL_VARIANTS, + EntityHandle, EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, FontId, + GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, + KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, ModelContext, 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, View, VisualContext, + WeakHandle, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -376,6 +376,35 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.notify(); } + pub fn subscribe( + &mut self, + handle: &H, + mut on_event: impl FnMut(H, &E::Event, &mut WindowContext<'_, '_>) + Send + 'static, + ) -> Subscription + where + E: EventEmitter, + H: EntityHandle, + { + let entity_id = handle.entity_id(); + let handle = handle.downgrade(); + let window_handle = self.window.handle; + self.app.event_listeners.insert( + entity_id, + Box::new(move |event, cx| { + cx.update_window(window_handle, |cx| { + if let Some(handle) = H::upgrade_from(&handle) { + let event = event.downcast_ref().expect("invalid event type"); + on_event(handle, event, cx); + true + } else { + false + } + }) + .unwrap_or(false) + }), + ) + } + /// Schedule the given closure to be run on the main thread. It will be invoked with /// a `MainThread`, which provides access to platform-specific functionality /// of the window. @@ -1600,21 +1629,24 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { ) } - pub fn subscribe( + pub fn subscribe( &mut self, - handle: &Handle, - mut on_event: impl FnMut(&mut V, Handle, &E::Event, &mut ViewContext<'_, '_, V>) - + Send - + 'static, - ) -> Subscription { + handle: &H, + mut on_event: impl FnMut(&mut V, H, &E::Event, &mut ViewContext<'_, '_, V>) + Send + 'static, + ) -> Subscription + where + E: EventEmitter, + H: EntityHandle, + { let view = self.view(); + let entity_id = handle.entity_id(); let handle = handle.downgrade(); let window_handle = self.window.handle; self.app.event_listeners.insert( - handle.entity_id, + entity_id, Box::new(move |event, cx| { cx.update_window(window_handle, |cx| { - if let Some(handle) = handle.upgrade() { + if let Some(handle) = H::upgrade_from(&handle) { let event = event.downcast_ref().expect("invalid event type"); view.update(cx, |this, cx| on_event(this, handle, event, cx)) .is_ok() diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index aedbcd8393..90d08d6c4a 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -104,30 +104,26 @@ pub trait Item: EventEmitter + Sized { // 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 tab_tooltip_text(&self, _: &AppContext) -> Option { + None + } + fn tab_description(&self, _: usize, _: &AppContext) -> Option { + None + } + fn tab_content(&self, detail: Option, 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 clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option + where + Self: Sized, + { + None + } // fn is_dirty(&self, _: &AppContext) -> bool { // false // } @@ -221,7 +217,6 @@ pub trait Item: EventEmitter + Sized { use std::{ any::Any, - borrow::Cow, cell::RefCell, ops::Range, path::PathBuf, @@ -235,7 +230,7 @@ use std::{ use gpui2::{ AnyElement, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, Point, - Task, View, ViewContext, WindowContext, + SharedString, Task, View, ViewContext, VisualContext, WindowContext, }; use project2::{Project, ProjectEntryId, ProjectPath}; use smallvec::SmallVec; @@ -252,10 +247,10 @@ pub trait ItemHandle: 'static + Send { fn subscribe_to_item_events( &self, cx: &mut WindowContext, - handler: Box, + 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_tooltip_text(&self, cx: &AppContext) -> Option; + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option; fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; fn dragged_tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; fn project_path(&self, cx: &AppContext) -> Option; @@ -329,7 +324,7 @@ impl ItemHandle for View { fn subscribe_to_item_events( &self, cx: &mut WindowContext, - handler: Box, + handler: Box, ) -> gpui2::Subscription { cx.subscribe(self, move |_, event, cx| { for item_event in T::to_item_events(event) { @@ -338,11 +333,11 @@ impl ItemHandle for View { }) } - fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option> { + fn tab_tooltip_text(&self, cx: &AppContext) -> Option { self.read(cx).tab_tooltip_text(cx) } - fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option> { + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option { self.read(cx).tab_description(detail, cx) } From 538a9e1392905a356acd2ecd81dc48e3f3ecb1c3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 Oct 2023 11:56:55 +0100 Subject: [PATCH 09/66] WIP --- crates/gpui2/src/view.rs | 38 +- crates/workspace2/src/item.rs | 224 ++--- crates/workspace2/src/pane.rs | 1061 ++++++++++---------- crates/workspace2/src/persistence/model.rs | 4 +- crates/workspace2/src/workspace2.rs | 346 +++---- 5 files changed, 781 insertions(+), 892 deletions(-) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index de10c7f338..8bef25b92d 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -305,42 +305,6 @@ impl Component for AnyView { } } -impl Element<()> for AnyView { - type ElementState = AnyBox; - - fn id(&self) -> Option { - Some(ElementId::View(self.0.entity_id())) - } - - fn initialize( - &mut self, - _: &mut (), - _: Option, - cx: &mut ViewContext<()>, - ) -> Self::ElementState { - self.0.initialize(cx) - } - - fn layout( - &mut self, - _: &mut (), - element: &mut Self::ElementState, - cx: &mut ViewContext<()>, - ) -> LayoutId { - self.0.layout(element, cx) - } - - fn paint( - &mut self, - bounds: Bounds, - _: &mut (), - element: &mut AnyBox, - cx: &mut ViewContext<()>, - ) { - self.0.paint(bounds, element, cx) - } -} - impl std::fmt::Debug for AnyView { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.debug(f) @@ -376,7 +340,7 @@ impl Element for EraseAnyViewState { type ElementState = AnyBox; fn id(&self) -> Option { - Element::id(&self.view) + Some(self.view.0.entity_id().into()) } fn initialize( diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index eddc9200f9..5995487f07 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -5,7 +5,7 @@ // use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; use anyhow::Result; use client2::{ - proto::{self, PeerId, ViewId}, + proto::{self, PeerId}, Client, }; use settings2::Settings; @@ -98,12 +98,12 @@ pub struct BreadcrumbText { pub highlights: Option, HighlightStyle)>>, } -pub trait Item: EventEmitter + Sized { - // fn deactivated(&mut self, _: &mut ViewContext) {} - // fn workspace_deactivated(&mut self, _: &mut ViewContext) {} - // fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { - // false - // } +pub trait Item: Render + EventEmitter + Send { + 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 } @@ -117,53 +117,53 @@ pub trait Item: EventEmitter + Sized { 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 + 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: Model, - // _cx: &mut ViewContext, - // ) -> Task> { - // unimplemented!("save() must be implemented if can_save() returns true") - // } - // fn save_as( - // &mut self, - // _project: Model, - // _abs_path: PathBuf, - // _cx: &mut ViewContext, - // ) -> Task> { - // unimplemented!("save_as() must be implemented if can_save() returns true") - // } - // fn reload( - // &mut self, - // _project: Model, - // _cx: &mut ViewContext, - // ) -> Task> { - // unimplemented!("reload() must be implemented if can_save() returns true") - // } + 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: Model, _cx: &mut ViewContext) -> Task> { + unimplemented!("save() must be implemented if can_save() returns true") + } + fn save_as( + &mut self, + _project: Model, + _abs_path: PathBuf, + _cx: &mut ViewContext, + ) -> Task> { + unimplemented!("save_as() must be implemented if can_save() returns true") + } + fn reload( + &mut self, + _project: Model, + _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 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, @@ -178,41 +178,41 @@ pub trait Item: EventEmitter + Sized { // } // } - // fn as_searchable(&self, _: &View) -> Option> { - // None - // } + fn as_searchable(&self, _: &View) -> Option> { + None + } - // fn breadcrumb_location(&self) -> ToolbarItemLocation { - // ToolbarItemLocation::Hidden - // } + fn breadcrumb_location(&self) -> ToolbarItemLocation { + ToolbarItemLocation::Hidden + } - // fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { - // None - // } + fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { + None + } - // fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} + fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} - // fn serialized_item_kind() -> Option<&'static str> { - // None - // } + fn serialized_item_kind() -> Option<&'static str> { + None + } - // fn deserialize( - // _project: Model, - // _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 - // } + fn deserialize( + _project: Model, + _workspace: WeakView, + _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 std::{ @@ -229,18 +229,19 @@ use std::{ }; use gpui2::{ - AnyElement, AnyWindowHandle, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, - SharedString, Task, View, ViewContext, WindowContext, + AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, HighlightStyle, Model, Pixels, + Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, }; use project2::{Project, ProjectEntryId, ProjectPath}; use smallvec::SmallVec; use crate::{ pane::{self, Pane}, + persistence::model::ItemId, searchable::SearchableItemHandle, workspace_settings::{AutosaveSetting, WorkspaceSettings}, - DelayedDebouncedEditAction, FollowableItemBuilders, ToolbarItemLocation, Workspace, - WorkspaceId, + DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation, + ViewId, Workspace, WorkspaceId, }; pub trait ItemHandle: 'static + Send { @@ -275,7 +276,7 @@ pub trait ItemHandle: 'static + Send { fn navigate(&self, data: Box, cx: &mut WindowContext) -> bool; fn id(&self) -> usize; fn window(&self) -> AnyWindowHandle; - // fn as_any(&self) -> &AnyView; todo!() + fn to_any(&self) -> AnyView; fn is_dirty(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool; fn can_save(&self, cx: &AppContext) -> bool; @@ -302,10 +303,10 @@ pub trait ItemHandle: 'static + Send { fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option>; } -pub trait WeakItemHandle { +pub trait WeakItemHandle: Send { fn id(&self) -> usize; fn window(&self) -> AnyWindowHandle; - fn upgrade(&self, cx: &AppContext) -> Option>; + fn upgrade(&self) -> Option>; } // todo!() @@ -399,10 +400,8 @@ impl ItemHandle for View { 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) + self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx)) + .map(|handle| Box::new(handle) as Box) } fn added_to_pane( @@ -447,7 +446,7 @@ impl ItemHandle for View { let pane = if let Some(pane) = workspace .panes_by_item .get(&item.id()) - .and_then(|pane| pane.upgrade(cx)) + .and_then(|pane| pane.upgrade()) { pane } else { @@ -570,10 +569,9 @@ impl ItemHandle for View { // AnyViewHandle::window(self) } - // todo!() - // fn as_any(&self) -> &AnyViewHandle { - // self - // } + fn to_any(&self) -> AnyView { + self.clone().into_any() + } fn is_dirty(&self, cx: &AppContext) -> bool { self.read(cx).is_dirty(cx) @@ -652,17 +650,17 @@ impl ItemHandle for View { } } -// impl From> for AnyViewHandle { -// fn from(val: Box) -> Self { -// val.as_any().clone() -// } -// } +impl From> for AnyView { + fn from(val: Box) -> Self { + val.to_any() + } +} -// impl From<&Box> for AnyViewHandle { -// fn from(val: &Box) -> Self { -// val.as_any().clone() -// } -// } +impl From<&Box> for AnyView { + fn from(val: &Box) -> Self { + val.to_any() + } +} impl Clone for Box { fn clone(&self) -> Box { @@ -670,19 +668,19 @@ impl Clone for Box { } } -// impl WeakItemHandle for WeakViewHandle { -// fn id(&self) -> usize { -// self.id() -// } +impl WeakItemHandle for WeakView { + fn id(&self) -> usize { + self.id() + } -// fn window(&self) -> AnyWindowHandle { -// self.window() -// } + fn window(&self) -> AnyWindowHandle { + self.window() + } -// fn upgrade(&self, cx: &AppContext) -> Option> { -// self.upgrade(cx).map(|v| Box::new(v) as Box) -// } -// } + fn upgrade(&self) -> Option> { + self.upgrade().map(|v| Box::new(v) as Box) + } +} pub trait ProjectItem: Item { type Item: project2::Item; diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index e0eb1b7ec2..ca42b0ef2e 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1,48 +1,20 @@ // 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 crate::{ + item::{Item, ItemHandle, WeakItemHandle}, + SplitDirection, Workspace, +}; +use collections::{HashMap, VecDeque}; +use gpui2::{EventEmitter, Model, View, ViewContext, WeakView}; +use parking_lot::Mutex; +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; +use std::{ + any::Any, + cmp, fmt, mem, + path::PathBuf, + sync::{atomic::AtomicUsize, Arc}, +}; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -69,19 +41,19 @@ pub enum SaveIntent { // #[derive(Clone, PartialEq)] // pub struct CloseItemById { // pub item_id: usize, -// pub pane: WeakViewHandle, +// pub pane: WeakView, // } // #[derive(Clone, PartialEq)] // pub struct CloseItemsToTheLeftById { // pub item_id: usize, -// pub pane: WeakViewHandle, +// pub pane: WeakView, // } // #[derive(Clone, PartialEq)] // pub struct CloseItemsToTheRightById { // pub item_id: usize, -// pub pane: WeakViewHandle, +// pub pane: WeakView, // } // #[derive(Clone, PartialEq, Debug, Deserialize, Default)] @@ -146,7 +118,6 @@ pub enum SaveIntent { // cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)); // } -#[derive(Debug)] pub enum Event { AddItem { item: Box }, ActivateItem { local: bool }, @@ -159,35 +130,44 @@ pub enum Event { ZoomOut, } -use crate::{ - item::{ItemHandle, WeakItemHandle}, - SplitDirection, -}; -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}, -}; +impl fmt::Debug for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Event::AddItem { item } => f.debug_struct("AddItem").field("item", &item.id()).finish(), + Event::ActivateItem { local } => f + .debug_struct("ActivateItem") + .field("local", local) + .finish(), + Event::Remove => f.write_str("Remove"), + Event::RemoveItem { item_id } => f + .debug_struct("RemoveItem") + .field("item_id", item_id) + .finish(), + Event::Split(direction) => f + .debug_struct("Split") + .field("direction", direction) + .finish(), + Event::ChangeItemTitle => f.write_str("ChangeItemTitle"), + Event::Focus => f.write_str("Focus"), + Event::ZoomIn => f.write_str("ZoomIn"), + Event::ZoomOut => f.write_str("ZoomOut"), + } + } +} pub struct Pane { items: Vec>, - // activation_history: Vec, + activation_history: Vec, // zoomed: bool, - // active_item_index: usize, + active_item_index: usize, // last_focused_view_by_item: HashMap, // autoscroll: bool, nav_history: NavHistory, - // toolbar: ViewHandle, + toolbar: View, // tab_bar_context_menu: TabBarContextMenu, // tab_context_menu: ViewHandle, - // workspace: WeakViewHandle, - project: Handle, + // workspace: WeakView, + project: Model, // has_focus: bool, // can_drop: Rc, &WindowContext) -> bool>, // can_split: bool, @@ -196,11 +176,11 @@ pub struct Pane { pub struct ItemNavHistory { history: NavHistory, - item: Rc, + item: Arc, } #[derive(Clone)] -pub struct NavHistory(Rc>); +pub struct NavHistory(Arc>); struct NavHistoryState { mode: NavigationMode, @@ -229,14 +209,14 @@ impl Default for NavigationMode { } pub struct NavigationEntry { - pub item: Rc, + pub item: Arc, pub data: Option>, pub timestamp: usize, } // pub struct DraggedItem { // pub handle: Box, -// pub pane: WeakViewHandle, +// pub pane: WeakView, // } // pub enum ReorderBehavior { @@ -315,108 +295,113 @@ pub struct NavigationEntry { // .into_any_named("nav button") // } +impl EventEmitter for Pane { + type Event = Event; +} + impl Pane { - // pub fn new( - // workspace: WeakViewHandle, - // project: ModelHandle, - // next_timestamp: Arc, - // cx: &mut ViewContext, - // ) -> Self { - // let pane_view_id = cx.view_id(); - // let handle = cx.weak_handle(); - // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)); - // context_menu.update(cx, |menu, _| { - // menu.set_position_mode(OverlayPositionMode::Local) - // }); + pub fn new( + workspace: WeakView, + project: Model, + next_timestamp: Arc, + cx: &mut ViewContext, + ) -> Self { + // todo!("context menu") + // let pane_view_id = cx.view_id(); + // 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"; - // } + let handle = cx.view(); + 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(Arc::new(Mutex::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() - // }), - // } - // } + // 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 { + // pub(crate) fn workspace(&self) -> &WeakView { // &self.workspace // } @@ -455,12 +440,12 @@ impl Pane { // cx.notify(); // } - // pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { - // ItemNavHistory { - // history: self.nav_history.clone(), - // item: Rc::new(item.downgrade()), - // } - // } + pub fn nav_history_for_item(&self, item: &View) -> ItemNavHistory { + ItemNavHistory { + history: self.nav_history.clone(), + item: Arc::new(item.downgrade()), + } + } // pub fn nav_history(&self) -> &NavHistory { // &self.nav_history @@ -532,7 +517,7 @@ impl Pane { let abs_path = project.absolute_path(&project_path, cx); self.nav_history .0 - .borrow_mut() + .lock() .paths_by_item .insert(item.id(), (project_path, abs_path)); } @@ -1071,8 +1056,8 @@ impl Pane { // } // pub async fn save_item( - // project: ModelHandle, - // pane: &WeakViewHandle, + // project: Model, + // pane: &WeakView, // item_ix: usize, // item: &dyn ItemHandle, // save_intent: SaveIntent, @@ -1178,7 +1163,7 @@ impl Pane { // pub fn autosave_item( // item: &dyn ItemHandle, - // project: ModelHandle, + // project: Model, // cx: &mut WindowContext, // ) -> Task> { // if Self::can_autosave_item(item, cx) { @@ -1340,15 +1325,15 @@ impl Pane { // Some(()) // } - // fn update_toolbar(&mut self, cx: &mut ViewContext) { - // let active_item = self - // .items - // .get(self.active_item_index) - // .map(|item| item.as_ref()); - // self.toolbar.update(cx, |toolbar, cx| { - // toolbar.set_active_item(active_item, cx); - // }); - // } + fn update_toolbar(&mut self, cx: &mut ViewContext) { + let active_item = self + .items + .get(self.active_item_index) + .map(|item| item.as_ref()); + self.toolbar.update(cx, |toolbar, cx| { + toolbar.set_active_item(active_item, cx); + }); + } // fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { // let theme = theme::current(cx).clone(); @@ -1544,7 +1529,7 @@ impl Pane { // fn render_tab( // item: &Box, - // pane: WeakViewHandle, + // pane: WeakView, // first: bool, // detail: Option, // hovered: bool, @@ -1557,7 +1542,7 @@ impl Pane { // fn render_dragged_tab( // item: &Box, - // pane: WeakViewHandle, + // pane: WeakView, // first: bool, // detail: Option, // hovered: bool, @@ -1571,7 +1556,7 @@ impl Pane { // fn render_tab_with_title( // title: AnyElement, // item: &Box, - // pane: WeakViewHandle, + // pane: WeakView, // first: bool, // hovered: bool, // tab_style: &theme::Tab, @@ -1736,387 +1721,387 @@ impl Pane { // pub fn is_zoomed(&self) -> bool { // self.zoomed // } - // } +} - // impl Entity for Pane { - // type Event = Event; - // } +// impl Entity for Pane { +// type Event = Event; +// } - // impl View for Pane { - // fn ui_name() -> &'static str { - // "Pane" - // } +// impl View for Pane { +// fn ui_name() -> &'static str { +// "Pane" +// } - // fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - // enum MouseNavigationHandler {} +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// enum MouseNavigationHandler {} - // MouseEventHandler::new::(0, cx, |_, cx| { - // let active_item_index = self.active_item_index; +// MouseEventHandler::new::(0, cx, |_, cx| { +// let active_item_index = self.active_item_index; - // if let Some(active_item) = self.active_item() { - // Flex::column() - // .with_child({ - // let theme = theme::current(cx).clone(); +// if let Some(active_item) = self.active_item() { +// Flex::column() +// .with_child({ +// let theme = theme::current(cx).clone(); - // let mut stack = Stack::new(); +// let mut stack = Stack::new(); - // enum TabBarEventHandler {} - // stack.add_child( - // MouseEventHandler::new::(0, cx, |_, _| { - // Empty::new() - // .contained() - // .with_style(theme.workspace.tab_bar.container) - // }) - // .on_down( - // MouseButton::Left, - // move |_, this, cx| { - // this.activate_item(active_item_index, true, true, cx); - // }, - // ), - // ); - // let tooltip_style = theme.tooltip.clone(); - // let tab_bar_theme = theme.workspace.tab_bar.clone(); +// enum TabBarEventHandler {} +// stack.add_child( +// MouseEventHandler::new::(0, cx, |_, _| { +// Empty::new() +// .contained() +// .with_style(theme.workspace.tab_bar.container) +// }) +// .on_down( +// MouseButton::Left, +// move |_, this, cx| { +// this.activate_item(active_item_index, true, true, cx); +// }, +// ), +// ); +// let tooltip_style = theme.tooltip.clone(); +// let tab_bar_theme = theme.workspace.tab_bar.clone(); - // let nav_button_height = tab_bar_theme.height; - // let button_style = tab_bar_theme.nav_button; - // let border_for_nav_buttons = tab_bar_theme - // .tab_style(false, false) - // .container - // .border - // .clone(); +// let 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")); +// 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(), - // ) - // } +// if self.has_focus { +// let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); +// tab_row.add_child( +// (render_tab_bar_buttons)(self, cx) +// .contained() +// .with_style(theme.workspace.tab_bar.pane_button_container) +// .flex(1., false) +// .into_any(), +// ) +// } - // stack.add_child(tab_row); - // stack - // .constrained() - // .with_height(theme.workspace.tab_bar.height) - // .flex(1., false) - // .into_any_named("tab bar") - // }) - // .with_child({ - // enum PaneContentTabDropTarget {} - // dragged_item_receiver::( - // self, - // 0, - // self.active_item_index + 1, - // !self.can_split, - // if self.can_split { Some(100.) } else { None }, - // cx, - // { - // let toolbar = self.toolbar.clone(); - // let toolbar_hidden = toolbar.read(cx).hidden(); - // move |_, cx| { - // Flex::column() - // .with_children( - // (!toolbar_hidden) - // .then(|| ChildView::new(&toolbar, cx).expanded()), - // ) - // .with_child( - // ChildView::new(active_item.as_any(), cx).flex(1., true), - // ) - // } - // }, - // ) - // .flex(1., true) - // }) - // .with_child(ChildView::new(&self.tab_context_menu, cx)) - // .into_any() - // } else { - // enum EmptyPane {} - // let theme = theme::current(cx).clone(); +// stack.add_child(tab_row); +// stack +// .constrained() +// .with_height(theme.workspace.tab_bar.height) +// .flex(1., false) +// .into_any_named("tab bar") +// }) +// .with_child({ +// enum PaneContentTabDropTarget {} +// dragged_item_receiver::( +// self, +// 0, +// self.active_item_index + 1, +// !self.can_split, +// if self.can_split { Some(100.) } else { None }, +// cx, +// { +// let toolbar = self.toolbar.clone(); +// let toolbar_hidden = toolbar.read(cx).hidden(); +// move |_, cx| { +// Flex::column() +// .with_children( +// (!toolbar_hidden) +// .then(|| ChildView::new(&toolbar, cx).expanded()), +// ) +// .with_child( +// ChildView::new(active_item.as_any(), cx).flex(1., true), +// ) +// } +// }, +// ) +// .flex(1., true) +// }) +// .with_child(ChildView::new(&self.tab_context_menu, cx)) +// .into_any() +// } else { +// enum EmptyPane {} +// let theme = theme::current(cx).clone(); - // dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { - // self.render_blank_pane(&theme, cx) - // }) - // .on_down(MouseButton::Left, |_, _, cx| { - // cx.focus_parent(); - // }) - // .into_any() - // } - // }) - // .on_down( - // MouseButton::Navigate(NavigationDirection::Back), - // move |_, pane, cx| { - // if let Some(workspace) = pane.workspace.upgrade(cx) { - // let pane = cx.weak_handle(); - // cx.window_context().defer(move |cx| { - // workspace.update(cx, |workspace, cx| { - // workspace.go_back(pane, cx).detach_and_log_err(cx) - // }) - // }) - // } - // }, - // ) - // .on_down(MouseButton::Navigate(NavigationDirection::Forward), { - // move |_, pane, cx| { - // if let Some(workspace) = pane.workspace.upgrade(cx) { - // let pane = cx.weak_handle(); - // cx.window_context().defer(move |cx| { - // workspace.update(cx, |workspace, cx| { - // workspace.go_forward(pane, cx).detach_and_log_err(cx) - // }) - // }) - // } - // } - // }) - // .into_any_named("pane") - // } +// dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { +// self.render_blank_pane(&theme, cx) +// }) +// .on_down(MouseButton::Left, |_, _, cx| { +// cx.focus_parent(); +// }) +// .into_any() +// } +// }) +// .on_down( +// MouseButton::Navigate(NavigationDirection::Back), +// move |_, pane, cx| { +// if let Some(workspace) = pane.workspace.upgrade(cx) { +// let pane = cx.weak_handle(); +// cx.window_context().defer(move |cx| { +// workspace.update(cx, |workspace, cx| { +// workspace.go_back(pane, cx).detach_and_log_err(cx) +// }) +// }) +// } +// }, +// ) +// .on_down(MouseButton::Navigate(NavigationDirection::Forward), { +// move |_, pane, cx| { +// if let Some(workspace) = pane.workspace.upgrade(cx) { +// let pane = cx.weak_handle(); +// cx.window_context().defer(move |cx| { +// workspace.update(cx, |workspace, cx| { +// workspace.go_forward(pane, cx).detach_and_log_err(cx) +// }) +// }) +// } +// } +// }) +// .into_any_named("pane") +// } - // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - // if !self.has_focus { - // self.has_focus = true; - // cx.emit(Event::Focus); - // cx.notify(); - // } +// fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { +// if !self.has_focus { +// self.has_focus = true; +// cx.emit(Event::Focus); +// cx.notify(); +// } - // self.toolbar.update(cx, |toolbar, cx| { - // toolbar.focus_changed(true, cx); - // }); +// 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()); - // } - // } +// 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()); - // } - // } - // } +// cx.focus(active_item.as_any()); +// } else if focused != self.tab_bar_context_menu.handle { +// self.last_focused_view_by_item +// .insert(active_item.id(), focused.downgrade()); +// } +// } +// } - // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - // self.has_focus = false; - // self.toolbar.update(cx, |toolbar, cx| { - // toolbar.focus_changed(false, cx); - // }); - // cx.notify(); - // } +// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { +// self.has_focus = false; +// self.toolbar.update(cx, |toolbar, cx| { +// toolbar.focus_changed(false, cx); +// }); +// cx.notify(); +// } - // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { - // Self::reset_to_default_keymap_context(keymap); - // } - // } +// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { +// Self::reset_to_default_keymap_context(keymap); +// } +// } - // impl ItemNavHistory { - // pub fn push(&mut self, data: Option, cx: &mut WindowContext) { - // self.history.push(data, self.item.clone(), cx); - // } +impl ItemNavHistory { + pub fn push(&mut self, data: Option, cx: &mut WindowContext) { + self.history.push(data, self.item.clone(), cx); + } - // pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { - // self.history.pop(NavigationMode::GoingBack, cx) - // } + pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option { + self.history.pop(NavigationMode::GoingBack, cx) + } - // pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { - // self.history.pop(NavigationMode::GoingForward, cx) - // } - // } + pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option { + self.history.pop(NavigationMode::GoingForward, cx) + } +} - // impl NavHistory { - // pub fn for_each_entry( - // &self, - // cx: &AppContext, - // mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), - // ) { - // let borrowed_history = self.0.borrow(); - // borrowed_history - // .forward_stack - // .iter() - // .chain(borrowed_history.backward_stack.iter()) - // .chain(borrowed_history.closed_stack.iter()) - // .for_each(|entry| { - // if let Some(project_and_abs_path) = - // borrowed_history.paths_by_item.get(&entry.item.id()) - // { - // f(entry, project_and_abs_path.clone()); - // } else if let Some(item) = entry.item.upgrade(cx) { - // if let Some(path) = item.project_path(cx) { - // f(entry, (path, None)); - // } - // } - // }) - // } +impl NavHistory { + pub fn for_each_entry( + &self, + cx: &AppContext, + mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), + ) { + let borrowed_history = self.0.borrow(); + borrowed_history + .forward_stack + .iter() + .chain(borrowed_history.backward_stack.iter()) + .chain(borrowed_history.closed_stack.iter()) + .for_each(|entry| { + if let Some(project_and_abs_path) = + borrowed_history.paths_by_item.get(&entry.item.id()) + { + f(entry, project_and_abs_path.clone()); + } else if let Some(item) = entry.item.upgrade(cx) { + if let Some(path) = item.project_path(cx) { + f(entry, (path, None)); + } + } + }) + } - // pub fn set_mode(&mut self, mode: NavigationMode) { - // self.0.borrow_mut().mode = mode; - // } + pub fn 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 disable(&mut self) { + self.0.borrow_mut().mode = NavigationMode::Disabled; + } - // pub fn enable(&mut self) { - // self.0.borrow_mut().mode = NavigationMode::Normal; - // } + pub fn enable(&mut self) { + self.0.borrow_mut().mode = NavigationMode::Normal; + } - // pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { - // let mut state = self.0.borrow_mut(); - // let entry = match mode { - // NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { - // return None - // } - // NavigationMode::GoingBack => &mut state.backward_stack, - // NavigationMode::GoingForward => &mut state.forward_stack, - // NavigationMode::ReopeningClosedItem => &mut state.closed_stack, - // } - // .pop_back(); - // if entry.is_some() { - // state.did_update(cx); - // } - // entry - // } + pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { + let mut state = self.0.borrow_mut(); + let entry = match mode { + NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { + return None + } + NavigationMode::GoingBack => &mut state.backward_stack, + NavigationMode::GoingForward => &mut state.forward_stack, + NavigationMode::ReopeningClosedItem => &mut state.closed_stack, + } + .pop_back(); + if entry.is_some() { + state.did_update(cx); + } + entry + } - // pub fn push( - // &mut self, - // data: Option, - // item: Rc, - // cx: &mut WindowContext, - // ) { - // let state = &mut *self.0.borrow_mut(); - // match state.mode { - // NavigationMode::Disabled => {} - // NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { - // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - // state.backward_stack.pop_front(); - // } - // state.backward_stack.push_back(NavigationEntry { - // item, - // data: data.map(|data| Box::new(data) as Box), - // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - // }); - // state.forward_stack.clear(); - // } - // NavigationMode::GoingBack => { - // if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - // state.forward_stack.pop_front(); - // } - // state.forward_stack.push_back(NavigationEntry { - // item, - // data: data.map(|data| Box::new(data) as Box), - // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - // }); - // } - // NavigationMode::GoingForward => { - // if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - // state.backward_stack.pop_front(); - // } - // state.backward_stack.push_back(NavigationEntry { - // item, - // data: data.map(|data| Box::new(data) as Box), - // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - // }); - // } - // NavigationMode::ClosingItem => { - // if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { - // state.closed_stack.pop_front(); - // } - // state.closed_stack.push_back(NavigationEntry { - // item, - // data: data.map(|data| Box::new(data) as Box), - // timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), - // }); - // } - // } - // state.did_update(cx); - // } + pub fn push( + &mut self, + data: Option, + item: Rc, + cx: &mut WindowContext, + ) { + let state = &mut *self.0.borrow_mut(); + match state.mode { + NavigationMode::Disabled => {} + NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { + if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.backward_stack.pop_front(); + } + state.backward_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + state.forward_stack.clear(); + } + NavigationMode::GoingBack => { + if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.forward_stack.pop_front(); + } + state.forward_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + } + NavigationMode::GoingForward => { + if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.backward_stack.pop_front(); + } + state.backward_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + } + NavigationMode::ClosingItem => { + if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN { + state.closed_stack.pop_front(); + } + state.closed_stack.push_back(NavigationEntry { + item, + data: data.map(|data| Box::new(data) as Box), + timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), + }); + } + } + state.did_update(cx); + } - // pub fn remove_item(&mut self, item_id: usize) { - // let mut state = self.0.borrow_mut(); - // state.paths_by_item.remove(&item_id); - // state - // .backward_stack - // .retain(|entry| entry.item.id() != item_id); - // state - // .forward_stack - // .retain(|entry| entry.item.id() != item_id); - // state - // .closed_stack - // .retain(|entry| entry.item.id() != item_id); - // } + pub fn remove_item(&mut self, item_id: usize) { + let mut state = self.0.borrow_mut(); + state.paths_by_item.remove(&item_id); + state + .backward_stack + .retain(|entry| entry.item.id() != item_id); + state + .forward_stack + .retain(|entry| entry.item.id() != item_id); + state + .closed_stack + .retain(|entry| entry.item.id() != item_id); + } - // pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { - // self.0.borrow().paths_by_item.get(&item_id).cloned() - // } + pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { + self.0.borrow().paths_by_item.get(&item_id).cloned() + } } // impl NavHistoryState { diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 2e28dabffb..8265848497 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -7,7 +7,7 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{AsyncAppContext, Handle, Task, View, WeakView, WindowBounds}; +use gpui2::{AsyncAppContext, AsyncWindowContext, Handle, Task, View, WeakView, WindowBounds}; use project2::Project; use std::{ path::{Path, PathBuf}, @@ -154,7 +154,7 @@ impl SerializedPaneGroup { project: &Handle, workspace_id: WorkspaceId, workspace: &WeakView, - cx: &mut AsyncAppContext, + cx: &mut AsyncWindowContext, ) -> Option<(Member, Option>, Vec>>)> { match self { SerializedPaneGroup::Group { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3731094b26..d00ef2c26a 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -11,65 +11,30 @@ mod toolbar; mod workspace_settings; use anyhow::{anyhow, Result}; -// use call2::ActiveCall; -// use client2::{ -// proto::{self, PeerId}, -// Client, Status, TypedEnvelope, UserStore, -// }; -// use collections::{hash_map, HashMap, HashSet}; -// use futures::{ -// channel::{mpsc, oneshot}, -// future::try_join_all, -// FutureExt, StreamExt, -// }; -// use gpui2::{ -// actions, -// elements::*, -// geometry::{ -// rect::RectF, -// vector::{vec2f, Vector2F}, -// }, -// impl_actions, -// platform::{ -// CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel, -// WindowBounds, WindowOptions, -// }, -// AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext, -// Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, -// View, WeakViewHandle, WindowContext, WindowHandle, -// }; -// use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; -// use itertools::Itertools; -// use language2::{LanguageRegistry, Rope}; -// use node_runtime::NodeRuntime;// // - -use futures::channel::oneshot; -// use crate::{ -// notifications::{simple_message_notification::MessageNotification, NotificationTracker}, -// persistence::model::{ -// DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, -// }, -// }; -// use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; -// use lazy_static::lazy_static; -// use notifications::{NotificationHandle, NotifyResultExt}; +use client2::{ + proto::{self, PeerId}, + Client, UserStore, +}; +use collections::{HashMap, HashSet}; +use futures::{channel::oneshot, FutureExt}; +use gpui2::{ + AnyModel, AnyView, AppContext, AsyncAppContext, DisplayId, MainThread, Model, Task, View, + ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowHandle, WindowOptions, +}; +use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; +use language2::LanguageRegistry; +use node_runtime::NodeRuntime; pub use pane::*; pub use pane_group::*; -// use persistence::{model::SerializedItem, DB}; -// pub use persistence::{ -// model::{ItemId, WorkspaceLocation}, -// WorkspaceDb, DB as WORKSPACE_DB, -// }; -// use postage::prelude::Stream; -// use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; -// use serde::Deserialize; -// use shared_screen::SharedScreen; -// use status_bar::StatusBar; -// pub use status_bar::StatusItemView; -// use theme::{Theme, ThemeSettings}; +use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; +use std::{ + any::TypeId, + path::{Path, PathBuf}, + sync::{atomic::AtomicUsize, Arc}, + time::Duration, +}; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; -// use util::ResultExt; -// pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings}; +use util::ResultExt; // lazy_static! { // static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") @@ -367,13 +332,12 @@ pub type WorkspaceId = i64; // } type ProjectItemBuilders = - HashMap, AnyHandle, &mut ViewContext) -> Box>; + HashMap, AnyModel, &mut ViewContext) -> Box>; pub fn register_project_item(cx: &mut AppContext) { - cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { - builders.insert(TypeId::of::(), |project, model, cx| { - let item = model.downcast::().unwrap(); - Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) - }); + let builders = cx.default_global::(); + builders.insert(TypeId::of::(), |project, model, cx| { + let item = model.downcast::().unwrap(); + Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx))) }); } @@ -392,26 +356,25 @@ type FollowableItemBuilders = HashMap< ), >; pub fn register_followable_item(cx: &mut AppContext) { - cx.update_default_global(|builders: &mut FollowableItemBuilders, _| { - builders.insert( - TypeId::of::(), - ( - |pane, workspace, id, state, cx| { - I::from_state_proto(pane, workspace, id, state, cx).map(|task| { - cx.foreground() - .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) - }) - }, - |this| Box::new(this.clone().downcast::().unwrap()), - ), - ); - }); + let builders = cx.default_global::(); + builders.insert( + TypeId::of::(), + ( + |pane, workspace, id, state, cx| { + I::from_state_proto(pane, workspace, id, state, cx).map(|task| { + cx.executor() + .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) + }) + }, + |this| Box::new(this.clone().downcast::().unwrap()), + ), + ); } type ItemDeserializers = HashMap< Arc, fn( - Handle, + Model, WeakView, WorkspaceId, ItemId, @@ -436,18 +399,18 @@ pub fn register_deserializable_item(cx: &mut AppContext) { pub struct AppState { pub languages: Arc, pub client: Arc, - pub user_store: Handle, - pub workspace_store: Handle, + pub user_store: Model, + pub workspace_store: Model, pub fs: Arc, pub build_window_options: fn(Option, Option, &MainThread) -> WindowOptions, pub initialize_workspace: - fn(WeakHandle, bool, Arc, AsyncAppContext) -> Task>, + fn(WeakModel, bool, Arc, AsyncAppContext) -> Task>, pub node_runtime: Arc, } pub struct WorkspaceStore { - workspaces: HashSet>, + workspaces: HashSet>, followers: Vec, client: Arc, _subscriptions: Vec, @@ -520,7 +483,7 @@ impl DelayedDebouncedEditAction { let previous_task = self.task.take(); self.task = Some(cx.spawn(|workspace, mut cx| async move { - let mut timer = cx.background().timer(delay).fuse(); + let mut timer = cx.executor().timer(delay).fuse(); if let Some(previous_task) = previous_task { previous_task.await; } @@ -540,13 +503,13 @@ impl DelayedDebouncedEditAction { } } -// pub enum Event { -// PaneAdded(View), -// ContactRequestedJoin(u64), -// } +pub enum Event { + PaneAdded(View), + ContactRequestedJoin(u64), +} pub struct Workspace { - weak_self: WeakHandle, + weak_self: WeakView, // modal: Option, // zoomed: Option, // zoomed_position: Option, @@ -555,16 +518,16 @@ pub struct Workspace { // bottom_dock: View, // right_dock: View, panes: Vec>, - // panes_by_item: HashMap>, + panes_by_item: HashMap>, // active_pane: View, last_active_center_pane: Option>, // last_active_view_id: Option, // status_bar: View, // titlebar_item: Option, // notifications: Vec<(TypeId, usize, Box)>, - project: Handle, + project: Model, // follower_states: HashMap, FollowerState>, - // last_leaders_by_pane: HashMap, PeerId>, + // last_leaders_by_pane: HashMap, PeerId>, // window_edited: bool, // active_call: Option<(ModelHandle, Vec)>, // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, @@ -574,7 +537,7 @@ pub struct Workspace { // _apply_leader_updates: Task>, // _observe_current_user: Task>, // _schedule_serialize: Option>, - // pane_history_timestamp: Arc, + pane_history_timestamp: Arc, } // struct ActiveModal { @@ -582,11 +545,11 @@ pub struct Workspace { // previously_focused_view_id: Option, // } -// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -// pub struct ViewId { -// pub creator: PeerId, -// pub id: u64, -// } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct ViewId { + pub creator: PeerId, + pub id: u64, +} #[derive(Default)] struct FollowerState { @@ -595,7 +558,7 @@ struct FollowerState { items_by_leader_view_id: HashMap>, } -// enum WorkspaceBounds {} +enum WorkspaceBounds {} impl Workspace { // pub fn new( @@ -799,7 +762,7 @@ impl Workspace { // requesting_window: Option>, // cx: &mut AppContext, // ) -> Task<( - // WeakViewHandle, + // WeakView, // Vec, anyhow::Error>>>, // )> { // let project_handle = Project::local( @@ -927,21 +890,21 @@ impl Workspace { // }) // } - // pub fn weak_handle(&self) -> WeakViewHandle { - // self.weak_self.clone() - // } + pub fn weak_handle(&self) -> WeakView { + self.weak_self.clone() + } - // pub fn left_dock(&self) -> &View { - // &self.left_dock - // } + // pub fn left_dock(&self) -> &View { + // &self.left_dock + // } - // pub fn bottom_dock(&self) -> &View { - // &self.bottom_dock - // } + // pub fn bottom_dock(&self) -> &View { + // &self.bottom_dock + // } - // pub fn right_dock(&self) -> &View { - // &self.right_dock - // } + // pub fn right_dock(&self) -> &View { + // &self.right_dock + // } // pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) // where @@ -1038,15 +1001,15 @@ impl Workspace { // &self.status_bar // } - // pub fn app_state(&self) -> &Arc { - // &self.app_state - // } + pub fn app_state(&self) -> &Arc { + &self.app_state + } - // pub fn user_store(&self) -> &ModelHandle { - // &self.app_state.user_store - // } + pub fn user_store(&self) -> &Model { + &self.app_state.user_store + } - pub fn project(&self) -> &Handle { + pub fn project(&self) -> &Model { &self.project } @@ -1108,7 +1071,7 @@ impl Workspace { // fn navigate_history( // &mut self, - // pane: WeakViewHandle, + // pane: WeakView, // mode: NavigationMode, // cx: &mut ViewContext, // ) -> Task> { @@ -1193,7 +1156,7 @@ impl Workspace { // pub fn go_back( // &mut self, - // pane: WeakViewHandle, + // pane: WeakView, // cx: &mut ViewContext, // ) -> Task> { // self.navigate_history(pane, NavigationMode::GoingBack, cx) @@ -1201,7 +1164,7 @@ impl Workspace { // pub fn go_forward( // &mut self, - // pane: WeakViewHandle, + // pane: WeakView, // cx: &mut ViewContext, // ) -> Task> { // self.navigate_history(pane, NavigationMode::GoingForward, cx) @@ -1592,11 +1555,11 @@ impl Workspace { // } fn project_path_for_path( - project: Handle, + project: Model, abs_path: &Path, visible: bool, cx: &mut AppContext, - ) -> Task, ProjectPath)>> { + ) -> Task, ProjectPath)>> { let entry = project.update(cx, |project, cx| { project.find_or_create_local_worktree(abs_path, visible, cx) }); @@ -1957,21 +1920,21 @@ impl Workspace { // cx.notify(); // } - // fn add_pane(&mut self, cx: &mut ViewContext) -> View { - // 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 - // } + fn add_pane(&mut self, cx: &mut ViewContext) -> View { + let pane = cx.build_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, @@ -2397,9 +2360,9 @@ impl Workspace { // pub fn split_pane_with_item( // &mut self, - // pane_to_split: WeakViewHandle, + // pane_to_split: WeakView, // split_direction: SplitDirection, - // from: WeakViewHandle, + // from: WeakView, // item_id_to_move: usize, // cx: &mut ViewContext, // ) { @@ -2420,7 +2383,7 @@ impl Workspace { // pub fn split_pane_with_project_entry( // &mut self, - // pane_to_split: WeakViewHandle, + // pane_to_split: WeakView, // split_direction: SplitDirection, // project_entry: ProjectEntryId, // cx: &mut ViewContext, @@ -2899,7 +2862,7 @@ impl Workspace { // } // async fn process_leader_update( - // this: &WeakViewHandle, + // this: &WeakView, // leader_id: PeerId, // update: proto::UpdateFollowers, // cx: &mut AsyncAppContext, @@ -2958,7 +2921,7 @@ impl Workspace { // } // async fn add_views_from_leader( - // this: WeakViewHandle, + // this: WeakView, // leader_id: PeerId, // panes: Vec>, // views: Vec, @@ -3045,25 +3008,25 @@ impl Workspace { // } // } - // 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) - // }) - // } + 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: &View) -> Option { - // self.follower_states.get(pane).map(|state| state.leader_id) - // } + pub fn leader_for_pane(&self, pane: &View) -> Option { + self.follower_states.get(pane).map(|state| state.leader_id) + } // fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { // cx.notify(); @@ -3380,7 +3343,7 @@ impl Workspace { // } // pub(crate) fn load_workspace( - // workspace: WeakViewHandle, + // workspace: WeakView, // serialized_workspace: SerializedWorkspace, // paths_to_open: Vec>, // cx: &mut AppContext, @@ -3570,7 +3533,7 @@ impl Workspace { // async fn open_items( // serialized_workspace: Option, - // workspace: &WeakViewHandle, + // workspace: &WeakView, // mut project_paths_to_open: Vec<(PathBuf, Option)>, // app_state: Arc, // mut cx: AsyncAppContext, @@ -3660,7 +3623,7 @@ impl Workspace { // Ok(opened_items) // } - // fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { + // fn notify_of_new_dock(workspace: &WeakView, 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; @@ -3741,7 +3704,7 @@ impl Workspace { // .ok(); } -// fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { +// fn notify_if_database_failed(workspace: &WeakView, 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"; // workspace @@ -4054,23 +4017,23 @@ impl Workspace { // type Event = (); // } -// impl ViewId { -// pub(crate) fn from_proto(message: proto::ViewId) -> Result { -// Ok(Self { -// creator: message -// .creator -// .ok_or_else(|| anyhow!("creator is missing"))?, -// id: message.id, -// }) -// } +impl ViewId { + pub(crate) fn from_proto(message: proto::ViewId) -> Result { + Ok(Self { + creator: message + .creator + .ok_or_else(|| anyhow!("creator is missing"))?, + id: message.id, + }) + } -// pub(crate) fn to_proto(&self) -> proto::ViewId { -// proto::ViewId { -// creator: Some(self.creator), -// id: self.id, -// } -// } -// } + pub(crate) fn to_proto(&self) -> proto::ViewId { + proto::ViewId { + creator: Some(self.creator), + id: self.id, + } + } +} // pub trait WorkspaceHandle { // fn file_project_paths(&self, cx: &AppContext) -> Vec; @@ -4099,7 +4062,7 @@ impl Workspace { // } // } -// pub struct WorkspaceCreated(pub WeakViewHandle); +// pub struct WorkspaceCreated(pub WeakView); pub async fn activate_workspace_for_project( cx: &mut AsyncAppContext, @@ -4321,27 +4284,6 @@ pub async fn activate_workspace_for_project( // None // } -use client2::{ - proto::{self, PeerId, ViewId}, - Client, UserStore, -}; -use collections::{HashMap, HashSet}; -use gpui2::{ - AnyHandle, AnyView, AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, View, - ViewContext, WeakHandle, WeakView, WindowBounds, WindowHandle, WindowOptions, -}; -use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; -use language2::LanguageRegistry; -use node_runtime::NodeRuntime; -use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; -use std::{ - any::TypeId, - path::{Path, PathBuf}, - sync::Arc, - time::Duration, -}; -use util::ResultExt; - #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], From 163fa3ff16cb95887ed6401c699f54a60e492105 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 Oct 2023 13:17:05 +0100 Subject: [PATCH 10/66] Introduce {Window,View}Context::defer --- crates/gpui2/src/window.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5d47fffb6e..e8c45f0191 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -376,6 +376,15 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.notify(); } + /// Schedules the given function to be run at the end of the current effect cycle, allowing entities + /// that are currently on the stack to be returned to the app. + pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static + Send) { + let window = self.window.handle; + self.app.defer(move |cx| { + cx.update_window(window, f).ok(); + }); + } + pub fn subscribe( &mut self, entity: &E, @@ -1595,6 +1604,15 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { self.window_cx.on_next_frame(move |cx| view.update(cx, f)); } + /// Schedules the given function to be run at the end of the current effect cycle, allowing entities + /// that are currently on the stack to be returned to the app. + pub fn defer(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static + Send) { + let view = self.view(); + self.window_cx.defer(move |cx| { + view.update(cx, f).ok(); + }); + } + pub fn observe( &mut self, entity: &E, From 46a99c5c41c29ae428356f2115bedc8ff6b08ae9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 Oct 2023 13:34:29 +0100 Subject: [PATCH 11/66] Allow View to be hashed and compared --- crates/gpui2/src/view.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 8bef25b92d..dfc294bc05 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -4,7 +4,12 @@ use crate::{ Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; -use std::{any::TypeId, marker::PhantomData, sync::Arc}; +use std::{ + any::TypeId, + hash::{Hash, Hasher}, + marker::PhantomData, + sync::Arc, +}; pub trait Render: 'static + Sized { type Element: Element + 'static + Send; @@ -76,6 +81,20 @@ impl Clone for View { } } +impl Hash for View { + fn hash(&self, state: &mut H) { + self.model.hash(state); + } +} + +impl PartialEq for View { + fn eq(&self, other: &Self) -> bool { + self.model == other.model + } +} + +impl Eq for View {} + impl Component for View { fn render(self) -> AnyElement { AnyElement::new(EraseViewState { From e8eea52d0f8c91f5e74c50181fd348f50bc13f62 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 Oct 2023 13:34:54 +0100 Subject: [PATCH 12/66] Allow WeakView to be hashed and compared --- crates/gpui2/src/view.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index dfc294bc05..b54afa8dea 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -178,6 +178,20 @@ impl Clone for WeakView { } } +impl Hash for WeakView { + fn hash(&self, state: &mut H) { + self.model.hash(state); + } +} + +impl PartialEq for WeakView { + fn eq(&self, other: &Self) -> bool { + self.model == other.model + } +} + +impl Eq for WeakView {} + struct EraseViewState { view: View, parent_view_state_type: PhantomData, From 14a6199b4b97d1cac01d34f9751ef6d1e4dbffd4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 Oct 2023 13:51:42 +0100 Subject: [PATCH 13/66] WIP: Make the item module compile again --- crates/gpui2/src/app.rs | 16 +- crates/gpui2/src/view.rs | 2 +- crates/workspace2/src/item.rs | 307 +++++++++++-------------- crates/workspace2/src/pane.rs | 344 ++++++++++++++-------------- crates/workspace2/src/toolbar.rs | 21 +- crates/workspace2/src/workspace2.rs | 309 +++++++++++++------------ 6 files changed, 478 insertions(+), 521 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 9442f5f1bb..60c1c12bed 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -14,7 +14,7 @@ pub use test_context::*; use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, - AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Executor, + AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, @@ -694,13 +694,17 @@ impl AppContext { self.globals_by_type.insert(global_type, lease.global); } - pub fn observe_release( + pub fn observe_release( &mut self, - handle: &Model, - mut on_release: impl FnMut(&mut E, &mut AppContext) + Send + 'static, - ) -> Subscription { + handle: &E, + mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static, + ) -> Subscription + where + E: Entity, + T: 'static, + { self.release_listeners.insert( - handle.entity_id, + handle.entity_id(), Box::new(move |entity, cx| { let entity = entity.downcast_mut().expect("invalid entity type"); on_release(entity, cx) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index b54afa8dea..246ef33ee7 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -314,7 +314,7 @@ impl AnyView { .map_err(|_| self) } - pub(crate) fn entity_type(&self) -> TypeId { + pub fn entity_type(&self) -> TypeId { self.0.entity_type() } diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 5995487f07..554a7aadb6 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -1,88 +1,80 @@ -// use crate::{ -// pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders, -// ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, -// }; -// use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; +use crate::{ + pane::{self, Pane}, + persistence::model::ItemId, + searchable::SearchableItemHandle, + workspace_settings::{AutosaveSetting, WorkspaceSettings}, + DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation, + ViewId, Workspace, WorkspaceId, +}; use anyhow::Result; use client2::{ proto::{self, PeerId}, Client, }; +use gpui2::{ + AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render, + SharedString, Task, View, ViewContext, WeakView, WindowContext, +}; +use parking_lot::Mutex; +use project2::{Project, ProjectEntryId, ProjectPath}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use settings2::Settings; +use smallvec::SmallVec; +use std::{ + any::{Any, TypeId}, + ops::Range, + path::PathBuf, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; use theme2::Theme; -// use client2::{ -// proto::{self, PeerId}, -// Client, -// }; -// use gpui2::geometry::vector::Vector2F; -// use gpui2::AnyWindowHandle; -// use gpui2::{ -// fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Model, Task, View, -// ViewContext, View, 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(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, -// } +#[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, -// } -// } -// } +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, -// } +#[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"); +impl Settings for ItemSettings { + const KEY: Option<&'static str> = Some("tabs"); -// type FileContent = ItemSettingsContent; + 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) -// } -// } + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &mut AppContext, + ) -> Result { + Self::load_via_json_merge(default_value, user_values) + } +} #[derive(Eq, PartialEq, Hash, Debug)] pub enum ItemEvent { @@ -165,18 +157,18 @@ pub trait Item: Render + EventEmitter + Send { false } - // fn act_as_type<'a>( - // &'a self, - // type_id: TypeId, - // self_handle: &'a View, - // _: &'a AppContext, - // ) -> Option<&AnyViewHandle> { - // if TypeId::of::() == type_id { - // Some(self_handle) - // } else { - // None - // } - // } + fn act_as_type<'a>( + &'a self, + type_id: TypeId, + self_handle: &'a View, + _: &'a AppContext, + ) -> Option { + if TypeId::of::() == type_id { + Some(self_handle.clone().into_any()) + } else { + None + } + } fn as_searchable(&self, _: &View) -> Option> { None @@ -215,35 +207,6 @@ pub trait Item: Render + EventEmitter + Send { } } -use std::{ - any::Any, - cell::RefCell, - ops::Range, - path::PathBuf, - rc::Rc, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Duration, -}; - -use gpui2::{ - AnyElement, AnyView, AnyWindowHandle, AppContext, EventEmitter, HighlightStyle, Model, Pixels, - Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, -}; -use project2::{Project, ProjectEntryId, ProjectPath}; -use smallvec::SmallVec; - -use crate::{ - pane::{self, Pane}, - persistence::model::ItemId, - searchable::SearchableItemHandle, - workspace_settings::{AutosaveSetting, WorkspaceSettings}, - DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation, - ViewId, Workspace, WorkspaceId, -}; - pub trait ItemHandle: 'static + Send { fn subscribe_to_item_events( &self, @@ -275,7 +238,6 @@ pub trait ItemHandle: 'static + Send { 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 to_any(&self) -> AnyView; fn is_dirty(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool; @@ -288,12 +250,12 @@ pub trait ItemHandle: 'static + Send { cx: &mut WindowContext, ) -> Task>; fn reload(&self, project: Model, cx: &mut WindowContext) -> Task>; - // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!() + fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; fn on_release( &self, cx: &mut AppContext, - callback: Box, + callback: Box, ) -> gpui2::Subscription; fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; @@ -303,23 +265,21 @@ pub trait ItemHandle: 'static + Send { fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option>; } -pub trait WeakItemHandle: Send { +pub trait WeakItemHandle: Send + Sync { fn id(&self) -> usize; - fn window(&self) -> AnyWindowHandle; fn upgrade(&self) -> Option>; } -// todo!() -// impl dyn ItemHandle { -// pub fn downcast(&self) -> Option> { -// self.as_any().clone().downcast() -// } +impl dyn ItemHandle { + pub fn downcast(&self) -> Option> { + self.to_any().downcast().ok() + } -// pub fn act_as(&self, cx: &AppContext) -> Option> { -// self.act_as_type(TypeId::of::(), cx) -// .and_then(|t| t.clone().downcast()) -// } -// } + pub fn act_as(&self, cx: &AppContext) -> Option> { + self.act_as_type(TypeId::of::(), cx) + .and_then(|t| t.downcast().ok()) + } +} impl ItemHandle for View { fn subscribe_to_item_events( @@ -438,8 +398,8 @@ impl ItemHandle for View { .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 pending_update = Arc::new(Mutex::new(None)); + let pending_update_scheduled = Arc::new(AtomicBool::new(false)); let mut event_subscription = Some(cx.subscribe(self, move |workspace, item, event, cx| { @@ -462,33 +422,31 @@ impl ItemHandle for View { workspace.unfollow(&pane, cx); } - if item.add_event_to_update_proto( - event, - &mut *pending_update.borrow_mut(), - cx, - ) && !pending_update_scheduled.load(Ordering::SeqCst) + if item.add_event_to_update_proto(event, &mut *pending_update.lock(), 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, - ); - } - }); + todo!("replace with on_next_frame?"); + // 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, + // ); + // } + // }); } } @@ -525,15 +483,16 @@ impl ItemHandle for View { } })); - cx.observe_focus(self, move |workspace, item, focused, cx| { - if !focused - && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange - { - Pane::autosave_item(&item, workspace.project.clone(), cx) - .detach_and_log_err(cx); - } - }) - .detach(); + todo!("observe focus"); + // cx.observe_focus(self, move |workspace, item, focused, cx| { + // if !focused + // && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange + // { + // Pane::autosave_item(&item, workspace.project.clone(), cx) + // .detach_and_log_err(cx); + // } + // }) + // .detach(); let item_id = self.id(); cx.observe_release(self, move |workspace, _, _| { @@ -564,11 +523,6 @@ impl ItemHandle for View { self.id() } - fn window(&self) -> AnyWindowHandle { - todo!() - // AnyViewHandle::window(self) - } - fn to_any(&self) -> AnyView { self.clone().into_any() } @@ -602,16 +556,15 @@ impl ItemHandle for View { self.update(cx, |item, cx| item.reload(project, cx)) } - // todo!() - // 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 act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option { + 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)) + let item = self.to_any(); + Some(builders.get(&item.entity_type())?.1(&item)) } else { None } @@ -620,7 +573,7 @@ impl ItemHandle for View { fn on_release( &self, cx: &mut AppContext, - callback: Box, + callback: Box, ) -> gpui2::Subscription { cx.observe_release(self, move |_, cx| callback(cx)) } @@ -673,10 +626,6 @@ impl WeakItemHandle for WeakView { self.id() } - fn window(&self) -> AnyWindowHandle { - self.window() - } - fn upgrade(&self) -> Option> { self.upgrade().map(|v| Box::new(v) as Box) } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index ca42b0ef2e..22aa61e6cd 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -2,10 +2,15 @@ use crate::{ item::{Item, ItemHandle, WeakItemHandle}, + toolbar::Toolbar, SplitDirection, Workspace, }; +use anyhow::Result; use collections::{HashMap, VecDeque}; -use gpui2::{EventEmitter, Model, View, ViewContext, WeakView}; +use gpui2::{ + AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, +}; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -68,6 +73,7 @@ pub enum SaveIntent { // pub save_intent: Option, // } +// todo!() // actions!( // pane, // [ @@ -90,8 +96,9 @@ pub enum SaveIntent { // impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]); -// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; +const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; +// todo!() // pub fn init(cx: &mut AppContext) { // cx.add_action(Pane::toggle_zoom); // cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { @@ -330,7 +337,7 @@ impl Pane { pane: handle.clone(), next_timestamp, }))), - // toolbar: cx.add_view(|_| Toolbar::new()), + toolbar: cx.build_view(|_| Toolbar::new()), // tab_bar_context_menu: TabBarContextMenu { // kind: TabBarContextMenuKind::New, // handle: context_menu, @@ -447,33 +454,33 @@ impl Pane { } } - // pub fn nav_history(&self) -> &NavHistory { - // &self.nav_history - // } + pub fn nav_history(&self) -> &NavHistory { + &self.nav_history + } - // pub fn nav_history_mut(&mut self) -> &mut NavHistory { - // &mut 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 disable_history(&mut self) { + self.nav_history.disable(); + } - // pub fn enable_history(&mut self) { - // self.nav_history.enable(); - // } + 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_backward(&self) -> bool { + !self.nav_history.0.lock().backward_stack.is_empty() + } - // pub fn can_navigate_forward(&self) -> bool { - // !self.nav_history.0.borrow().forward_stack.is_empty() - // } + pub fn can_navigate_forward(&self) -> bool { + !self.nav_history.0.lock().forward_stack.is_empty() + } - // fn history_updated(&mut self, cx: &mut ViewContext) { - // self.toolbar.update(cx, |_, cx| cx.notify()); - // } + fn history_updated(&mut self, cx: &mut ViewContext) { + self.toolbar.update(cx, |_, cx| cx.notify()); + } pub(crate) fn open_item( &mut self, @@ -736,115 +743,115 @@ impl Pane { // )) // } - // pub fn close_item_by_id( - // &mut self, - // item_id_to_close: usize, - // save_intent: SaveIntent, - // cx: &mut ViewContext, - // ) -> Task> { - // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + pub fn close_item_by_id( + &mut self, + item_id_to_close: usize, + save_intent: SaveIntent, + cx: &mut ViewContext, + ) -> Task> { + self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + } + + // pub fn close_inactive_items( + // &mut self, + // _: &CloseInactiveItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; // } - // pub fn close_inactive_items( - // &mut self, - // _: &CloseInactiveItems, - // cx: &mut ViewContext, - // ) -> Option>> { - // if self.items.is_empty() { - // return None; - // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_id != active_item_id + // })) + // } - // let active_item_id = self.items[self.active_item_index].id(); - // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { - // item_id != active_item_id - // })) + // pub fn close_clean_items( + // &mut self, + // _: &CloseCleanItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // let item_ids: Vec<_> = self + // .items() + // .filter(|item| !item.is_dirty(cx)) + // .map(|item| item.id()) + // .collect(); + // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // })) + // } + + // pub fn close_items_to_the_left( + // &mut self, + // _: &CloseItemsToTheLeft, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items_to_the_left_by_id(active_item_id, cx)) + // } + + // pub fn close_items_to_the_left_by_id( + // &mut self, + // item_id: usize, + // cx: &mut ViewContext, + // ) -> Task> { + // let item_ids: Vec<_> = self + // .items() + // .take_while(|item| item.id() != item_id) + // .map(|item| item.id()) + // .collect(); + // self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // }) + // } + + // pub fn close_items_to_the_right( + // &mut self, + // _: &CloseItemsToTheRight, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; + // } + // let active_item_id = self.items[self.active_item_index].id(); + // Some(self.close_items_to_the_right_by_id(active_item_id, cx)) + // } + + // pub fn close_items_to_the_right_by_id( + // &mut self, + // item_id: usize, + // cx: &mut ViewContext, + // ) -> Task> { + // let item_ids: Vec<_> = self + // .items() + // .rev() + // .take_while(|item| item.id() != item_id) + // .map(|item| item.id()) + // .collect(); + // self.close_items(cx, SaveIntent::Close, move |item_id| { + // item_ids.contains(&item_id) + // }) + // } + + // pub fn close_all_items( + // &mut self, + // action: &CloseAllItems, + // cx: &mut ViewContext, + // ) -> Option>> { + // if self.items.is_empty() { + // return None; // } - // pub fn close_clean_items( - // &mut self, - // _: &CloseCleanItems, - // cx: &mut ViewContext, - // ) -> Option>> { - // let item_ids: Vec<_> = self - // .items() - // .filter(|item| !item.is_dirty(cx)) - // .map(|item| item.id()) - // .collect(); - // Some(self.close_items(cx, SaveIntent::Close, move |item_id| { - // item_ids.contains(&item_id) - // })) - // } - - // pub fn close_items_to_the_left( - // &mut self, - // _: &CloseItemsToTheLeft, - // cx: &mut ViewContext, - // ) -> Option>> { - // if self.items.is_empty() { - // return None; - // } - // let active_item_id = self.items[self.active_item_index].id(); - // Some(self.close_items_to_the_left_by_id(active_item_id, cx)) - // } - - // pub fn close_items_to_the_left_by_id( - // &mut self, - // item_id: usize, - // cx: &mut ViewContext, - // ) -> Task> { - // let item_ids: Vec<_> = self - // .items() - // .take_while(|item| item.id() != item_id) - // .map(|item| item.id()) - // .collect(); - // self.close_items(cx, SaveIntent::Close, move |item_id| { - // item_ids.contains(&item_id) - // }) - // } - - // pub fn close_items_to_the_right( - // &mut self, - // _: &CloseItemsToTheRight, - // cx: &mut ViewContext, - // ) -> Option>> { - // if self.items.is_empty() { - // return None; - // } - // let active_item_id = self.items[self.active_item_index].id(); - // Some(self.close_items_to_the_right_by_id(active_item_id, cx)) - // } - - // pub fn close_items_to_the_right_by_id( - // &mut self, - // item_id: usize, - // cx: &mut ViewContext, - // ) -> Task> { - // let item_ids: Vec<_> = self - // .items() - // .rev() - // .take_while(|item| item.id() != item_id) - // .map(|item| item.id()) - // .collect(); - // self.close_items(cx, SaveIntent::Close, move |item_id| { - // item_ids.contains(&item_id) - // }) - // } - - // pub fn close_all_items( - // &mut self, - // action: &CloseAllItems, - // cx: &mut ViewContext, - // ) -> Option>> { - // if self.items.is_empty() { - // return None; - // } - - // Some( - // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { - // true - // }), - // ) - // } + // Some( + // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| { + // true + // }), + // ) + // } // pub(super) fn file_names_for_prompt( // items: &mut dyn Iterator>, @@ -1156,28 +1163,29 @@ impl Pane { // 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 - // } + 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: Model, - // cx: &mut WindowContext, - // ) -> Task> { - // if Self::can_autosave_item(item, cx) { - // item.save(project, cx) - // } else { - // Task::ready(Ok(())) - // } - // } + pub fn autosave_item( + item: &dyn ItemHandle, + project: Model, + cx: &mut WindowContext, + ) -> Task> { + if Self::can_autosave_item(item, cx) { + item.save(project, cx) + } else { + Task::ready(Ok(())) + } + } - // pub fn focus_active_item(&mut self, cx: &mut ViewContext) { - // if let Some(active_item) = self.active_item() { - // cx.focus(active_item.as_any()); - // } - // } + pub fn focus_active_item(&mut self, cx: &mut ViewContext) { + todo!(); + // if let Some(active_item) = self.active_item() { + // cx.focus(active_item.as_any()); + // } + } // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { // cx.emit(Event::Split(direction)); @@ -1979,7 +1987,7 @@ impl NavHistory { cx: &AppContext, mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option)), ) { - let borrowed_history = self.0.borrow(); + let borrowed_history = self.0.lock(); borrowed_history .forward_stack .iter() @@ -1990,7 +1998,7 @@ impl NavHistory { 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) { + } else if let Some(item) = entry.item.upgrade() { if let Some(path) = item.project_path(cx) { f(entry, (path, None)); } @@ -1999,23 +2007,23 @@ impl NavHistory { } pub fn set_mode(&mut self, mode: NavigationMode) { - self.0.borrow_mut().mode = mode; + self.0.lock().mode = mode; } pub fn mode(&self) -> NavigationMode { - self.0.borrow().mode + self.0.lock().mode } pub fn disable(&mut self) { - self.0.borrow_mut().mode = NavigationMode::Disabled; + self.0.lock().mode = NavigationMode::Disabled; } pub fn enable(&mut self) { - self.0.borrow_mut().mode = NavigationMode::Normal; + self.0.lock().mode = NavigationMode::Normal; } pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option { - let mut state = self.0.borrow_mut(); + let mut state = self.0.lock(); let entry = match mode { NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { return None @@ -2034,10 +2042,10 @@ impl NavHistory { pub fn push( &mut self, data: Option, - item: Rc, + item: Arc, cx: &mut WindowContext, ) { - let state = &mut *self.0.borrow_mut(); + let state = &mut *self.0.lock(); match state.mode { NavigationMode::Disabled => {} NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { @@ -2086,7 +2094,7 @@ impl NavHistory { } pub fn remove_item(&mut self, item_id: usize) { - let mut state = self.0.borrow_mut(); + let mut state = self.0.lock(); state.paths_by_item.remove(&item_id); state .backward_stack @@ -2100,19 +2108,19 @@ impl NavHistory { } pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { - self.0.borrow().paths_by_item.get(&item_id).cloned() + self.0.lock().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)); -// }); -// } -// } -// } +impl NavHistoryState { + pub fn did_update(&self, cx: &mut WindowContext) { + if let Some(pane) = self.pane.upgrade() { + cx.defer(move |cx| { + pane.update(cx, |pane, cx| pane.history_updated(cx)); + }); + } + } +} // pub struct PaneBackdrop { // child_view: usize, diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index 4357c6a49d..49e3bd1d98 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -1,7 +1,7 @@ use crate::ItemHandle; -use gpui2::{AppContext, EventEmitter, View, ViewContext, WindowContext}; +use gpui2::{AnyView, AppContext, EventEmitter, Render, View, ViewContext, WindowContext}; -pub trait ToolbarItemView: EventEmitter + Sized { +pub trait ToolbarItemView: Render + EventEmitter { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, @@ -22,14 +22,14 @@ pub trait ToolbarItemView: EventEmitter + Sized { /// Number of times toolbar's height will be repeated to get the effective height. /// Useful when multiple rows one under each other are needed. /// The rows have the same width and act as a whole when reacting to resizes and similar events. - fn row_count(&self, _cx: &ViewContext) -> usize { + fn row_count(&self, _cx: &WindowContext) -> usize { 1 } } -trait ToolbarItemViewHandle { +trait ToolbarItemViewHandle: Send { fn id(&self) -> usize; - // fn as_any(&self) -> &AnyViewHandle; todo!() + fn to_any(&self) -> AnyView; fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, @@ -249,7 +249,7 @@ impl Toolbar { pub fn item_of_type(&self) -> Option> { self.items .iter() - .find_map(|(item, _)| item.as_any().clone().downcast()) + .find_map(|(item, _)| item.to_any().downcast().ok()) } pub fn hidden(&self) -> bool { @@ -262,10 +262,9 @@ impl ToolbarItemViewHandle for View { self.id() } - // todo!() - // fn as_any(&self) -> &AnyViewHandle { - // self - // } + fn to_any(&self) -> AnyView { + self.clone().into_any() + } fn set_active_pane_item( &self, @@ -285,7 +284,7 @@ impl ToolbarItemViewHandle for View { } fn row_count(&self, cx: &WindowContext) -> usize { - self.read_with(cx, |this, cx| this.row_count(cx)) + self.read(cx).row_count(cx) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index d00ef2c26a..723a922bc6 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -36,6 +36,10 @@ use std::{ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; +use crate::persistence::model::{ + DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, +}; + // lazy_static! { // static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") // .ok() @@ -514,9 +518,9 @@ pub struct Workspace { // zoomed: Option, // zoomed_position: Option, // center: PaneGroup, - // left_dock: View, - // bottom_dock: View, - // right_dock: View, + left_dock: View, + bottom_dock: View, + right_dock: View, panes: Vec>, panes_by_item: HashMap>, // active_pane: View, @@ -526,8 +530,8 @@ pub struct Workspace { // titlebar_item: Option, // notifications: Vec<(TypeId, usize, Box)>, project: Model, - // follower_states: HashMap, FollowerState>, - // last_leaders_by_pane: HashMap, PeerId>, + 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)>, @@ -2613,37 +2617,33 @@ impl Workspace { // self.start_following(leader_id, cx) // } - // pub fn unfollow( - // &mut self, - // pane: &View, - // cx: &mut ViewContext, - // ) -> Option { - // let state = self.follower_states.remove(pane)?; - // let leader_id = state.leader_id; - // for (_, item) in state.items_by_leader_view_id { - // item.set_leader_peer_id(None, cx); - // } + pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { + let state = self.follower_states.remove(pane)?; + let leader_id = state.leader_id; + for (_, item) in state.items_by_leader_view_id { + item.set_leader_peer_id(None, cx); + } - // if self - // .follower_states - // .values() - // .all(|state| state.leader_id != state.leader_id) - // { - // let project_id = self.project.read(cx).remote_id(); - // let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); - // self.app_state - // .client - // .send(proto::Unfollow { - // room_id, - // project_id, - // leader_id: Some(leader_id), - // }) - // .log_err(); - // } + 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) - // } + cx.notify(); + Some(leader_id) + } // pub fn is_being_followed(&self, peer_id: PeerId) -> bool { // self.follower_states @@ -3210,137 +3210,134 @@ impl Workspace { // })); // } - // fn serialize_workspace(&self, cx: &ViewContext) { - // fn serialize_pane_handle( - // pane_handle: &View, - // cx: &AppContext, - // ) -> SerializedPane { - // let (items, active) = { - // let pane = pane_handle.read(cx); - // let active_item_id = pane.active_item().map(|item| item.id()); - // ( - // pane.items() - // .filter_map(|item_handle| { - // Some(SerializedItem { - // kind: Arc::from(item_handle.serialized_item_kind()?), - // item_id: item_handle.id(), - // active: Some(item_handle.id()) == active_item_id, - // }) - // }) - // .collect::>(), - // pane.has_focus(), - // ) - // }; + fn serialize_workspace(&self, cx: &ViewContext) { + fn serialize_pane_handle(pane_handle: &View, cx: &AppContext) -> SerializedPane { + let (items, active) = { + let pane = pane_handle.read(cx); + let active_item_id = pane.active_item().map(|item| item.id()); + ( + pane.items() + .filter_map(|item_handle| { + Some(SerializedItem { + kind: Arc::from(item_handle.serialized_item_kind()?), + item_id: item_handle.id(), + active: Some(item_handle.id()) == active_item_id, + }) + }) + .collect::>(), + pane.has_focus(), + ) + }; - // SerializedPane::new(items, active) - // } + SerializedPane::new(items, active) + } - // fn build_serialized_pane_group( - // pane_group: &Member, - // cx: &AppContext, - // ) -> SerializedPaneGroup { - // match pane_group { - // Member::Axis(PaneAxis { - // axis, - // members, - // flexes, - // bounding_boxes: _, - // }) => SerializedPaneGroup::Group { - // axis: *axis, - // children: members - // .iter() - // .map(|member| build_serialized_pane_group(member, cx)) - // .collect::>(), - // flexes: Some(flexes.borrow().clone()), - // }, - // Member::Pane(pane_handle) => { - // SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) - // } - // } - // } + fn build_serialized_pane_group( + pane_group: &Member, + cx: &AppContext, + ) -> SerializedPaneGroup { + match pane_group { + Member::Axis(PaneAxis { + axis, + members, + flexes, + bounding_boxes: _, + }) => SerializedPaneGroup::Group { + axis: *axis, + children: members + .iter() + .map(|member| build_serialized_pane_group(member, cx)) + .collect::>(), + flexes: Some(flexes.borrow().clone()), + }, + Member::Pane(pane_handle) => { + SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) + } + } + } - // fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { - // let left_dock = this.left_dock.read(cx); - // let left_visible = left_dock.is_open(); - // let left_active_panel = left_dock.visible_panel().and_then(|panel| { - // Some( - // cx.view_ui_name(panel.as_any().window(), panel.id())? - // .to_string(), - // ) - // }); - // let left_dock_zoom = left_dock - // .visible_panel() - // .map(|panel| panel.is_zoomed(cx)) - // .unwrap_or(false); + fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { + let left_dock = this.left_dock.read(cx); + let left_visible = left_dock.is_open(); + let left_active_panel = left_dock.visible_panel().and_then(|panel| { + Some( + cx.view_ui_name(panel.as_any().window(), panel.id())? + .to_string(), + ) + }); + let left_dock_zoom = left_dock + .visible_panel() + .map(|panel| panel.is_zoomed(cx)) + .unwrap_or(false); - // let right_dock = this.right_dock.read(cx); - // let right_visible = right_dock.is_open(); - // let right_active_panel = right_dock.visible_panel().and_then(|panel| { - // Some( - // cx.view_ui_name(panel.as_any().window(), panel.id())? - // .to_string(), - // ) - // }); - // let right_dock_zoom = right_dock - // .visible_panel() - // .map(|panel| panel.is_zoomed(cx)) - // .unwrap_or(false); + let 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); + 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, - // }, - // } - // } + 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); + 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, - // }; + 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(); - // } - // } - // } + cx.background() + .spawn(persistence::DB.save_workspace(serialized_workspace)) + .detach(); + } + } + } // pub(crate) fn load_workspace( // workspace: WeakView, From bbe2dd1f8faa4c2c0c221c615600830c2791ac79 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 Oct 2023 14:04:59 +0100 Subject: [PATCH 14/66] WIP --- crates/workspace2/src/dock.rs | 524 +++++++++++++--------------- crates/workspace2/src/status_bar.rs | 95 +++-- crates/workspace2/src/workspace2.rs | 6 +- 3 files changed, 293 insertions(+), 332 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 5445f89050..5165c970af 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,14 +1,13 @@ -use crate::{StatusItemView, Workspace, WorkspaceBounds}; +use crate::{Axis, Workspace}; use gpui2::{ - elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext, - Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, + Action, AnyElement, AnyView, AppContext, Render, Subscription, View, ViewContext, WeakView, + WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::rc::Rc; -use theme2::ThemeSettings; +use std::sync::Arc; -pub trait Panel: View { +pub trait Panel: Render { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); @@ -55,10 +54,10 @@ pub trait PanelHandle { fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>); fn icon_label(&self, cx: &WindowContext) -> Option; fn has_focus(&self, cx: &WindowContext) -> bool; - fn as_any(&self) -> &AnyViewHandle; + fn to_any(&self) -> AnyView; } -impl PanelHandle for ViewHandle +impl PanelHandle for View where T: Panel, { @@ -114,14 +113,14 @@ where self.read(cx).has_focus(cx) } - fn as_any(&self) -> &AnyViewHandle { - self + fn to_any(&self) -> AnyView { + self.clone().into_any() } } -impl From<&dyn PanelHandle> for AnyViewHandle { +impl From<&dyn PanelHandle> for AnyView { fn from(val: &dyn PanelHandle) -> Self { - val.as_any().clone() + val.to_any() } } @@ -149,13 +148,14 @@ impl DockPosition { } } - fn to_resize_handle_side(self) -> HandleSide { - match self { - Self::Left => HandleSide::Right, - Self::Bottom => HandleSide::Top, - Self::Right => HandleSide::Left, - } - } + // todo!() + // fn to_resize_handle_side(self) -> HandleSide { + // match self { + // Self::Left => HandleSide::Right, + // Self::Bottom => HandleSide::Top, + // Self::Right => HandleSide::Left, + // } + // } pub fn axis(&self) -> Axis { match self { @@ -166,14 +166,15 @@ impl DockPosition { } struct PanelEntry { - panel: Rc, - context_menu: ViewHandle, + panel: Arc, + // todo!() + // context_menu: View, _subscriptions: [Subscription; 2], } pub struct PanelButtons { - dock: ViewHandle, - workspace: WeakViewHandle, + dock: View, + workspace: WeakView, } impl Dock { @@ -199,7 +200,7 @@ impl Dock { .map_or(false, |panel| panel.has_focus(cx)) } - pub fn panel(&self) -> Option> { + pub fn panel(&self) -> Option> { self.panel_entries .iter() .find_map(|entry| entry.panel.as_any().clone().downcast()) @@ -212,10 +213,11 @@ impl Dock { } pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { - self.panel_entries.iter().position(|entry| { - let panel = entry.panel.as_any(); - cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) - }) + todo!() + // self.panel_entries.iter().position(|entry| { + // let panel = entry.panel.as_any(); + // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) + // }) } pub fn active_panel_index(&self) -> usize { @@ -233,12 +235,7 @@ impl Dock { } } - pub fn set_panel_zoomed( - &mut self, - panel: &AnyViewHandle, - zoomed: bool, - cx: &mut ViewContext, - ) { + pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { for entry in &mut self.panel_entries { if entry.panel.as_any() == panel { if zoomed != entry.panel.is_zoomed(cx) { @@ -260,7 +257,7 @@ impl Dock { } } - pub(crate) fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) { + pub(crate) fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), cx.subscribe(&panel, |this, panel, event, cx| { @@ -284,18 +281,19 @@ impl Dock { let dock_view_id = cx.view_id(); self.panel_entries.push(PanelEntry { - panel: Rc::new(panel), - context_menu: cx.add_view(|cx| { - let mut menu = ContextMenu::new(dock_view_id, cx); - menu.set_position_mode(OverlayPositionMode::Local); - menu - }), + panel: Arc::new(panel), + // todo!() + // context_menu: cx.add_view(|cx| { + // let mut menu = ContextMenu::new(dock_view_id, cx); + // menu.set_position_mode(OverlayPositionMode::Local); + // menu + // }), _subscriptions: subscriptions, }); cx.notify() } - pub fn remove_panel(&mut self, panel: &ViewHandle, cx: &mut ViewContext) { + pub fn remove_panel(&mut self, panel: &View, cx: &mut ViewContext) { if let Some(panel_ix) = self .panel_entries .iter() @@ -331,12 +329,12 @@ impl Dock { } } - pub fn visible_panel(&self) -> Option<&Rc> { + pub fn visible_panel(&self) -> Option<&Arc> { let entry = self.visible_entry()?; Some(&entry.panel) } - pub fn active_panel(&self) -> Option<&Rc> { + pub fn active_panel(&self) -> Option<&Arc> { Some(&self.panel_entries.get(self.active_panel_index)?.panel) } @@ -348,7 +346,7 @@ impl Dock { } } - pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { + pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { let entry = self.visible_entry()?; if entry.panel.is_zoomed(cx) { Some(entry.panel.clone()) @@ -382,227 +380,218 @@ impl Dock { } pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { - if let Some(active_entry) = self.visible_entry() { - Empty::new() - .into_any() - .contained() - .with_style(self.style(cx)) - .resizable::( - self.position.to_resize_handle_side(), - active_entry.panel.size(cx), - |_, _, _| {}, - ) - .into_any() - } else { - Empty::new().into_any() - } - } - - fn style(&self, cx: &WindowContext) -> ContainerStyle { - let theme = &settings::get::(cx).theme; - let style = match self.position { - DockPosition::Left => theme.workspace.dock.left, - DockPosition::Bottom => theme.workspace.dock.bottom, - DockPosition::Right => theme.workspace.dock.right, - }; - style + todo!() + // if let Some(active_entry) = self.visible_entry() { + // Empty::new() + // .into_any() + // .contained() + // .with_style(self.style(cx)) + // .resizable::( + // self.position.to_resize_handle_side(), + // active_entry.panel.size(cx), + // |_, _, _| {}, + // ) + // .into_any() + // } else { + // Empty::new().into_any() + // } } } -impl Entity for Dock { - type Event = (); -} +// todo!() +// impl View for Dock { +// fn ui_name() -> &'static str { +// "Dock" +// } -impl View for Dock { - fn ui_name() -> &'static str { - "Dock" - } +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// if let Some(active_entry) = self.visible_entry() { +// let style = self.style(cx); +// ChildView::new(active_entry.panel.as_any(), cx) +// .contained() +// .with_style(style) +// .resizable::( +// self.position.to_resize_handle_side(), +// active_entry.panel.size(cx), +// |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), +// ) +// .into_any() +// } else { +// Empty::new().into_any() +// } +// } - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_entry) = self.visible_entry() { - let style = self.style(cx); - ChildView::new(active_entry.panel.as_any(), cx) - .contained() - .with_style(style) - .resizable::( - self.position.to_resize_handle_side(), - active_entry.panel.size(cx), - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() - } else { - Empty::new().into_any() - } - } +// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { +// if cx.is_self_focused() { +// if let Some(active_entry) = self.visible_entry() { +// cx.focus(active_entry.panel.as_any()); +// } else { +// cx.focus_parent(); +// } +// } +// } +// } - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - if let Some(active_entry) = self.visible_entry() { - cx.focus(active_entry.panel.as_any()); - } else { - cx.focus_parent(); - } - } - } -} +// todo!() +// impl PanelButtons { +// pub fn new( +// dock: View, +// workspace: WeakViewHandle, +// cx: &mut ViewContext, +// ) -> Self { +// cx.observe(&dock, |_, _, cx| cx.notify()).detach(); +// Self { dock, workspace } +// } +// } -impl PanelButtons { - pub fn new( - dock: ViewHandle, - workspace: WeakViewHandle, - cx: &mut ViewContext, - ) -> Self { - cx.observe(&dock, |_, _, cx| cx.notify()).detach(); - Self { dock, workspace } - } -} +// todo!() +// impl Entity for PanelButtons { +// type Event = (); +// } -impl Entity for PanelButtons { - type Event = (); -} +// todo!() +// impl View for PanelButtons { +// fn ui_name() -> &'static str { +// "PanelButtons" +// } -impl View for PanelButtons { - fn ui_name() -> &'static str { - "PanelButtons" - } +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = &settings::get::(cx).theme; +// let tooltip_style = theme.tooltip.clone(); +// let theme = &theme.workspace.status_bar.panel_buttons; +// let button_style = theme.button.clone(); +// let dock = self.dock.read(cx); +// let active_ix = dock.active_panel_index; +// let is_open = dock.is_open; +// let dock_position = dock.position; +// let group_style = match dock_position { +// DockPosition::Left => theme.group_left, +// DockPosition::Bottom => theme.group_bottom, +// DockPosition::Right => theme.group_right, +// }; +// let menu_corner = match dock_position { +// DockPosition::Left => AnchorCorner::BottomLeft, +// DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, +// }; - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &settings::get::(cx).theme; - let tooltip_style = theme.tooltip.clone(); - let theme = &theme.workspace.status_bar.panel_buttons; - let button_style = theme.button.clone(); - let dock = self.dock.read(cx); - let active_ix = dock.active_panel_index; - let is_open = dock.is_open; - let dock_position = dock.position; - let group_style = match dock_position { - DockPosition::Left => theme.group_left, - DockPosition::Bottom => theme.group_bottom, - DockPosition::Right => theme.group_right, - }; - let menu_corner = match dock_position { - DockPosition::Left => AnchorCorner::BottomLeft, - DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, - }; +// let panels = dock +// .panel_entries +// .iter() +// .map(|item| (item.panel.clone(), item.context_menu.clone())) +// .collect::>(); +// Flex::row() +// .with_children(panels.into_iter().enumerate().filter_map( +// |(panel_ix, (view, context_menu))| { +// let icon_path = view.icon_path(cx)?; +// let is_active = is_open && panel_ix == active_ix; +// let (tooltip, tooltip_action) = if is_active { +// ( +// format!("Close {} dock", dock_position.to_label()), +// Some(match dock_position { +// DockPosition::Left => crate::ToggleLeftDock.boxed_clone(), +// DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(), +// DockPosition::Right => crate::ToggleRightDock.boxed_clone(), +// }), +// ) +// } else { +// view.icon_tooltip(cx) +// }; +// Some( +// Stack::new() +// .with_child( +// MouseEventHandler::new::(panel_ix, cx, |state, cx| { +// let style = button_style.in_state(is_active); - let panels = dock - .panel_entries - .iter() - .map(|item| (item.panel.clone(), item.context_menu.clone())) - .collect::>(); - Flex::row() - .with_children(panels.into_iter().enumerate().filter_map( - |(panel_ix, (view, context_menu))| { - let icon_path = view.icon_path(cx)?; - let is_active = is_open && panel_ix == active_ix; - let (tooltip, tooltip_action) = if is_active { - ( - format!("Close {} dock", dock_position.to_label()), - Some(match dock_position { - DockPosition::Left => crate::ToggleLeftDock.boxed_clone(), - DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(), - DockPosition::Right => crate::ToggleRightDock.boxed_clone(), - }), - ) - } else { - view.icon_tooltip(cx) - }; - Some( - Stack::new() - .with_child( - MouseEventHandler::new::(panel_ix, cx, |state, cx| { - let style = button_style.in_state(is_active); +// let style = style.style_for(state); +// Flex::row() +// .with_child( +// Svg::new(icon_path) +// .with_color(style.icon_color) +// .constrained() +// .with_width(style.icon_size) +// .aligned(), +// ) +// .with_children(if let Some(label) = view.icon_label(cx) { +// Some( +// Label::new(label, style.label.text.clone()) +// .contained() +// .with_style(style.label.container) +// .aligned(), +// ) +// } else { +// None +// }) +// .constrained() +// .with_height(style.icon_size) +// .contained() +// .with_style(style.container) +// }) +// .with_cursor_style(CursorStyle::PointingHand) +// .on_click(MouseButton::Left, { +// let tooltip_action = +// tooltip_action.as_ref().map(|action| action.boxed_clone()); +// move |_, this, cx| { +// if let Some(tooltip_action) = &tooltip_action { +// let window = cx.window(); +// let view_id = this.workspace.id(); +// let tooltip_action = tooltip_action.boxed_clone(); +// cx.spawn(|_, mut cx| async move { +// window.dispatch_action( +// view_id, +// &*tooltip_action, +// &mut cx, +// ); +// }) +// .detach(); +// } +// } +// }) +// .on_click(MouseButton::Right, { +// let view = view.clone(); +// let menu = context_menu.clone(); +// move |_, _, cx| { +// const POSITIONS: [DockPosition; 3] = [ +// DockPosition::Left, +// DockPosition::Right, +// DockPosition::Bottom, +// ]; - let style = style.style_for(state); - Flex::row() - .with_child( - Svg::new(icon_path) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .aligned(), - ) - .with_children(if let Some(label) = view.icon_label(cx) { - Some( - Label::new(label, style.label.text.clone()) - .contained() - .with_style(style.label.container) - .aligned(), - ) - } else { - None - }) - .constrained() - .with_height(style.icon_size) - .contained() - .with_style(style.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let tooltip_action = - tooltip_action.as_ref().map(|action| action.boxed_clone()); - move |_, this, cx| { - if let Some(tooltip_action) = &tooltip_action { - let window = cx.window(); - let view_id = this.workspace.id(); - let tooltip_action = tooltip_action.boxed_clone(); - cx.spawn(|_, mut cx| async move { - window.dispatch_action( - view_id, - &*tooltip_action, - &mut cx, - ); - }) - .detach(); - } - } - }) - .on_click(MouseButton::Right, { - let view = view.clone(); - let menu = context_menu.clone(); - move |_, _, cx| { - const POSITIONS: [DockPosition; 3] = [ - DockPosition::Left, - DockPosition::Right, - DockPosition::Bottom, - ]; - - menu.update(cx, |menu, cx| { - let items = POSITIONS - .into_iter() - .filter(|position| { - *position != dock_position - && view.position_is_valid(*position, cx) - }) - .map(|position| { - let view = view.clone(); - ContextMenuItem::handler( - format!("Dock {}", position.to_label()), - move |cx| view.set_position(position, cx), - ) - }) - .collect(); - menu.show(Default::default(), menu_corner, items, cx); - }) - } - }) - .with_tooltip::( - panel_ix, - tooltip, - tooltip_action, - tooltip_style.clone(), - cx, - ), - ) - .with_child(ChildView::new(&context_menu, cx)), - ) - }, - )) - .contained() - .with_style(group_style) - .into_any() - } -} +// menu.update(cx, |menu, cx| { +// let items = POSITIONS +// .into_iter() +// .filter(|position| { +// *position != dock_position +// && view.position_is_valid(*position, cx) +// }) +// .map(|position| { +// let view = view.clone(); +// ContextMenuItem::handler( +// format!("Dock {}", position.to_label()), +// move |cx| view.set_position(position, cx), +// ) +// }) +// .collect(); +// menu.show(Default::default(), menu_corner, items, cx); +// }) +// } +// }) +// .with_tooltip::( +// panel_ix, +// tooltip, +// tooltip_action, +// tooltip_style.clone(), +// cx, +// ), +// ) +// .with_child(ChildView::new(&context_menu, cx)), +// ) +// }, +// )) +// .contained() +// .with_style(group_style) +// .into_any() +// } +// } impl StatusItemView for PanelButtons { fn set_active_pane_item( @@ -616,7 +605,7 @@ impl StatusItemView for PanelButtons { #[cfg(any(test, feature = "test-support"))] pub mod test { use super::*; - use gpui2::{ViewContext, WindowContext}; + use gpui2::{div, Div, ViewContext, WindowContext}; #[derive(Debug)] pub enum TestPanelEvent { @@ -648,31 +637,16 @@ pub mod test { } } - impl Entity for TestPanel { - type Event = TestPanelEvent; - } + impl Render for TestPanel { + type Element = Div; - impl View for TestPanel { - fn ui_name() -> &'static str { - "TestPanel" - } - - fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement { - Empty::new().into_any() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; - cx.emit(TestPanelEvent::Focus); - } - - fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { - self.has_focus = false; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + div() } } impl Panel for TestPanel { - fn position(&self, _: &gpui::WindowContext) -> super::DockPosition { + fn position(&self, _: &gpui2::WindowContext) -> super::DockPosition { self.position } diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index b62dae2114..b68b366c7c 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -1,18 +1,8 @@ +use crate::{ItemHandle, Pane}; +use gpui2::{AnyView, Render, Subscription, View, ViewContext, WindowContext}; use std::ops::Range; -use crate::{ItemHandle, Pane}; -use gpui::{ - elements::*, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, - json::{json, ToJson}, - AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle, - WindowContext, -}; - -pub trait StatusItemView: View { +pub trait StatusItemView: Render { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, @@ -21,7 +11,7 @@ pub trait StatusItemView: View { } trait StatusItemViewHandle { - fn as_any(&self) -> &AnyViewHandle; + fn to_any(&self) -> AnyView; fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, @@ -33,50 +23,47 @@ trait StatusItemViewHandle { pub struct StatusBar { left_items: Vec>, right_items: Vec>, - active_pane: ViewHandle, + active_pane: View, _observe_active_pane: Subscription, } -impl Entity for StatusBar { - type Event = (); -} +// todo!() +// impl View for StatusBar { +// fn ui_name() -> &'static str { +// "StatusBar" +// } -impl View for StatusBar { - fn ui_name() -> &'static str { - "StatusBar" - } +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = &theme::current(cx).workspace.status_bar; - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &theme::current(cx).workspace.status_bar; - - StatusBarElement { - left: Flex::row() - .with_children(self.left_items.iter().map(|i| { - ChildView::new(i.as_any(), cx) - .aligned() - .contained() - .with_margin_right(theme.item_spacing) - })) - .into_any(), - right: Flex::row() - .with_children(self.right_items.iter().rev().map(|i| { - ChildView::new(i.as_any(), cx) - .aligned() - .contained() - .with_margin_left(theme.item_spacing) - })) - .into_any(), - } - .contained() - .with_style(theme.container) - .constrained() - .with_height(theme.height) - .into_any() - } -} +// StatusBarElement { +// left: Flex::row() +// .with_children(self.left_items.iter().map(|i| { +// ChildView::new(i.as_any(), cx) +// .aligned() +// .contained() +// .with_margin_right(theme.item_spacing) +// })) +// .into_any(), +// right: Flex::row() +// .with_children(self.right_items.iter().rev().map(|i| { +// ChildView::new(i.as_any(), cx) +// .aligned() +// .contained() +// .with_margin_left(theme.item_spacing) +// })) +// .into_any(), +// } +// .contained() +// .with_style(theme.container) +// .constrained() +// .with_height(theme.height) +// .into_any() +// } +// } impl StatusBar { - pub fn new(active_pane: &ViewHandle, cx: &mut ViewContext) -> Self { + pub fn new(active_pane: &View, cx: &mut ViewContext) -> Self { let mut this = Self { left_items: Default::default(), right_items: Default::default(), @@ -88,7 +75,7 @@ impl StatusBar { this } - pub fn add_left_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + pub fn add_left_item(&mut self, item: View, cx: &mut ViewContext) where T: 'static + StatusItemView, { @@ -96,7 +83,7 @@ impl StatusBar { cx.notify(); } - pub fn item_of_type(&self) -> Option> { + pub fn item_of_type(&self) -> Option> { self.left_items .iter() .chain(self.right_items.iter()) @@ -146,7 +133,7 @@ impl StatusBar { cx.notify(); } - pub fn add_right_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + pub fn add_right_item(&mut self, item: View, cx: &mut ViewContext) where T: 'static + StatusItemView, { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 723a922bc6..3e1578f779 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,4 +1,4 @@ -// pub mod dock; +pub mod dock; pub mod item; // pub mod notifications; pub mod pane; @@ -6,7 +6,7 @@ pub mod pane_group; mod persistence; pub mod searchable; // pub mod shared_screen; -// mod status_bar; +mod status_bar; mod toolbar; mod workspace_settings; @@ -37,7 +37,7 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use crate::persistence::model::{ - DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, + DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }; // lazy_static! { From 8d0905e4793e14530c7cda5fae943292bd676335 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 09:25:36 -0400 Subject: [PATCH 15/66] dock compiling with todos outstanding Co-Authored-By: Kirill --- crates/workspace2/src/dock.rs | 430 +++++++++++++++++----------------- 1 file changed, 219 insertions(+), 211 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 5165c970af..33edc27e62 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,13 +1,12 @@ -use crate::{Axis, Workspace}; +use crate::{status_bar::StatusItemView, Axis, Workspace}; use gpui2::{ - Action, AnyElement, AnyView, AppContext, Render, Subscription, View, ViewContext, WeakView, - WindowContext, + Action, AnyView, EventEmitter, Render, Subscription, View, ViewContext, WeakView, WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::sync::Arc; -pub trait Panel: Render { +pub trait Panel: Render + EventEmitter { fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); @@ -177,226 +176,226 @@ pub struct PanelButtons { workspace: WeakView, } -impl Dock { - pub fn new(position: DockPosition) -> Self { - Self { - position, - panel_entries: Default::default(), - active_panel_index: 0, - is_open: false, - } - } +// impl Dock { +// pub fn new(position: DockPosition) -> Self { +// Self { +// position, +// panel_entries: Default::default(), +// active_panel_index: 0, +// is_open: false, +// } +// } - pub fn position(&self) -> DockPosition { - self.position - } +// pub fn position(&self) -> DockPosition { +// self.position +// } - pub fn is_open(&self) -> bool { - self.is_open - } +// pub fn is_open(&self) -> bool { +// self.is_open +// } - pub fn has_focus(&self, cx: &WindowContext) -> bool { - self.visible_panel() - .map_or(false, |panel| panel.has_focus(cx)) - } +// pub fn has_focus(&self, cx: &WindowContext) -> bool { +// self.visible_panel() +// .map_or(false, |panel| panel.has_focus(cx)) +// } - pub fn panel(&self) -> Option> { - self.panel_entries - .iter() - .find_map(|entry| entry.panel.as_any().clone().downcast()) - } +// pub fn panel(&self) -> Option> { +// self.panel_entries +// .iter() +// .find_map(|entry| entry.panel.as_any().clone().downcast()) +// } - pub fn panel_index_for_type(&self) -> Option { - self.panel_entries - .iter() - .position(|entry| entry.panel.as_any().is::()) - } +// pub fn panel_index_for_type(&self) -> Option { +// self.panel_entries +// .iter() +// .position(|entry| entry.panel.as_any().is::()) +// } - pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { - todo!() - // self.panel_entries.iter().position(|entry| { - // let panel = entry.panel.as_any(); - // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) - // }) - } +// pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { +// todo!() +// // self.panel_entries.iter().position(|entry| { +// // let panel = entry.panel.as_any(); +// // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) +// // }) +// } - pub fn active_panel_index(&self) -> usize { - self.active_panel_index - } +// pub fn active_panel_index(&self) -> usize { +// self.active_panel_index +// } - pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { - if open != self.is_open { - self.is_open = open; - if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { - active_panel.panel.set_active(open, cx); - } +// pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { +// if open != self.is_open { +// self.is_open = open; +// if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { +// active_panel.panel.set_active(open, cx); +// } - cx.notify(); - } - } +// cx.notify(); +// } +// } - pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { - for entry in &mut self.panel_entries { - if entry.panel.as_any() == panel { - if zoomed != entry.panel.is_zoomed(cx) { - entry.panel.set_zoomed(zoomed, cx); - } - } else if entry.panel.is_zoomed(cx) { - entry.panel.set_zoomed(false, cx); - } - } +// pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { +// for entry in &mut self.panel_entries { +// if entry.panel.as_any() == panel { +// if zoomed != entry.panel.is_zoomed(cx) { +// entry.panel.set_zoomed(zoomed, cx); +// } +// } else if entry.panel.is_zoomed(cx) { +// entry.panel.set_zoomed(false, cx); +// } +// } - cx.notify(); - } +// cx.notify(); +// } - pub fn zoom_out(&mut self, cx: &mut ViewContext) { - for entry in &mut self.panel_entries { - if entry.panel.is_zoomed(cx) { - entry.panel.set_zoomed(false, cx); - } - } - } +// pub fn zoom_out(&mut self, cx: &mut ViewContext) { +// for entry in &mut self.panel_entries { +// if entry.panel.is_zoomed(cx) { +// entry.panel.set_zoomed(false, cx); +// } +// } +// } - pub(crate) fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { - let subscriptions = [ - cx.observe(&panel, |_, _, cx| cx.notify()), - cx.subscribe(&panel, |this, panel, event, cx| { - if T::should_activate_on_event(event) { - if let Some(ix) = this - .panel_entries - .iter() - .position(|entry| entry.panel.id() == panel.id()) - { - this.set_open(true, cx); - this.activate_panel(ix, cx); - cx.focus(&panel); - } - } else if T::should_close_on_event(event) - && this.visible_panel().map_or(false, |p| p.id() == panel.id()) - { - this.set_open(false, cx); - } - }), - ]; +// pub(crate) fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { +// let subscriptions = [ +// cx.observe(&panel, |_, _, cx| cx.notify()), +// cx.subscribe(&panel, |this, panel, event, cx| { +// if T::should_activate_on_event(event) { +// if let Some(ix) = this +// .panel_entries +// .iter() +// .position(|entry| entry.panel.id() == panel.id()) +// { +// this.set_open(true, cx); +// this.activate_panel(ix, cx); +// cx.focus(&panel); +// } +// } else if T::should_close_on_event(event) +// && this.visible_panel().map_or(false, |p| p.id() == panel.id()) +// { +// this.set_open(false, cx); +// } +// }), +// ]; - let dock_view_id = cx.view_id(); - self.panel_entries.push(PanelEntry { - panel: Arc::new(panel), - // todo!() - // context_menu: cx.add_view(|cx| { - // let mut menu = ContextMenu::new(dock_view_id, cx); - // menu.set_position_mode(OverlayPositionMode::Local); - // menu - // }), - _subscriptions: subscriptions, - }); - cx.notify() - } +// let dock_view_id = cx.view_id(); +// self.panel_entries.push(PanelEntry { +// panel: Arc::new(panel), +// // todo!() +// // context_menu: cx.add_view(|cx| { +// // let mut menu = ContextMenu::new(dock_view_id, cx); +// // menu.set_position_mode(OverlayPositionMode::Local); +// // menu +// // }), +// _subscriptions: subscriptions, +// }); +// cx.notify() +// } - pub fn remove_panel(&mut self, panel: &View, cx: &mut ViewContext) { - if let Some(panel_ix) = self - .panel_entries - .iter() - .position(|entry| entry.panel.id() == panel.id()) - { - if panel_ix == self.active_panel_index { - self.active_panel_index = 0; - self.set_open(false, cx); - } else if panel_ix < self.active_panel_index { - self.active_panel_index -= 1; - } - self.panel_entries.remove(panel_ix); - cx.notify(); - } - } +// pub fn remove_panel(&mut self, panel: &View, cx: &mut ViewContext) { +// if let Some(panel_ix) = self +// .panel_entries +// .iter() +// .position(|entry| entry.panel.id() == panel.id()) +// { +// if panel_ix == self.active_panel_index { +// self.active_panel_index = 0; +// self.set_open(false, cx); +// } else if panel_ix < self.active_panel_index { +// self.active_panel_index -= 1; +// } +// self.panel_entries.remove(panel_ix); +// cx.notify(); +// } +// } - pub fn panels_len(&self) -> usize { - self.panel_entries.len() - } +// pub fn panels_len(&self) -> usize { +// self.panel_entries.len() +// } - pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - if panel_ix != self.active_panel_index { - if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { - active_panel.panel.set_active(false, cx); - } +// pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { +// if panel_ix != self.active_panel_index { +// if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { +// active_panel.panel.set_active(false, cx); +// } - self.active_panel_index = panel_ix; - if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { - active_panel.panel.set_active(true, cx); - } +// self.active_panel_index = panel_ix; +// if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { +// active_panel.panel.set_active(true, cx); +// } - cx.notify(); - } - } +// cx.notify(); +// } +// } - pub fn visible_panel(&self) -> Option<&Arc> { - let entry = self.visible_entry()?; - Some(&entry.panel) - } +// pub fn visible_panel(&self) -> Option<&Arc> { +// let entry = self.visible_entry()?; +// Some(&entry.panel) +// } - pub fn active_panel(&self) -> Option<&Arc> { - Some(&self.panel_entries.get(self.active_panel_index)?.panel) - } +// pub fn active_panel(&self) -> Option<&Arc> { +// Some(&self.panel_entries.get(self.active_panel_index)?.panel) +// } - fn visible_entry(&self) -> Option<&PanelEntry> { - if self.is_open { - self.panel_entries.get(self.active_panel_index) - } else { - None - } - } +// fn visible_entry(&self) -> Option<&PanelEntry> { +// if self.is_open { +// self.panel_entries.get(self.active_panel_index) +// } else { +// None +// } +// } - pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { - let entry = self.visible_entry()?; - if entry.panel.is_zoomed(cx) { - Some(entry.panel.clone()) - } else { - None - } - } +// pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { +// let entry = self.visible_entry()?; +// if entry.panel.is_zoomed(cx) { +// Some(entry.panel.clone()) +// } else { +// None +// } +// } - pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { - self.panel_entries - .iter() - .find(|entry| entry.panel.id() == panel.id()) - .map(|entry| entry.panel.size(cx)) - } +// pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { +// self.panel_entries +// .iter() +// .find(|entry| entry.panel.id() == panel.id()) +// .map(|entry| entry.panel.size(cx)) +// } - pub fn active_panel_size(&self, cx: &WindowContext) -> Option { - if self.is_open { - self.panel_entries - .get(self.active_panel_index) - .map(|entry| entry.panel.size(cx)) - } else { - None - } - } +// pub fn active_panel_size(&self, cx: &WindowContext) -> Option { +// if self.is_open { +// self.panel_entries +// .get(self.active_panel_index) +// .map(|entry| entry.panel.size(cx)) +// } else { +// None +// } +// } - pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { - if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { - entry.panel.set_size(size, cx); - cx.notify(); - } - } +// pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { +// if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { +// entry.panel.set_size(size, cx); +// cx.notify(); +// } +// } - pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { - todo!() - // if let Some(active_entry) = self.visible_entry() { - // Empty::new() - // .into_any() - // .contained() - // .with_style(self.style(cx)) - // .resizable::( - // self.position.to_resize_handle_side(), - // active_entry.panel.size(cx), - // |_, _, _| {}, - // ) - // .into_any() - // } else { - // Empty::new().into_any() - // } - } -} +// pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { +// todo!() +// if let Some(active_entry) = self.visible_entry() { +// Empty::new() +// .into_any() +// .contained() +// .with_style(self.style(cx)) +// .resizable::( +// self.position.to_resize_handle_side(), +// active_entry.panel.size(cx), +// |_, _, _| {}, +// ) +// .into_any() +// } else { +// Empty::new().into_any() +// } +// } +// } // todo!() // impl View for Dock { @@ -444,13 +443,17 @@ impl Dock { // } // } -// todo!() -// impl Entity for PanelButtons { -// type Event = (); -// } +impl EventEmitter for PanelButtons { + type Event = (); +} + +// impl Render for PanelButtons { +// type Element = (); + +// fn render(&mut self, cx: &mut ViewContext) -> Self::Element { +// todo!("") +// } -// todo!() -// impl View for PanelButtons { // fn ui_name() -> &'static str { // "PanelButtons" // } @@ -593,14 +596,15 @@ impl Dock { // } // } -impl StatusItemView for PanelButtons { - fn set_active_pane_item( - &mut self, - _: Option<&dyn crate::ItemHandle>, - _: &mut ViewContext, - ) { - } -} +// impl StatusItemView for PanelButtons { +// fn set_active_pane_item( +// &mut self, +// active_pane_item: Option<&dyn crate::ItemHandle>, +// cx: &mut ViewContext, +// ) { +// todo!() +// } +// } #[cfg(any(test, feature = "test-support"))] pub mod test { @@ -625,6 +629,10 @@ pub mod test { pub size: f32, } + impl EventEmitter for TestPanel { + type Event = TestPanelEvent; + } + impl TestPanel { pub fn new(position: DockPosition) -> Self { Self { From efce38fce2a940a539ca1aa81f40f38c49085bc8 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 10:04:04 -0400 Subject: [PATCH 16/66] wip --- crates/gpui2/src/app/entity_map.rs | 5 +++++ crates/workspace2/src/pane.rs | 24 ++++++++++++--------- crates/workspace2/src/pane_group.rs | 16 +++++++------- crates/workspace2/src/persistence/model.rs | 11 ++++------ crates/workspace2/src/workspace_settings.rs | 2 +- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index bbeabd3e4f..8ceadfe73e 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -164,6 +164,11 @@ impl AnyModel { self.entity_id } + // todo!() added for populating `ProjectItemBuilders` in `load_path` method + pub fn type_id(&self) -> TypeId { + self.entity_type + } + pub fn downgrade(&self) -> AnyWeakModel { AnyWeakModel { entity_id: self.entity_id, diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 22aa61e6cd..9325f58b37 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -18,7 +18,10 @@ use std::{ any::Any, cmp, fmt, mem, path::PathBuf, - sync::{atomic::AtomicUsize, Arc}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, }; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -168,7 +171,7 @@ pub struct Pane { // zoomed: bool, active_item_index: usize, // last_focused_view_by_item: HashMap, - // autoscroll: bool, + autoscroll: bool, nav_history: NavHistory, toolbar: View, // tab_bar_context_menu: TabBarContextMenu, @@ -327,7 +330,7 @@ impl Pane { // zoomed: false, active_item_index: 0, // last_focused_view_by_item: Default::default(), - // autoscroll: false, + autoscroll: false, nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState { mode: NavigationMode::Normal, backward_stack: Default::default(), @@ -607,9 +610,9 @@ impl Pane { cx.emit(Event::AddItem { item }); } - // pub fn items_len(&self) -> usize { - // self.items.len() - // } + pub fn items_len(&self) -> usize { + self.items.len() + } // pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { // self.items.iter() @@ -621,9 +624,9 @@ impl Pane { // .filter_map(|item| item.as_any().clone().downcast()) // } - // pub fn active_item(&self) -> Option> { - // self.items.get(self.active_item_index).cloned() - // } + pub fn active_item(&self) -> Option> { + self.items.get(self.active_item_index).cloned() + } // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option { // self.items @@ -749,7 +752,8 @@ impl Pane { save_intent: SaveIntent, cx: &mut ViewContext, ) -> Task> { - self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) + todo!() } // pub fn close_inactive_items( diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index f226f7fc43..964194ef22 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -2,7 +2,7 @@ use crate::{AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, Result}; use call2::ActiveCall; use collections::HashMap; -use gpui2::{size, AnyElement, AnyView, Bounds, Handle, Pixels, Point, View, ViewContext}; +use gpui2::{size, AnyElement, AnyView, Bounds, Handle, Model, Pixels, Point, View, ViewContext}; use project2::Project; use serde::Deserialize; use std::{cell::RefCell, rc::Rc, sync::Arc}; @@ -91,10 +91,10 @@ impl PaneGroup { pub(crate) fn render( &self, - project: &Handle, + project: &Model, theme: &Theme, follower_states: &HashMap, FollowerState>, - active_call: Option<&Handle>, + active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyView>, app_state: &Arc, @@ -120,7 +120,7 @@ impl PaneGroup { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub(crate) enum Member { Axis(PaneAxis), Pane(View), @@ -153,11 +153,11 @@ impl Member { pub fn render( &self, - project: &Handle, + project: &Model, basis: usize, theme: &Theme, follower_states: &HashMap, FollowerState>, - active_call: Option<&Handle>, + active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyView>, app_state: &Arc, @@ -470,11 +470,11 @@ impl PaneAxis { fn render( &self, - project: &Handle, + project: &Model, basis: usize, theme: &Theme, follower_states: &HashMap, FollowerState>, - active_call: Option<&Handle>, + active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyView>, app_state: &Arc, diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 8265848497..4323e6dae0 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -7,7 +7,7 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{AsyncAppContext, AsyncWindowContext, Handle, Task, View, WeakView, WindowBounds}; +use gpui2::{AsyncAppContext, AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; use project2::Project; use std::{ path::{Path, PathBuf}, @@ -151,7 +151,7 @@ impl SerializedPaneGroup { #[async_recursion(?Send)] pub(crate) async fn deserialize( self, - project: &Handle, + project: &Model, workspace_id: WorkspaceId, workspace: &WeakView, cx: &mut AsyncWindowContext, @@ -200,10 +200,7 @@ impl SerializedPaneGroup { .await .log_err()?; - if pane - .read_with(cx, |pane, _| pane.items_len() != 0) - .log_err()? - { + if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? { let pane = pane.upgrade()?; Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) } else { @@ -231,7 +228,7 @@ impl SerializedPane { pub async fn deserialize_to( &self, - project: &Handle, + project: &Model, pane: &WeakView, workspace_id: WorkspaceId, workspace: &WeakView, diff --git a/crates/workspace2/src/workspace_settings.rs b/crates/workspace2/src/workspace_settings.rs index 5d158e5a05..4b93b705a3 100644 --- a/crates/workspace2/src/workspace_settings.rs +++ b/crates/workspace2/src/workspace_settings.rs @@ -49,7 +49,7 @@ impl Settings for WorkspaceSettings { fn load( default_value: &Self::FileContent, user_values: &[&Self::FileContent], - _: &gpui2::AppContext, + _: &mut gpui2::AppContext, ) -> anyhow::Result { Self::load_via_json_merge(default_value, user_values) } From eb4ac2c27688267a13cbfdd8b358054adb8af7d0 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 10:12:40 -0400 Subject: [PATCH 17/66] wip --- crates/workspace2/src/status_bar.rs | 162 ++++++++++++++-------------- 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index b68b366c7c..4567367be6 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -1,6 +1,8 @@ +use std::any::TypeId; + use crate::{ItemHandle, Pane}; use gpui2::{AnyView, Render, Subscription, View, ViewContext, WindowContext}; -use std::ops::Range; +use util::ResultExt; pub trait StatusItemView: Render { fn set_active_pane_item( @@ -10,14 +12,14 @@ pub trait StatusItemView: Render { ); } -trait StatusItemViewHandle { +trait StatusItemViewHandle: Send { fn to_any(&self) -> AnyView; fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut WindowContext, ); - fn ui_name(&self) -> &'static str; + fn item_type(&self) -> TypeId; } pub struct StatusBar { @@ -87,7 +89,7 @@ impl StatusBar { self.left_items .iter() .chain(self.right_items.iter()) - .find_map(|item| item.as_any().clone().downcast()) + .find_map(|item| item.to_any().clone().downcast().log_err()) } pub fn position_of_item(&self) -> Option @@ -95,12 +97,12 @@ impl StatusBar { T: StatusItemView, { for (index, item) in self.left_items.iter().enumerate() { - if item.as_ref().ui_name() == T::ui_name() { + if item.item_type() == TypeId::of::() { return Some(index); } } for (index, item) in self.right_items.iter().enumerate() { - if item.as_ref().ui_name() == T::ui_name() { + if item.item_type() == TypeId::of::() { return Some(index + self.left_items.len()); } } @@ -110,7 +112,7 @@ impl StatusBar { pub fn insert_item_after( &mut self, position: usize, - item: ViewHandle, + item: View, cx: &mut ViewContext, ) where T: 'static + StatusItemView, @@ -141,7 +143,7 @@ impl StatusBar { cx.notify(); } - pub fn set_active_pane(&mut self, active_pane: &ViewHandle, cx: &mut ViewContext) { + pub fn set_active_pane(&mut self, active_pane: &View, cx: &mut ViewContext) { self.active_pane = active_pane.clone(); self._observe_active_pane = cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)); @@ -156,9 +158,9 @@ impl StatusBar { } } -impl StatusItemViewHandle for ViewHandle { - fn as_any(&self) -> &AnyViewHandle { - self +impl StatusItemViewHandle for View { + fn to_any(&self) -> AnyView { + self.clone().into_any() } fn set_active_pane_item( @@ -171,88 +173,90 @@ impl StatusItemViewHandle for ViewHandle { }); } - fn ui_name(&self) -> &'static str { - T::ui_name() + fn item_type(&self) -> TypeId { + TypeId::of::() } } -impl From<&dyn StatusItemViewHandle> for AnyViewHandle { +impl From<&dyn StatusItemViewHandle> for AnyView { fn from(val: &dyn StatusItemViewHandle) -> Self { - val.as_any().clone() + val.to_any().clone() } } -struct StatusBarElement { - left: AnyElement, - right: AnyElement, -} +// todo!() +// struct StatusBarElement { +// left: AnyElement, +// right: AnyElement, +// } -impl Element for StatusBarElement { - type LayoutState = (); - type PaintState = (); +// todo!() +// impl Element for StatusBarElement { +// type LayoutState = (); +// type PaintState = (); - fn layout( - &mut self, - mut constraint: SizeConstraint, - view: &mut StatusBar, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState) { - let max_width = constraint.max.x(); - constraint.min = vec2f(0., constraint.min.y()); +// fn layout( +// &mut self, +// mut constraint: SizeConstraint, +// view: &mut StatusBar, +// cx: &mut ViewContext, +// ) -> (Vector2F, Self::LayoutState) { +// let max_width = constraint.max.x(); +// constraint.min = vec2f(0., constraint.min.y()); - let right_size = self.right.layout(constraint, view, cx); - let constraint = SizeConstraint::new( - vec2f(0., constraint.min.y()), - vec2f(max_width - right_size.x(), constraint.max.y()), - ); +// let right_size = self.right.layout(constraint, view, cx); +// let constraint = SizeConstraint::new( +// vec2f(0., constraint.min.y()), +// vec2f(max_width - right_size.x(), constraint.max.y()), +// ); - self.left.layout(constraint, view, cx); +// self.left.layout(constraint, view, cx); - (vec2f(max_width, right_size.y()), ()) - } +// (vec2f(max_width, right_size.y()), ()) +// } - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - _: &mut Self::LayoutState, - view: &mut StatusBar, - cx: &mut ViewContext, - ) -> Self::PaintState { - let origin_y = bounds.upper_right().y(); - let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); +// fn paint( +// &mut self, +// bounds: RectF, +// visible_bounds: RectF, +// _: &mut Self::LayoutState, +// view: &mut StatusBar, +// cx: &mut ViewContext, +// ) -> Self::PaintState { +// let origin_y = bounds.upper_right().y(); +// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - let left_origin = vec2f(bounds.lower_left().x(), origin_y); - self.left.paint(left_origin, visible_bounds, view, cx); +// let left_origin = vec2f(bounds.lower_left().x(), origin_y); +// self.left.paint(left_origin, visible_bounds, view, cx); - let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y); - self.right.paint(right_origin, visible_bounds, view, cx); - } +// let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y); +// self.right.paint(right_origin, visible_bounds, view, cx); +// } - fn rect_for_text_range( - &self, - _: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &StatusBar, - _: &ViewContext, - ) -> Option { - None - } +// fn rect_for_text_range( +// &self, +// _: Range, +// _: RectF, +// _: RectF, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// _: &StatusBar, +// _: &ViewContext, +// ) -> Option { +// None +// } - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &StatusBar, - _: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "StatusBarElement", - "bounds": bounds.to_json() - }) - } -} +// fn debug( +// &self, +// bounds: RectF, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// _: &StatusBar, +// _: &ViewContext, +// ) -> serde_json::Value { +// json!({ +// "type": "StatusBarElement", +// "bounds": bounds.to_json() +// }) +// } +// } From 663e8aed8a18a10e69ead77f7172172978e56f2e Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 10:49:51 -0400 Subject: [PATCH 18/66] wip progress --- crates/workspace2/src/dock.rs | 415 +++++++++++---------- crates/workspace2/src/item.rs | 92 ++--- crates/workspace2/src/pane.rs | 16 +- crates/workspace2/src/pane_group.rs | 70 ++-- crates/workspace2/src/persistence/model.rs | 4 +- crates/workspace2/src/searchable.rs | 50 ++- crates/workspace2/src/workspace2.rs | 263 +++++++------ 7 files changed, 472 insertions(+), 438 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 33edc27e62..e8be64393f 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,6 +1,7 @@ use crate::{status_bar::StatusItemView, Axis, Workspace}; use gpui2::{ - Action, AnyView, EventEmitter, Render, Subscription, View, ViewContext, WeakView, WindowContext, + Action, AnyView, Div, EventEmitter, Render, Subscription, View, ViewContext, WeakView, + WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -176,226 +177,226 @@ pub struct PanelButtons { workspace: WeakView, } -// impl Dock { -// pub fn new(position: DockPosition) -> Self { -// Self { -// position, -// panel_entries: Default::default(), -// active_panel_index: 0, -// is_open: false, -// } -// } +impl Dock { + // pub fn new(position: DockPosition) -> Self { + // Self { + // position, + // panel_entries: Default::default(), + // active_panel_index: 0, + // is_open: false, + // } + // } -// pub fn position(&self) -> DockPosition { -// self.position -// } + // pub fn position(&self) -> DockPosition { + // self.position + // } -// pub fn is_open(&self) -> bool { -// self.is_open -// } + pub fn is_open(&self) -> bool { + self.is_open + } -// pub fn has_focus(&self, cx: &WindowContext) -> bool { -// self.visible_panel() -// .map_or(false, |panel| panel.has_focus(cx)) -// } + // pub fn has_focus(&self, cx: &WindowContext) -> bool { + // self.visible_panel() + // .map_or(false, |panel| panel.has_focus(cx)) + // } -// pub fn panel(&self) -> Option> { -// self.panel_entries -// .iter() -// .find_map(|entry| entry.panel.as_any().clone().downcast()) -// } + // pub fn panel(&self) -> Option> { + // self.panel_entries + // .iter() + // .find_map(|entry| entry.panel.as_any().clone().downcast()) + // } -// pub fn panel_index_for_type(&self) -> Option { -// self.panel_entries -// .iter() -// .position(|entry| entry.panel.as_any().is::()) -// } + // pub fn panel_index_for_type(&self) -> Option { + // self.panel_entries + // .iter() + // .position(|entry| entry.panel.as_any().is::()) + // } -// pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { -// todo!() -// // self.panel_entries.iter().position(|entry| { -// // let panel = entry.panel.as_any(); -// // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) -// // }) -// } + // pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { + // todo!() + // // self.panel_entries.iter().position(|entry| { + // // let panel = entry.panel.as_any(); + // // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) + // // }) + // } -// pub fn active_panel_index(&self) -> usize { -// self.active_panel_index -// } + // pub fn active_panel_index(&self) -> usize { + // self.active_panel_index + // } -// pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { -// if open != self.is_open { -// self.is_open = open; -// if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { -// active_panel.panel.set_active(open, cx); -// } + // pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { + // if open != self.is_open { + // self.is_open = open; + // if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + // active_panel.panel.set_active(open, cx); + // } -// cx.notify(); -// } -// } + // cx.notify(); + // } + // } -// pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { -// for entry in &mut self.panel_entries { -// if entry.panel.as_any() == panel { -// if zoomed != entry.panel.is_zoomed(cx) { -// entry.panel.set_zoomed(zoomed, cx); -// } -// } else if entry.panel.is_zoomed(cx) { -// entry.panel.set_zoomed(false, cx); -// } -// } + // pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { + // for entry in &mut self.panel_entries { + // if entry.panel.as_any() == panel { + // if zoomed != entry.panel.is_zoomed(cx) { + // entry.panel.set_zoomed(zoomed, cx); + // } + // } else if entry.panel.is_zoomed(cx) { + // entry.panel.set_zoomed(false, cx); + // } + // } -// cx.notify(); -// } + // cx.notify(); + // } -// pub fn zoom_out(&mut self, cx: &mut ViewContext) { -// for entry in &mut self.panel_entries { -// if entry.panel.is_zoomed(cx) { -// entry.panel.set_zoomed(false, cx); -// } -// } -// } + // pub fn zoom_out(&mut self, cx: &mut ViewContext) { + // for entry in &mut self.panel_entries { + // if entry.panel.is_zoomed(cx) { + // entry.panel.set_zoomed(false, cx); + // } + // } + // } -// pub(crate) fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { -// let subscriptions = [ -// cx.observe(&panel, |_, _, cx| cx.notify()), -// cx.subscribe(&panel, |this, panel, event, cx| { -// if T::should_activate_on_event(event) { -// if let Some(ix) = this -// .panel_entries -// .iter() -// .position(|entry| entry.panel.id() == panel.id()) -// { -// this.set_open(true, cx); -// this.activate_panel(ix, cx); -// cx.focus(&panel); -// } -// } else if T::should_close_on_event(event) -// && this.visible_panel().map_or(false, |p| p.id() == panel.id()) -// { -// this.set_open(false, cx); -// } -// }), -// ]; + // pub(crate) fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { + // let subscriptions = [ + // cx.observe(&panel, |_, _, cx| cx.notify()), + // cx.subscribe(&panel, |this, panel, event, cx| { + // if T::should_activate_on_event(event) { + // if let Some(ix) = this + // .panel_entries + // .iter() + // .position(|entry| entry.panel.id() == panel.id()) + // { + // this.set_open(true, cx); + // this.activate_panel(ix, cx); + // cx.focus(&panel); + // } + // } else if T::should_close_on_event(event) + // && this.visible_panel().map_or(false, |p| p.id() == panel.id()) + // { + // this.set_open(false, cx); + // } + // }), + // ]; -// let dock_view_id = cx.view_id(); -// self.panel_entries.push(PanelEntry { -// panel: Arc::new(panel), -// // todo!() -// // context_menu: cx.add_view(|cx| { -// // let mut menu = ContextMenu::new(dock_view_id, cx); -// // menu.set_position_mode(OverlayPositionMode::Local); -// // menu -// // }), -// _subscriptions: subscriptions, -// }); -// cx.notify() -// } + // let dock_view_id = cx.view_id(); + // self.panel_entries.push(PanelEntry { + // panel: Arc::new(panel), + // // todo!() + // // context_menu: cx.add_view(|cx| { + // // let mut menu = ContextMenu::new(dock_view_id, cx); + // // menu.set_position_mode(OverlayPositionMode::Local); + // // menu + // // }), + // _subscriptions: subscriptions, + // }); + // cx.notify() + // } -// pub fn remove_panel(&mut self, panel: &View, cx: &mut ViewContext) { -// if let Some(panel_ix) = self -// .panel_entries -// .iter() -// .position(|entry| entry.panel.id() == panel.id()) -// { -// if panel_ix == self.active_panel_index { -// self.active_panel_index = 0; -// self.set_open(false, cx); -// } else if panel_ix < self.active_panel_index { -// self.active_panel_index -= 1; -// } -// self.panel_entries.remove(panel_ix); -// cx.notify(); -// } -// } + // pub fn remove_panel(&mut self, panel: &View, cx: &mut ViewContext) { + // if let Some(panel_ix) = self + // .panel_entries + // .iter() + // .position(|entry| entry.panel.id() == panel.id()) + // { + // if panel_ix == self.active_panel_index { + // self.active_panel_index = 0; + // self.set_open(false, cx); + // } else if panel_ix < self.active_panel_index { + // self.active_panel_index -= 1; + // } + // self.panel_entries.remove(panel_ix); + // cx.notify(); + // } + // } -// pub fn panels_len(&self) -> usize { -// self.panel_entries.len() -// } + // pub fn panels_len(&self) -> usize { + // self.panel_entries.len() + // } -// pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { -// if panel_ix != self.active_panel_index { -// if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { -// active_panel.panel.set_active(false, cx); -// } + // pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + // if panel_ix != self.active_panel_index { + // if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + // active_panel.panel.set_active(false, cx); + // } -// self.active_panel_index = panel_ix; -// if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { -// active_panel.panel.set_active(true, cx); -// } + // self.active_panel_index = panel_ix; + // if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + // active_panel.panel.set_active(true, cx); + // } -// cx.notify(); -// } -// } + // cx.notify(); + // } + // } -// pub fn visible_panel(&self) -> Option<&Arc> { -// let entry = self.visible_entry()?; -// Some(&entry.panel) -// } + pub fn visible_panel(&self) -> Option<&Arc> { + let entry = self.visible_entry()?; + Some(&entry.panel) + } -// pub fn active_panel(&self) -> Option<&Arc> { -// Some(&self.panel_entries.get(self.active_panel_index)?.panel) -// } + // pub fn active_panel(&self) -> Option<&Arc> { + // Some(&self.panel_entries.get(self.active_panel_index)?.panel) + // } -// fn visible_entry(&self) -> Option<&PanelEntry> { -// if self.is_open { -// self.panel_entries.get(self.active_panel_index) -// } else { -// None -// } -// } + fn visible_entry(&self) -> Option<&PanelEntry> { + if self.is_open { + self.panel_entries.get(self.active_panel_index) + } else { + None + } + } -// pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { -// let entry = self.visible_entry()?; -// if entry.panel.is_zoomed(cx) { -// Some(entry.panel.clone()) -// } else { -// None -// } -// } + // pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { + // let entry = self.visible_entry()?; + // if entry.panel.is_zoomed(cx) { + // Some(entry.panel.clone()) + // } else { + // None + // } + // } -// pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { -// self.panel_entries -// .iter() -// .find(|entry| entry.panel.id() == panel.id()) -// .map(|entry| entry.panel.size(cx)) -// } + // pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { + // self.panel_entries + // .iter() + // .find(|entry| entry.panel.id() == panel.id()) + // .map(|entry| entry.panel.size(cx)) + // } -// pub fn active_panel_size(&self, cx: &WindowContext) -> Option { -// if self.is_open { -// self.panel_entries -// .get(self.active_panel_index) -// .map(|entry| entry.panel.size(cx)) -// } else { -// None -// } -// } + // pub fn active_panel_size(&self, cx: &WindowContext) -> Option { + // if self.is_open { + // self.panel_entries + // .get(self.active_panel_index) + // .map(|entry| entry.panel.size(cx)) + // } else { + // None + // } + // } -// pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { -// if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { -// entry.panel.set_size(size, cx); -// cx.notify(); -// } -// } + // pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { + // if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { + // entry.panel.set_size(size, cx); + // cx.notify(); + // } + // } -// pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { -// todo!() -// if let Some(active_entry) = self.visible_entry() { -// Empty::new() -// .into_any() -// .contained() -// .with_style(self.style(cx)) -// .resizable::( -// self.position.to_resize_handle_side(), -// active_entry.panel.size(cx), -// |_, _, _| {}, -// ) -// .into_any() -// } else { -// Empty::new().into_any() -// } -// } -// } + // pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { + // todo!() + // if let Some(active_entry) = self.visible_entry() { + // Empty::new() + // .into_any() + // .contained() + // .with_style(self.style(cx)) + // .resizable::( + // self.position.to_resize_handle_side(), + // active_entry.panel.size(cx), + // |_, _, _| {}, + // ) + // .into_any() + // } else { + // Empty::new().into_any() + // } + // } +} // todo!() // impl View for Dock { @@ -596,15 +597,23 @@ impl EventEmitter for PanelButtons { // } // } -// impl StatusItemView for PanelButtons { -// fn set_active_pane_item( -// &mut self, -// active_pane_item: Option<&dyn crate::ItemHandle>, -// cx: &mut ViewContext, -// ) { -// todo!() -// } -// } +impl Render for PanelButtons { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + todo!() + } +} + +impl StatusItemView for PanelButtons { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn crate::ItemHandle>, + cx: &mut ViewContext, + ) { + todo!() + } +} #[cfg(any(test, feature = "test-support"))] pub mod test { diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 554a7aadb6..ccf6adfdda 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -691,58 +691,58 @@ pub trait FollowableItemHandle: ItemHandle { fn is_project_item(&self, cx: &AppContext) -> bool; } -// impl FollowableItemHandle for View { -// 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, -// }) -// }) -// } +impl FollowableItemHandle for View { + 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 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 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 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: &Model, -// message: proto::update_view::Variant, -// cx: &mut WindowContext, -// ) -> Task> { -// self.update(cx, |this, cx| this.apply_update_proto(project, message, cx)) -// } + fn apply_update_proto( + &self, + project: &Model, + 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 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) -// } -// } + fn is_project_item(&self, cx: &AppContext) -> bool { + self.read(cx).is_project_item(cx) + } +} // #[cfg(any(test, feature = "test-support"))] // pub mod test { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 9325f58b37..fc74139238 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -178,7 +178,7 @@ pub struct Pane { // tab_context_menu: ViewHandle, // workspace: WeakView, project: Model, - // has_focus: bool, + has_focus: bool, // can_drop: Rc, &WindowContext) -> bool>, // can_split: bool, // render_tab_bar_buttons: Rc) -> AnyElement>, @@ -348,7 +348,7 @@ impl Pane { // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), // workspace, project, - // has_focus: false, + has_focus: false, // can_drop: Rc::new(|_, _| true), // can_split: true, // render_tab_bar_buttons: Rc::new(move |pane, cx| { @@ -415,9 +415,9 @@ impl Pane { // &self.workspace // } - // pub fn has_focus(&self) -> bool { - // self.has_focus - // } + pub fn has_focus(&self) -> bool { + self.has_focus + } // pub fn active_item_index(&self) -> usize { // self.active_item_index @@ -614,9 +614,9 @@ impl Pane { self.items.len() } - // pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { - // self.items.iter() - // } + pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { + self.items.iter() + } // pub fn items_of_type(&self) -> impl '_ + Iterator> { // self.items diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 964194ef22..4d71cf397b 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -2,22 +2,24 @@ use crate::{AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, Result}; use call2::ActiveCall; use collections::HashMap; -use gpui2::{size, AnyElement, AnyView, Bounds, Handle, Model, Pixels, Point, View, ViewContext}; +use gpui2::{point, size, AnyElement, AnyView, Bounds, Model, Pixels, Point, View, ViewContext}; +use parking_lot::Mutex; use project2::Project; use serde::Deserialize; -use std::{cell::RefCell, rc::Rc, sync::Arc}; +use std::sync::Arc; use theme2::Theme; const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; +#[derive(Copy, Clone, PartialEq, Eq)] pub enum Axis { Vertical, Horizontal, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, } @@ -305,18 +307,24 @@ impl Member { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone)] pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, - pub flexes: Rc>>, - pub bounding_boxes: Rc>>>>, + pub flexes: Arc>>, + pub bounding_boxes: Arc>>>>, +} + +impl PartialEq for PaneAxis { + fn eq(&self, other: &Self) -> bool { + todo!() + } } impl PaneAxis { pub fn new(axis: Axis, members: Vec) -> Self { - let flexes = Rc::new(RefCell::new(vec![1.; members.len()])); - let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()])); + let flexes = Arc::new(Mutex::new(vec![1.; members.len()])); + let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()])); Self { axis, members, @@ -329,8 +337,8 @@ impl PaneAxis { let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]); debug_assert!(members.len() == flexes.len()); - let flexes = Rc::new(RefCell::new(flexes)); - let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()])); + let flexes = Arc::new(Mutex::new(flexes)); + let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()])); Self { axis, members, @@ -360,7 +368,7 @@ impl PaneAxis { } self.members.insert(idx, Member::Pane(new_pane.clone())); - *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + *self.flexes.lock() = vec![1.; self.members.len()]; } else { *member = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); @@ -400,12 +408,12 @@ impl PaneAxis { if found_pane { if let Some(idx) = remove_member { self.members.remove(idx); - *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + *self.flexes.lock() = vec![1.; self.members.len()]; } if self.members.len() == 1 { let result = self.members.pop(); - *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + *self.flexes.lock() = vec![1.; self.members.len()]; Ok(result) } else { Ok(None) @@ -431,13 +439,13 @@ impl PaneAxis { } fn bounding_box_for_pane(&self, pane: &View) -> Option> { - debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); + debug_assert!(self.members.len() == self.bounding_boxes.lock().len()); for (idx, member) in self.members.iter().enumerate() { match member { Member::Pane(found) => { if pane == found { - return self.bounding_boxes.borrow()[idx]; + return self.bounding_boxes.lock()[idx]; } } Member::Axis(axis) => { @@ -451,9 +459,9 @@ impl PaneAxis { } fn pane_at_pixel_position(&self, coordinate: Point) -> Option<&View> { - debug_assert!(self.members.len() == self.bounding_boxes.borrow().len()); + debug_assert!(self.members.len() == self.bounding_boxes.lock().len()); - let bounding_boxes = self.bounding_boxes.borrow(); + let bounding_boxes = self.bounding_boxes.lock(); for (idx, member) in self.members.iter().enumerate() { if let Some(coordinates) = bounding_boxes[idx] { @@ -480,7 +488,7 @@ impl PaneAxis { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - debug_assert!(self.members.len() == self.flexes.borrow().len()); + debug_assert!(self.members.len() == self.flexes.lock().len()); todo!() // let mut pane_axis = PaneAxisElement::new( @@ -546,32 +554,32 @@ impl SplitDirection { [Self::Up, Self::Down, Self::Left, Self::Right] } - pub fn edge(&self, rect: Bounds) -> f32 { + pub fn edge(&self, rect: Bounds) -> Pixels { match self { - Self::Up => rect.min_y(), - Self::Down => rect.max_y(), - Self::Left => rect.min_x(), - Self::Right => rect.max_x(), + Self::Up => rect.origin.y, + Self::Down => rect.lower_left().y, + Self::Left => rect.lower_left().x, + Self::Right => rect.lower_right().x, } } pub fn along_edge(&self, bounds: Bounds, length: Pixels) -> Bounds { match self { Self::Up => Bounds { - origin: bounds.origin(), - size: size(bounds.width(), length), + origin: bounds.origin, + size: size(bounds.size.width, length), }, Self::Down => Bounds { - origin: size(bounds.min_x(), bounds.max_y() - length), - size: size(bounds.width(), length), + origin: point(bounds.lower_left().x, bounds.lower_left().y - length), + size: size(bounds.size.width, length), }, Self::Left => Bounds { - origin: bounds.origin(), - size: size(length, bounds.height()), + origin: bounds.origin, + size: size(length, bounds.size.height), }, Self::Right => Bounds { - origin: size(bounds.max_x() - length, bounds.min_y()), - size: size(length, bounds.height()), + origin: point(bounds.lower_right().x - length, bounds.lower_left().y), + size: size(length, bounds.size.height), }, } } diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 4323e6dae0..6e6cb8e55c 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -55,7 +55,7 @@ impl Column for WorkspaceLocation { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, @@ -127,7 +127,7 @@ impl Bind for DockData { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] pub enum SerializedPaneGroup { Group { axis: Axis, diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index 591fab5cd9..7b911b75d0 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -1,6 +1,6 @@ use std::{any::Any, sync::Arc}; -use gpui2::{AppContext, Subscription, Task, View, ViewContext, WindowContext}; +use gpui2::{AnyView, AppContext, Subscription, Task, View, ViewContext, WindowContext}; use project2::search::SearchQuery; use crate::{ @@ -95,7 +95,7 @@ pub trait SearchableItemHandle: ItemHandle { fn subscribe_to_search_events( &self, cx: &mut WindowContext, - handler: Box, + handler: Box, ) -> Subscription; fn clear_matches(&self, cx: &mut WindowContext); fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext); @@ -130,7 +130,8 @@ pub trait SearchableItemHandle: ItemHandle { impl SearchableItemHandle for View { fn downgrade(&self) -> Box { - Box::new(self.downgrade()) + // Box::new(self.downgrade()) + todo!() } fn boxed_clone(&self) -> Box { @@ -144,7 +145,7 @@ impl SearchableItemHandle for View { fn subscribe_to_search_events( &self, cx: &mut WindowContext, - handler: Box, + handler: Box, ) -> Subscription { cx.subscribe(self, move |handle, event, cx| { let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx)); @@ -198,7 +199,7 @@ impl SearchableItemHandle for View { cx: &mut WindowContext, ) -> Task>> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); - cx.foreground().spawn(async { + cx.spawn_on_main(|cx| async { let matches = matches.await; matches .into_iter() @@ -231,23 +232,21 @@ fn downcast_matches(matches: &Vec>) -> Vec> for AnyViewHandle { -// fn from(this: Box) -> Self { -// this.as_any().clone() -// } -// } +impl From> for AnyView { + fn from(this: Box) -> Self { + this.to_any().clone() + } +} -// todo!() -// impl From<&Box> for AnyViewHandle { -// fn from(this: &Box) -> Self { -// this.as_any().clone() -// } -// } +impl From<&Box> for AnyView { + fn from(this: &Box) -> Self { + this.to_any().clone() + } +} impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { - self.id() == other.id() && self.window() == other.window() + self.id() == other.id() } } @@ -256,24 +255,23 @@ impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; - // todo!() - // fn into_any(self) -> AnyWeakViewHandle; + // fn into_any(self) -> AnyWeakView; } // todo!() -// impl WeakSearchableItemHandle for WeakViewHandle { +// impl WeakSearchableItemHandle for WeakView { // fn upgrade(&self, cx: &AppContext) -> Option> { // Some(Box::new(self.upgrade(cx)?)) // } -// fn into_any(self) -> AnyWeakViewHandle { -// self.into_any() -// } +// // fn into_any(self) -> AnyView { +// // self.into_any() +// // } // } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { - self.id() == other.id() && self.window() == other.window() + self.id() == other.id() } } @@ -281,6 +279,6 @@ impl Eq for Box {} impl std::hash::Hash for Box { fn hash(&self, state: &mut H) { - (self.id(), self.window().id()).hash(state) + self.id().hash(state) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3e1578f779..fda2b189ff 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -11,21 +11,25 @@ mod toolbar; mod workspace_settings; use anyhow::{anyhow, Result}; +use call2::ActiveCall; use client2::{ proto::{self, PeerId}, Client, UserStore, }; use collections::{HashMap, HashSet}; +use dock::Dock; use futures::{channel::oneshot, FutureExt}; use gpui2::{ - AnyModel, AnyView, AppContext, AsyncAppContext, DisplayId, MainThread, Model, Task, View, - ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowHandle, WindowOptions, + AnyModel, AnyView, AppContext, AsyncAppContext, DisplayId, EventEmitter, MainThread, Model, + Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; use node_runtime::NodeRuntime; pub use pane::*; pub use pane_group::*; +use persistence::model::{ItemId, WorkspaceLocation}; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; use std::{ any::TypeId, @@ -37,7 +41,8 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use crate::persistence::model::{ - DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, + SerializedWorkspace, }; // lazy_static! { @@ -386,14 +391,13 @@ type ItemDeserializers = HashMap< ) -> Task>>, >; pub fn register_deserializable_item(cx: &mut AppContext) { - cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| { + cx.update_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.spawn_on_main(|cx| async { Ok(Box::new(task.await?) as Box<_>) }) }, ); } @@ -426,6 +430,7 @@ struct Follower { peer_id: PeerId, } +// todo!() // impl AppState { // #[cfg(any(test, feature = "test-support"))] // pub fn test(cx: &mut AppContext) -> Arc { @@ -476,7 +481,7 @@ impl DelayedDebouncedEditAction { fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) where - F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, + F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, { if let Some(channel) = self.cancel_channel.take() { _ = channel.send(()); @@ -517,7 +522,7 @@ pub struct Workspace { // modal: Option, // zoomed: Option, // zoomed_position: Option, - // center: PaneGroup, + center: PaneGroup, left_dock: View, bottom_dock: View, right_dock: View, @@ -533,9 +538,9 @@ pub struct Workspace { follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, // window_edited: bool, - // active_call: Option<(ModelHandle, Vec)>, + active_call: Option<(Model, Vec)>, // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, - // database_id: WorkspaceId, + database_id: WorkspaceId, app_state: Arc, // subscriptions: Vec, // _apply_leader_updates: Task>, @@ -1925,19 +1930,21 @@ impl Workspace { // } fn add_pane(&mut self, cx: &mut ViewContext) -> View { - let pane = cx.build_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 + todo!() + // let pane = cx.build_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()); + // todo!() + // cx.focus(&pane); + // cx.emit(Event::PaneAdded(pane.clone())); + // pane } // pub fn add_item_to_center( @@ -2083,19 +2090,19 @@ impl Workspace { ) -> Task< Result<( ProjectEntryId, - impl 'static + FnOnce(&mut ViewContext) -> Box, + impl 'static + Send + FnOnce(&mut ViewContext) -> Box, )>, > { let project = self.project().clone(); let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); - cx.spawn(|_, mut cx| async move { + cx.spawn(|_, cx| async move { let (project_entry_id, project_item) = project_item.await?; let build_item = cx.update(|cx| { cx.default_global::() - .get(&project_item.model_type()) + .get(&project_item.type_id()) .ok_or_else(|| anyhow!("no item builder for project item")) .cloned() - })?; + })??; let build_item = move |cx: &mut ViewContext| build_item(project, project_item, cx); Ok((project_entry_id, build_item)) @@ -3012,14 +3019,14 @@ impl Workspace { &self, project_only: bool, update: proto::update_followers::Variant, - cx: &AppContext, + cx: &mut 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| { + self.app_state().workspace_store.update(cx, |store, cx| { store.update_followers(project_id, update, cx) }) } @@ -3141,9 +3148,9 @@ impl Workspace { // } // } - // fn active_call(&self) -> Option<&ModelHandle> { - // self.active_call.as_ref().map(|(call, _)| call) - // } + fn active_call(&self) -> Option<&Model> { + self.active_call.as_ref().map(|(call, _)| call) + } // fn on_active_call_event( // &mut self, @@ -3164,21 +3171,21 @@ impl Workspace { // self.database_id // } - // fn location(&self, cx: &AppContext) -> Option { - // let project = self.project().read(cx); + fn location(&self, cx: &AppContext) -> Option { + let project = self.project().read(cx); - // if project.is_local() { - // Some( - // project - // .visible_worktrees(cx) - // .map(|worktree| worktree.read(cx).abs_path()) - // .collect::>() - // .into(), - // ) - // } else { - // None - // } - // } + if project.is_local() { + Some( + project + .visible_worktrees(cx) + .map(|worktree| worktree.read(cx).abs_path()) + .collect::>() + .into(), + ) + } else { + None + } + } // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { // match member { @@ -3193,14 +3200,17 @@ impl Workspace { // } // } - // fn force_remove_pane(&mut self, pane: &View, cx: &mut ViewContext) { - // self.panes.retain(|p| p != pane); - // cx.focus(self.panes.last().unwrap()); - // if self.last_active_center_pane == Some(pane.downgrade()) { - // self.last_active_center_pane = None; - // } - // cx.notify(); - // } + fn force_remove_pane(&mut self, pane: &View, cx: &mut ViewContext) { + self.panes.retain(|p| p != pane); + if true { + todo!() + // cx.focus(self.panes.last().unwrap()); + } + if self.last_active_center_pane == Some(pane.downgrade()) { + self.last_active_center_pane = None; + } + cx.notify(); + } // fn schedule_serialize(&mut self, cx: &mut ViewContext) { // self._schedule_serialize = Some(cx.spawn(|this, cx| async move { @@ -3248,7 +3258,7 @@ impl Workspace { .iter() .map(|member| build_serialized_pane_group(member, cx)) .collect::>(), - flexes: Some(flexes.borrow().clone()), + flexes: Some(flexes.lock().clone()), }, Member::Pane(pane_handle) => { SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) @@ -3260,10 +3270,11 @@ impl Workspace { 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(), - ) + todo!() + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) }); let left_dock_zoom = left_dock .visible_panel() @@ -3273,10 +3284,11 @@ impl Workspace { 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(), - ) + todo!() + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) }); let right_dock_zoom = right_dock .visible_panel() @@ -3286,10 +3298,11 @@ impl Workspace { 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(), - ) + todo!() + // Some( + // cx.view_ui_name(panel.as_any().window(), panel.id())? + // .to_string(), + // ) }); let bottom_dock_zoom = bottom_dock .visible_panel() @@ -3332,8 +3345,7 @@ impl Workspace { docks, }; - cx.background() - .spawn(persistence::DB.save_workspace(serialized_workspace)) + cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace)) .detach(); } } @@ -3719,6 +3731,11 @@ impl Workspace { // .log_err(); // } +impl EventEmitter for Workspace { + type Event = Event; +} + +// todo!() // impl Entity for Workspace { // type Event = Event; @@ -3869,54 +3886,55 @@ impl Workspace { // } // } -// impl WorkspaceStore { -// pub fn new(client: Arc, cx: &mut ModelContext) -> Self { -// Self { -// workspaces: Default::default(), -// followers: Default::default(), -// _subscriptions: vec![ -// client.add_request_handler(cx.handle(), Self::handle_follow), -// client.add_message_handler(cx.handle(), Self::handle_unfollow), -// client.add_message_handler(cx.handle(), Self::handle_update_followers), -// ], -// client, -// } -// } +impl WorkspaceStore { + // pub fn new(client: Arc, cx: &mut ModelContext) -> Self { + // Self { + // workspaces: Default::default(), + // followers: Default::default(), + // _subscriptions: vec![ + // client.add_request_handler(cx.handle(), Self::handle_follow), + // client.add_message_handler(cx.handle(), Self::handle_unfollow), + // client.add_message_handler(cx.handle(), Self::handle_update_followers), + // ], + // client, + // } + // } -// pub fn update_followers( -// &self, -// project_id: Option, -// update: proto::update_followers::Variant, -// cx: &AppContext, -// ) -> Option<()> { -// if !cx.has_global::>() { -// return None; -// } + pub fn update_followers( + &self, + project_id: Option, + update: proto::update_followers::Variant, + cx: &AppContext, + ) -> Option<()> { + if !cx.has_global::>() { + return None; + } -// let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); -// let follower_ids: Vec<_> = self -// .followers -// .iter() -// .filter_map(|follower| { -// if follower.project_id == project_id || project_id.is_none() { -// Some(follower.peer_id.into()) -// } else { -// None -// } -// }) -// .collect(); -// if follower_ids.is_empty() { -// return None; -// } -// self.client -// .send(proto::UpdateFollowers { -// room_id, -// project_id, -// follower_ids, -// variant: Some(update), -// }) -// .log_err() -// } + let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); + let follower_ids: Vec<_> = self + .followers + .iter() + .filter_map(|follower| { + if follower.project_id == project_id || project_id.is_none() { + Some(follower.peer_id.into()) + } else { + None + } + }) + .collect(); + if follower_ids.is_empty() { + return None; + } + self.client + .send(proto::UpdateFollowers { + room_id, + project_id, + follower_ids, + variant: Some(update), + }) + .log_err() + } +} // async fn handle_follow( // this: ModelHandle, @@ -4303,13 +4321,14 @@ pub fn open_paths( .await; if let Some(existing) = existing { - Ok(( - existing.clone(), - cx.update_window_root(&existing, |workspace, cx| { - workspace.open_paths(abs_paths, true, cx) - })? - .await, - )) + // Ok(( + // existing.clone(), + // cx.update_window_root(&existing, |workspace, cx| { + // workspace.open_paths(abs_paths, true, cx) + // })? + // .await, + // )) + todo!() } else { todo!() // Ok(cx From e315e1bb6c6f90d54ccbfdb952cfd7161d04fbdc Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 10:50:28 -0400 Subject: [PATCH 19/66] small window change --- crates/gpui2/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e8c45f0191..f1d4ff76ac 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1620,7 +1620,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { ) -> Subscription where V2: 'static, - V: Any + Send, + V: 'static + Send, E: Entity, { let view = self.view(); From 5550e80c4e078548e62e7a948edbf55d20a7fc43 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 11:14:43 -0400 Subject: [PATCH 20/66] workspace2 is compiling Co-Authored-By: Kirill --- crates/gpui2/src/platform.rs | 63 +++++++- crates/workspace2/src/item.rs | 8 +- crates/workspace2/src/pane_group.rs | 34 ++++- crates/workspace2/src/persistence/model.rs | 4 +- crates/workspace2/src/workspace2.rs | 169 ++++++++++----------- 5 files changed, 184 insertions(+), 94 deletions(-) diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index cacb1922f6..d95a20d1f7 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -9,11 +9,13 @@ use crate::{ GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, }; -use anyhow::anyhow; +use anyhow::{anyhow, bail}; use async_task::Runnable; use futures::channel::oneshot; use seahash::SeaHasher; use serde::{Deserialize, Serialize}; +use sqlez::bindable::{Bind, Column, StaticColumnCount}; +use sqlez::statement::Statement; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::time::Duration; @@ -368,6 +370,65 @@ pub enum WindowBounds { Fixed(Bounds), } +impl StaticColumnCount for WindowBounds { + fn column_count() -> usize { + 5 + } +} + +impl Bind for WindowBounds { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let (region, next_index) = match self { + WindowBounds::Fullscreen => { + let next_index = statement.bind(&"Fullscreen", start_index)?; + (None, next_index) + } + WindowBounds::Maximized => { + let next_index = statement.bind(&"Maximized", start_index)?; + (None, next_index) + } + WindowBounds::Fixed(region) => { + let next_index = statement.bind(&"Fixed", start_index)?; + (Some(*region), next_index) + } + }; + + todo!() + // statement.bind( + // ®ion.map(|region| { + // ( + // region.min_x(), + // region.min_y(), + // region.width(), + // region.height(), + // ) + // }), + // next_index, + // ) + } +} + +impl Column for WindowBounds { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (window_state, next_index) = String::column(statement, start_index)?; + let bounds = match window_state.as_str() { + "Fullscreen" => WindowBounds::Fullscreen, + "Maximized" => WindowBounds::Maximized, + "Fixed" => { + // let ((x, y, width, height), _) = Column::column(statement, next_index)?; + // WindowBounds::Fixed(RectF::new( + // Vector2F::new(x, y), + // Vector2F::new(width, height), + // )) + todo!() + } + _ => bail!("Window State did not have a valid string"), + }; + + Ok((bounds, next_index + 4)) + } +} + #[derive(Copy, Clone, Debug)] pub enum WindowAppearance { Light, diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index ccf6adfdda..ce4b7b0901 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -253,9 +253,9 @@ pub trait ItemHandle: 'static + Send { fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; fn on_release( - &self, + &mut self, cx: &mut AppContext, - callback: Box, + callback: Box, ) -> gpui2::Subscription; fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; @@ -571,9 +571,9 @@ impl ItemHandle for View { } fn on_release( - &self, + &mut self, cx: &mut AppContext, - callback: Box, + mut callback: Box, ) -> gpui2::Subscription { cx.observe_release(self, move |_, cx| callback(cx)) } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 4d71cf397b..d537a1d2fb 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,7 +1,11 @@ use crate::{AppState, FollowerState, Pane, Workspace}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use call2::ActiveCall; use collections::HashMap; +use db2::sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; use gpui2::{point, size, AnyElement, AnyView, Bounds, Model, Pixels, Point, View, ViewContext}; use parking_lot::Mutex; use project2::Project; @@ -13,12 +17,38 @@ const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Axis { Vertical, Horizontal, } +impl StaticColumnCount for Axis {} +impl Bind for Axis { + fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { + match self { + Axis::Horizontal => "Horizontal", + Axis::Vertical => "Vertical", + } + .bind(statement, start_index) + } +} + +impl Column for Axis { + fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { + String::column(statement, start_index).and_then(|(axis_text, next_index)| { + Ok(( + match axis_text.as_str() { + "Horizontal" => Axis::Horizontal, + "Vertical" => Axis::Vertical, + _ => bail!("Stored serialized item kind is incorrect"), + }, + next_index, + )) + }) + } +} + #[derive(Clone, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 6e6cb8e55c..90b8a9b3be 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -7,7 +7,7 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{AsyncAppContext, AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; +use gpui2::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; use project2::Project; use std::{ path::{Path, PathBuf}, @@ -232,7 +232,7 @@ impl SerializedPane { pane: &WeakView, workspace_id: WorkspaceId, workspace: &WeakView, - cx: &mut AsyncAppContext, + cx: &mut AsyncWindowContext, ) -> Result>>> { let mut items = Vec::new(); let mut active_item_index = None; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index fda2b189ff..52a9971bc5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -491,7 +491,7 @@ impl DelayedDebouncedEditAction { self.cancel_channel = Some(sender); let previous_task = self.task.take(); - self.task = Some(cx.spawn(|workspace, mut cx| async move { + self.task = Some(cx.spawn(move |workspace, mut cx| async move { let mut timer = cx.executor().timer(delay).fuse(); if let Some(previous_task) = previous_task { previous_task.await; @@ -765,47 +765,47 @@ impl Workspace { // } // } - // fn new_local( - // abs_paths: Vec, - // app_state: Arc, - // requesting_window: Option>, - // cx: &mut AppContext, - // ) -> Task<( - // WeakView, - // Vec, anyhow::Error>>>, - // )> { - // let project_handle = Project::local( - // app_state.client.clone(), - // app_state.node_runtime.clone(), - // app_state.user_store.clone(), - // app_state.languages.clone(), - // app_state.fs.clone(), - // cx, - // ); + // fn new_local( + // abs_paths: Vec, + // app_state: Arc, + // requesting_window: Option>, + // cx: &mut AppContext, + // ) -> Task<( + // WeakView, + // Vec, anyhow::Error>>>, + // )> { + // let project_handle = Project::local( + // app_state.client.clone(), + // app_state.node_runtime.clone(), + // app_state.user_store.clone(), + // app_state.languages.clone(), + // app_state.fs.clone(), + // cx, + // ); - // cx.spawn(|mut cx| async move { - // let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + // 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); + // let paths_to_open = Arc::new(abs_paths); - // // Get project paths for all of the abs_paths - // let mut worktree_roots: HashSet> = Default::default(); - // let mut project_paths: Vec<(PathBuf, Option)> = - // Vec::with_capacity(paths_to_open.len()); - // for path in paths_to_open.iter().cloned() { - // if let Some((worktree, project_entry)) = cx - // .update(|cx| { - // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) - // }) - // .await - // .log_err() - // { - // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); - // project_paths.push((path, Some(project_entry))); - // } else { - // project_paths.push((path, None)); - // } + // // Get project paths for all of the abs_paths + // let mut worktree_roots: HashSet> = Default::default(); + // let mut project_paths: Vec<(PathBuf, Option)> = + // Vec::with_capacity(paths_to_open.len()); + // for path in paths_to_open.iter().cloned() { + // if let Some((worktree, project_entry)) = cx + // .update(|cx| { + // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) + // }) + // .await + // .log_err() + // { + // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); + // project_paths.push((path, Some(project_entry))); + // } else { + // project_paths.push((path, None)); // } + // } // let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { // serialized_workspace.id @@ -1470,13 +1470,13 @@ impl Workspace { visible: bool, cx: &mut ViewContext, ) -> Task, anyhow::Error>>>> { - log::info!("open paths {:?}", abs_paths); + 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 { + cx.spawn(move |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 @@ -1495,45 +1495,41 @@ impl Workspace { }; 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, - ))); - }) - } + let abs_path = abs_path.clone(); + let fs = fs.clone(); + let task = cx.spawn(move |mut cx| 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()?; - None - } + .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); @@ -1572,7 +1568,7 @@ impl Workspace { let entry = project.update(cx, |project, cx| { project.find_or_create_local_worktree(abs_path, visible, cx) }); - cx.spawn(|cx| async move { + cx.spawn(|mut cx| async move { let (worktree, path) = entry.await?; let worktree_id = worktree.update(&mut cx, |t, _| t.id())?; Ok(( @@ -2043,7 +2039,7 @@ impl Workspace { }); let task = self.load_path(path.into(), cx); - cx.spawn(|_, mut cx| async move { + cx.spawn(move |_, 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) @@ -3220,7 +3216,7 @@ impl Workspace { // })); // } - fn serialize_workspace(&self, cx: &ViewContext) { + fn serialize_workspace(&self, cx: &mut ViewContext) { fn serialize_pane_handle(pane_handle: &View, cx: &AppContext) -> SerializedPane { let (items, active) = { let pane = pane_handle.read(cx); @@ -3266,7 +3262,10 @@ impl Workspace { } } - fn build_serialized_docks(this: &Workspace, cx: &ViewContext) -> DockStructure { + fn build_serialized_docks( + this: &Workspace, + cx: &mut ViewContext, + ) -> DockStructure { let left_dock = this.left_dock.read(cx); let left_visible = left_dock.is_open(); let left_active_panel = left_dock.visible_panel().and_then(|panel| { @@ -4313,9 +4312,9 @@ pub fn open_paths( > { let app_state = app_state.clone(); let abs_paths = abs_paths.to_vec(); - cx.spawn(|mut cx| async move { + cx.spawn(move |mut cx| async move { // Open paths in existing workspace if possible - let existing = activate_workspace_for_project(&mut cx, |project, cx| { + let existing = activate_workspace_for_project(&mut cx, move |project, cx| { project.contains_paths(&abs_paths, cx) }) .await; @@ -4330,12 +4329,12 @@ pub fn open_paths( // )) todo!() } else { - todo!() // Ok(cx // .update(|cx| { // Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) // }) // .await) + todo!() } }) } From 68a1c7ce4cbf5bbe4e16d4811cd2a605c7559117 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 11:32:56 -0400 Subject: [PATCH 21/66] wip --- crates/gpui2/src/app.rs | 6 +- crates/gpui2/src/platform.rs | 10 +- crates/gpui2/src/window.rs | 2 +- crates/workspace2/src/item.rs | 4 +- crates/zed2/src/zed2.rs | 199 +++++++++++++++++----------------- 5 files changed, 110 insertions(+), 111 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 60c1c12bed..1fc9b9a5f1 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -120,7 +120,7 @@ type FrameCallback = Box; type Handler = Box bool + Send + 'static>; type Listener = Box bool + Send + 'static>; type QuitHandler = Box BoxFuture<'static, ()> + Send + 'static>; -type ReleaseListener = Box; +type ReleaseListener = Box; pub struct AppContext { this: Weak>, @@ -408,7 +408,7 @@ impl AppContext { for (entity_id, mut entity) in dropped { self.observers.remove(&entity_id); self.event_listeners.remove(&entity_id); - for mut release_callback in self.release_listeners.remove(&entity_id) { + for release_callback in self.release_listeners.remove(&entity_id) { release_callback(&mut entity, self); } } @@ -697,7 +697,7 @@ impl AppContext { pub fn observe_release( &mut self, handle: &E, - mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static, + on_release: impl FnOnce(&mut T, &mut AppContext) + Send + 'static, ) -> Subscription where E: Entity, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index d95a20d1f7..295a89a190 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -393,18 +393,18 @@ impl Bind for WindowBounds { } }; - todo!() // statement.bind( // ®ion.map(|region| { // ( - // region.min_x(), - // region.min_y(), - // region.width(), - // region.height(), + // region.origin.x, + // region.origin.y, + // region.size.width, + // region.size.height, // ) // }), // next_index, // ) + todo!() } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f1d4ff76ac..2d4e90f719 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1675,7 +1675,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn on_release( &mut self, - mut on_release: impl FnMut(&mut V, &mut WindowContext) + Send + 'static, + on_release: impl FnOnce(&mut V, &mut WindowContext) + Send + 'static, ) -> Subscription { let window_handle = self.window.handle; self.app.release_listeners.insert( diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index ce4b7b0901..20250f6a71 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -255,7 +255,7 @@ pub trait ItemHandle: 'static + Send { fn on_release( &mut self, cx: &mut AppContext, - callback: Box, + callback: Box, ) -> gpui2::Subscription; fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; @@ -573,7 +573,7 @@ impl ItemHandle for View { fn on_release( &mut self, cx: &mut AppContext, - mut callback: Box, + callback: Box, ) -> gpui2::Subscription { cx.observe_release(self, move |_, cx| callback(cx)) } diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index e9f6ad0c10..729102046f 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -4,9 +4,8 @@ mod only_instance; mod open_listener; pub use assets::*; -use client2::{Client, UserStore}; use collections::HashMap; -use gpui2::{AsyncAppContext, Model}; +use gpui2::{AsyncAppContext, Point}; pub use only_instance::*; pub use open_listener::*; @@ -21,6 +20,7 @@ use futures::{ }; use std::{path::Path, sync::Arc, thread, time::Duration}; use util::{paths::PathLikeWithPosition, ResultExt}; +use workspace2::AppState; pub fn connect_to_cli( server_name: &str, @@ -51,11 +51,6 @@ pub fn connect_to_cli( Ok((async_request_rx, response_tx)) } -pub struct AppState { - pub client: Arc, - pub user_store: Model, -} - pub async fn handle_cli_connection( (mut requests, responses): (mpsc::Receiver, IpcSender), app_state: Arc, @@ -96,118 +91,122 @@ pub async fn handle_cli_connection( } Some(path) }) - .collect() + .collect::>() }; let mut errored = false; - match cx + if let Some(open_paths_task) = cx .update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) - .await + .log_err() { - Ok((workspace, items)) => { - let mut item_release_futures = Vec::new(); + match open_paths_task.await { + Ok((workspace, items)) => { + let mut item_release_futures = Vec::new(); - for (item, path) in items.into_iter().zip(&paths) { - match item { - Some(Ok(item)) => { - if let Some(point) = caret_positions.remove(path) { - todo!() - // if let Some(active_editor) = item.downcast::() { - // active_editor - // .downgrade() - // .update(&mut cx, |editor, cx| { - // let snapshot = - // editor.snapshot(cx).display_snapshot; - // let point = snapshot - // .buffer_snapshot - // .clip_point(point, Bias::Left); - // editor.change_selections( - // Some(Autoscroll::center()), - // cx, - // |s| s.select_ranges([point..point]), - // ); - // }) - // .log_err(); - // } - } + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(mut item)) => { + if let Some(point) = caret_positions.remove(path) { + todo!() + // if let Some(active_editor) = item.downcast::() { + // active_editor + // .downgrade() + // .update(&mut cx, |editor, cx| { + // let snapshot = + // editor.snapshot(cx).display_snapshot; + // let point = snapshot + // .buffer_snapshot + // .clip_point(point, Bias::Left); + // editor.change_selections( + // Some(Autoscroll::center()), + // cx, + // |s| s.select_ranges([point..point]), + // ); + // }) + // .log_err(); + // } + } - let released = oneshot::channel(); - cx.update(|cx| { - item.on_release( - cx, - Box::new(move |_| { - let _ = released.0.send(()); - }), - ) - .detach(); - }); - item_release_futures.push(released.1); - } - Some(Err(err)) => { - responses - .send(CliResponse::Stderr { - message: format!("error opening {:?}: {}", path, err), - }) - .log_err(); - errored = true; - } - None => {} - } - } - - if wait { - let executor = cx.executor(); - let wait = async move { - if paths.is_empty() { - let (done_tx, done_rx) = oneshot::channel(); - if let Some(workspace) = workspace.upgrade(&cx) { - let _subscription = cx.update(|cx| { - cx.observe_release(&workspace, move |_, _| { - let _ = done_tx.send(()); - }) + let released = oneshot::channel(); + cx.update(move |cx| { + item.on_release( + cx, + Box::new(move |_| { + let _ = released.0.send(()); + }), + ) + .detach(); }); + item_release_futures.push(released.1); + } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!( + "error opening {:?}: {}", + path, err + ), + }) + .log_err(); + errored = true; + } + None => {} + } + } + + if wait { + let executor = cx.executor().clone(); + let wait = async move { + if paths.is_empty() { + let (done_tx, done_rx) = oneshot::channel(); + let _subscription = + cx.update_window_root(&workspace, move |_, cx| { + cx.on_release(|_, _| { + let _ = done_tx.send(()); + }) + }); drop(workspace); let _ = done_rx.await; - } - } else { - let _ = - futures::future::try_join_all(item_release_futures).await; - }; - } - .fuse(); - futures::pin_mut!(wait); + } else { + let _ = futures::future::try_join_all(item_release_futures) + .await; + }; + } + .fuse(); + futures::pin_mut!(wait); - loop { - // Repeatedly check if CLI is still open to avoid wasting resources - // waiting for files or workspaces to close. - let mut timer = executor.timer(Duration::from_secs(1)).fuse(); - futures::select_biased! { - _ = wait => break, - _ = timer => { - if responses.send(CliResponse::Ping).is_err() { - break; + loop { + // Repeatedly check if CLI is still open to avoid wasting resources + // waiting for files or workspaces to close. + let mut timer = executor.timer(Duration::from_secs(1)).fuse(); + futures::select_biased! { + _ = wait => break, + _ = timer => { + if responses.send(CliResponse::Ping).is_err() { + break; + } } } } } } + Err(error) => { + errored = true; + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", paths, error), + }) + .log_err(); + } } - Err(error) => { - errored = true; - responses - .send(CliResponse::Stderr { - message: format!("error opening {:?}: {}", paths, error), - }) - .log_err(); - } - } - responses - .send(CliResponse::Exit { - status: i32::from(errored), - }) - .log_err(); + responses + .send(CliResponse::Exit { + status: i32::from(errored), + }) + .log_err(); + } } } } From fed391fe6b9870c36f71ac5e293abbd75bb4dca0 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 12:00:45 -0400 Subject: [PATCH 22/66] wip --- crates/workspace2/src/workspace2.rs | 249 ++++++++++++++-------------- crates/zed2/src/main.rs | 50 +++--- crates/zed2/src/zed2.rs | 30 +++- 3 files changed, 181 insertions(+), 148 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 52a9971bc5..a5d73b2904 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -14,15 +14,18 @@ use anyhow::{anyhow, Result}; use call2::ActiveCall; use client2::{ proto::{self, PeerId}, - Client, UserStore, + Client, TypedEnvelope, UserStore, }; use collections::{HashMap, HashSet}; use dock::Dock; -use futures::{channel::oneshot, FutureExt}; +use futures::{ + channel::{mpsc, oneshot}, + FutureExt, +}; use gpui2::{ - AnyModel, AnyView, AppContext, AsyncAppContext, DisplayId, EventEmitter, MainThread, Model, - Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, - WindowHandle, WindowOptions, + AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, DisplayId, EventEmitter, + MainThread, Model, ModelContext, Subscription, Task, View, ViewContext, VisualContext, + WeakModel, WeakView, WindowBounds, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; @@ -418,7 +421,7 @@ pub struct AppState { } pub struct WorkspaceStore { - workspaces: HashSet>, + workspaces: HashSet>, followers: Vec, client: Arc, _subscriptions: Vec, @@ -539,7 +542,7 @@ pub struct Workspace { last_leaders_by_pane: HashMap, PeerId>, // window_edited: bool, active_call: Option<(Model, Vec)>, - // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, + leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, // subscriptions: Vec, @@ -2853,16 +2856,16 @@ impl Workspace { // } // } - // fn handle_update_followers( - // &mut self, - // leader_id: PeerId, - // message: proto::UpdateFollowers, - // _cx: &mut ViewContext, - // ) { - // self.leader_updates_tx - // .unbounded_send((leader_id, message)) - // .ok(); - // } + fn handle_update_followers( + &mut self, + leader_id: PeerId, + message: proto::UpdateFollowers, + _cx: &mut ViewContext, + ) { + self.leader_updates_tx + .unbounded_send((leader_id, message)) + .ok(); + } // async fn process_leader_update( // this: &WeakView, @@ -3886,18 +3889,19 @@ impl EventEmitter for Workspace { // } impl WorkspaceStore { - // pub fn new(client: Arc, cx: &mut ModelContext) -> Self { - // Self { - // workspaces: Default::default(), - // followers: Default::default(), - // _subscriptions: vec![ - // client.add_request_handler(cx.handle(), Self::handle_follow), - // client.add_message_handler(cx.handle(), Self::handle_unfollow), - // client.add_message_handler(cx.handle(), Self::handle_update_followers), - // ], - // client, - // } - // } + pub fn new(client: Arc, cx: &mut ModelContext) -> Self { + // Self { + // workspaces: Default::default(), + // followers: Default::default(), + // _subscriptions: vec![ + // client.add_request_handler(cx.weak_model(), Self::handle_follow), + // client.add_message_handler(cx.weak_model(), Self::handle_unfollow), + // client.add_message_handler(cx.weak_model(), Self::handle_update_followers), + // ], + // client, + // } + todo!() + } pub fn update_followers( &self, @@ -3933,100 +3937,101 @@ impl WorkspaceStore { }) .log_err() } + + // async fn handle_follow( + // this: Model, + // envelope: TypedEnvelope, + // _: Arc, + // mut cx: AsyncAppContext, + // ) -> Result { + // this.update(&mut cx, |this, cx| { + // let follower = Follower { + // project_id: envelope.payload.project_id, + // peer_id: envelope.original_sender_id()?, + // }; + // let active_project = ActiveCall::global(cx) + // .read(cx) + // .location() + // .map(|project| project.id()); + + // let mut response = proto::FollowResponse::default(); + // for workspace in &this.workspaces { + // let Some(workspace) = workspace.upgrade(cx) else { + // continue; + // }; + + // workspace.update(cx.as_mut(), |workspace, cx| { + // let handler_response = workspace.handle_follow(follower.project_id, cx); + // if response.views.is_empty() { + // response.views = handler_response.views; + // } else { + // response.views.extend_from_slice(&handler_response.views); + // } + + // if let Some(active_view_id) = handler_response.active_view_id.clone() { + // if response.active_view_id.is_none() + // || Some(workspace.project.id()) == active_project + // { + // response.active_view_id = Some(active_view_id); + // } + // } + // }); + // } + + // if let Err(ix) = this.followers.binary_search(&follower) { + // this.followers.insert(ix, follower); + // } + + // Ok(response) + // }) + // } + + async fn handle_unfollow( + model: Model, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result<()> { + model.update(&mut cx, |this, _| { + let follower = Follower { + project_id: envelope.payload.project_id, + peer_id: envelope.original_sender_id()?, + }; + if let Ok(ix) = this.followers.binary_search(&follower) { + this.followers.remove(ix); + } + Ok(()) + })? + } + + async fn handle_update_followers( + this: Model, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncWindowContext, + ) -> Result<()> { + // let leader_id = envelope.original_sender_id()?; + // let update = envelope.payload; + + // this.update(&mut cx, |this, cx| { + // for workspace in &this.workspaces { + // let Some(workspace) = workspace.upgrade() else { + // continue; + // }; + // workspace.update(cx, |workspace, cx| { + // let project_id = workspace.project.read(cx).remote_id(); + // if update.project_id != project_id && update.project_id.is_some() { + // return; + // } + // workspace.handle_update_followers(leader_id, update.clone(), cx); + // }); + // } + // Ok(()) + // })? + todo!() + } } -// async fn handle_follow( -// this: ModelHandle, -// envelope: TypedEnvelope, -// _: Arc, -// mut cx: AsyncAppContext, -// ) -> Result { -// this.update(&mut cx, |this, cx| { -// let follower = Follower { -// project_id: envelope.payload.project_id, -// peer_id: envelope.original_sender_id()?, -// }; -// let active_project = ActiveCall::global(cx) -// .read(cx) -// .location() -// .map(|project| project.id()); - -// let mut response = proto::FollowResponse::default(); -// for workspace in &this.workspaces { -// let Some(workspace) = workspace.upgrade(cx) else { -// continue; -// }; - -// workspace.update(cx.as_mut(), |workspace, cx| { -// let handler_response = workspace.handle_follow(follower.project_id, cx); -// if response.views.is_empty() { -// response.views = handler_response.views; -// } else { -// response.views.extend_from_slice(&handler_response.views); -// } - -// if let Some(active_view_id) = handler_response.active_view_id.clone() { -// if response.active_view_id.is_none() -// || Some(workspace.project.id()) == active_project -// { -// response.active_view_id = Some(active_view_id); -// } -// } -// }); -// } - -// if let Err(ix) = this.followers.binary_search(&follower) { -// this.followers.insert(ix, follower); -// } - -// Ok(response) -// }) -// } - -// async fn handle_unfollow( -// this: ModelHandle, -// envelope: TypedEnvelope, -// _: Arc, -// mut cx: AsyncAppContext, -// ) -> Result<()> { -// this.update(&mut cx, |this, _| { -// let follower = Follower { -// project_id: envelope.payload.project_id, -// peer_id: envelope.original_sender_id()?, -// }; -// if let Ok(ix) = this.followers.binary_search(&follower) { -// this.followers.remove(ix); -// } -// Ok(()) -// }) -// } - -// async fn handle_update_followers( -// this: ModelHandle, -// envelope: TypedEnvelope, -// _: Arc, -// mut cx: AsyncAppContext, -// ) -> Result<()> { -// let leader_id = envelope.original_sender_id()?; -// let update = envelope.payload; -// this.update(&mut cx, |this, cx| { -// for workspace in &this.workspaces { -// let Some(workspace) = workspace.upgrade(cx) else { -// continue; -// }; -// workspace.update(cx.as_mut(), |workspace, cx| { -// let project_id = workspace.project.read(cx).remote_id(); -// if update.project_id != project_id && update.project_id.is_some() { -// return; -// } -// workspace.handle_update_followers(leader_id, update.clone(), cx); -// }); -// } -// Ok(()) -// }) -// } -// } - // impl Entity for WorkspaceStore { // type Event = (); // } @@ -4320,7 +4325,7 @@ pub fn open_paths( .await; if let Some(existing) = existing { - // Ok(( + // // Ok(( // existing.clone(), // cx.update_window_root(&existing, |workspace, cx| { // workspace.open_paths(abs_paths, true, cx) diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 64946e1829..a736a20574 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -12,7 +12,7 @@ use client2::UserStore; use db2::kvp::KEY_VALUE_STORE; use fs2::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; -use gpui2::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; +use gpui2::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; use isahc::{prelude::Configurable, Request}; use language2::LanguageRegistry; use log::LevelFilter; @@ -45,7 +45,7 @@ use util::{ paths, ResultExt, }; use uuid::Uuid; -use workspace2::AppState; +use workspace2::{AppState, WorkspaceStore}; use zed2::languages; use zed2::{ensure_only_instance, Assets, IsOnlyInstance}; @@ -120,7 +120,7 @@ fn main() { language2::init(cx); languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); + let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx)); cx.set_global(client.clone()); @@ -165,20 +165,18 @@ fn main() { // client.telemetry().start(installation_id, session_id, cx); - // todo!("app_state") - let app_state: Arc = todo!(); - // let app_state = Arc::new(AppState { - // languages, - // client: client.clone(), - // user_store, - // fs, - // build_window_options, - // initialize_workspace, - // background_actions, - // workspace_store, - // node_runtime, - // }); - // cx.set_global(Arc::downgrade(&app_state)); + let app_state = Arc::new(AppState { + languages, + client: client.clone(), + user_store, + fs, + build_window_options, + initialize_workspace, + background_actions, + workspace_store, + node_runtime, + }); + cx.set_global(Arc::downgrade(&app_state)); // audio::init(Assets, cx); // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); @@ -914,11 +912,13 @@ async fn handle_cli_connection( } } -// pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { -// &[ -// ("Go to file", &file_finder::Toggle), -// ("Open command palette", &command_palette::Toggle), -// ("Open recent projects", &recent_projects::OpenRecent), -// ("Change your settings", &zed_actions::OpenSettings), -// ] -// } +pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { + // &[ + // ("Go to file", &file_finder::Toggle), + // ("Open command palette", &command_palette::Toggle), + // ("Open recent projects", &recent_projects::OpenRecent), + // ("Change your settings", &zed_actions::OpenSettings), + // ] + // todo!() + &[] +} diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 729102046f..4f9aad042f 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -5,7 +5,10 @@ mod open_listener; pub use assets::*; use collections::HashMap; -use gpui2::{AsyncAppContext, Point}; +use gpui2::{ + point, px, AsyncAppContext, Point, Styled, TitlebarOptions, WindowBounds, WindowKind, + WindowOptions, +}; pub use only_instance::*; pub use open_listener::*; @@ -20,6 +23,7 @@ use futures::{ }; use std::{path::Path, sync::Arc, thread, time::Duration}; use util::{paths::PathLikeWithPosition, ResultExt}; +use uuid::Uuid; use workspace2::AppState; pub fn connect_to_cli( @@ -211,3 +215,27 @@ pub async fn handle_cli_connection( } } } + +pub fn build_window_options( + bounds: Option, + display: Option, + platform: &dyn Platform, +) -> WindowOptions { + let bounds = bounds.unwrap_or(WindowBounds::Maximized); + let display_id = display.and_then(|display| platform.screen_by_id(display)); + + WindowOptions { + bounds, + titlebar: Some(TitlebarOptions { + title: None, + appears_transparent: true, + traffic_light_position: Some(point(px(8.), px(8.))), + }), + center: false, + focus: false, + show: false, + kind: WindowKind::Normal, + is_movable: false, + display_id, + } +} From 0a2fde8707a0ccd050bf02203432942641aafe77 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 11:03:01 -0600 Subject: [PATCH 23/66] WIP --- crates/gpui2/src/app.rs | 15 ++++++-- crates/gpui2/src/platform.rs | 4 +++ crates/gpui2/src/platform/mac/display.rs | 44 +++++++++++++++++++++++- crates/workspace2/src/searchable.rs | 1 + crates/workspace2/src/workspace2.rs | 3 +- crates/zed2/src/main.rs | 2 +- crates/zed2/src/zed2.rs | 12 +++---- 7 files changed, 69 insertions(+), 12 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index c71b2d28d0..1e09136bdb 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -11,14 +11,15 @@ use refineable::Refineable; use smallvec::SmallVec; #[cfg(any(test, feature = "test-support"))] pub use test_context::*; +use uuid::Uuid; use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, - Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, - TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, - WindowHandle, WindowId, + Pixels, Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, + SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, + WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -32,6 +33,7 @@ use std::{ mem, ops::{Deref, DerefMut}, path::PathBuf, + rc::Rc, sync::{atomic::Ordering::SeqCst, Arc, Weak}, time::Duration, }; @@ -847,6 +849,13 @@ where pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { self.platform().path_for_auxiliary_executable(name) } + + pub fn display_for_uuid(&self, uuid: Uuid) -> Option> { + self.platform() + .displays() + .into_iter() + .find(|display| display.uuid().ok() == Some(uuid)) + } } impl MainThread { diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 295a89a190..bf047a4947 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -28,6 +28,7 @@ use std::{ str::FromStr, sync::Arc, }; +use uuid::Uuid; pub use keystroke::*; #[cfg(target_os = "macos")] @@ -106,6 +107,9 @@ pub(crate) trait Platform: 'static { pub trait PlatformDisplay: Send + Sync + Debug { fn id(&self) -> DisplayId; + /// Returns a stable identifier for this display that can be persisted and used + /// across system restarts. + fn uuid(&self) -> Result; fn as_any(&self) -> &dyn Any; fn bounds(&self) -> Bounds; } diff --git a/crates/gpui2/src/platform/mac/display.rs b/crates/gpui2/src/platform/mac/display.rs index dc064293f3..b326eaa66d 100644 --- a/crates/gpui2/src/platform/mac/display.rs +++ b/crates/gpui2/src/platform/mac/display.rs @@ -1,9 +1,12 @@ use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; +use anyhow::Result; +use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}; use core_graphics::{ display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList}, geometry::{CGPoint, CGRect, CGSize}, }; use std::any::Any; +use uuid::Uuid; #[derive(Debug)] pub struct MacDisplay(pub(crate) CGDirectDisplayID); @@ -11,17 +14,23 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); unsafe impl Send for MacDisplay {} impl MacDisplay { - /// Get the screen with the given UUID. + /// Get the screen with the given [DisplayId]. pub fn find_by_id(id: DisplayId) -> Option { Self::all().find(|screen| screen.id() == id) } + /// Get the screen with the given persistent [Uuid]. + pub fn find_by_uuid(uuid: Uuid) -> Option { + Self::all().find(|screen| screen.uuid().ok() == Some(uuid)) + } + /// Get the primary screen - the one with the menu bar, and whose bottom left /// corner is at the origin of the AppKit coordinate system. pub fn primary() -> Self { Self::all().next().unwrap() } + /// Obtains an iterator over all currently active system displays. pub fn all() -> impl Iterator { unsafe { let mut display_count: u32 = 0; @@ -40,6 +49,11 @@ impl MacDisplay { } } +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} + /// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space. /// /// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen, @@ -88,6 +102,34 @@ impl PlatformDisplay for MacDisplay { DisplayId(self.0) } + fn uuid(&self) -> Result { + let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) }; + anyhow::ensure!( + !cfuuid.is_null(), + "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID" + ); + + let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) }; + Ok(Uuid::from_bytes([ + bytes.byte0, + bytes.byte1, + bytes.byte2, + bytes.byte3, + bytes.byte4, + bytes.byte5, + bytes.byte6, + bytes.byte7, + bytes.byte8, + bytes.byte9, + bytes.byte10, + bytes.byte11, + bytes.byte12, + bytes.byte13, + bytes.byte14, + bytes.byte15, + ])) + } + fn as_any(&self) -> &dyn Any { self } diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index 7b911b75d0..ff132a8d80 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -128,6 +128,7 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; } +// todo!("here is where we need to use AnyWeakView"); impl SearchableItemHandle for View { fn downgrade(&self) -> Box { // Box::new(self.downgrade()) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a5d73b2904..e7745dc763 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -42,6 +42,7 @@ use std::{ }; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; +use uuid::Uuid; use crate::persistence::model::{ DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, @@ -414,7 +415,7 @@ pub struct AppState { pub workspace_store: Model, pub fs: Arc, pub build_window_options: - fn(Option, Option, &MainThread) -> WindowOptions, + fn(Option, Option, MainThread) -> WindowOptions, pub initialize_workspace: fn(WeakModel, bool, Arc, AsyncAppContext) -> Task>, pub node_runtime: Arc, diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index a736a20574..8b1d087687 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -46,7 +46,7 @@ use util::{ }; use uuid::Uuid; use workspace2::{AppState, WorkspaceStore}; -use zed2::languages; +use zed2::{build_window_options, languages}; use zed2::{ensure_only_instance, Assets, IsOnlyInstance}; mod open_listener; diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 4f9aad042f..478e3a20d2 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -6,8 +6,8 @@ mod open_listener; pub use assets::*; use collections::HashMap; use gpui2::{ - point, px, AsyncAppContext, Point, Styled, TitlebarOptions, WindowBounds, WindowKind, - WindowOptions, + point, px, AppContext, AsyncAppContext, MainThread, Point, TitlebarOptions, WindowBounds, + WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; @@ -218,11 +218,11 @@ pub async fn handle_cli_connection( pub fn build_window_options( bounds: Option, - display: Option, - platform: &dyn Platform, + display_uuid: Option, + cx: MainThread, ) -> WindowOptions { let bounds = bounds.unwrap_or(WindowBounds::Maximized); - let display_id = display.and_then(|display| platform.screen_by_id(display)); + let display = display_uuid.and_then(|uuid| cx.display_for_uuid(uuid)); WindowOptions { bounds, @@ -236,6 +236,6 @@ pub fn build_window_options( show: false, kind: WindowKind::Normal, is_movable: false, - display_id, + display_id: display.map(|display| display.id()), } } From 90601fe4fd8d53e1e1a62ea9af656d612f4a9873 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 11:15:53 -0600 Subject: [PATCH 24/66] Checkpoint --- crates/gpui2/src/app/async_context.rs | 2 +- crates/gpui2/src/gpui2.rs | 4 +- crates/gpui2/src/window.rs | 46 ++++--- crates/workspace2/src/pane.rs | 2 +- crates/workspace2/src/workspace2.rs | 8 +- crates/zed2/src/main.rs | 4 +- crates/zed2/src/zed2.rs | 165 +++++++++++++++++++++++++- 7 files changed, 195 insertions(+), 36 deletions(-) diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index c5c03c4e9a..3a9a68a033 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -259,7 +259,7 @@ impl Context for AsyncWindowContext { } impl VisualContext for AsyncWindowContext { - type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>; + type ViewContext<'a, 'w, V: 'static> = ViewContext<'a, 'w, V>; fn build_view( &mut self, diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 8625866a44..74d87449b9 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -95,7 +95,7 @@ pub trait Context { } pub trait VisualContext: Context { - type ViewContext<'a, 'w, V>; + type ViewContext<'a, 'w, V: 'static>; fn build_view( &mut self, @@ -184,7 +184,7 @@ impl Context for MainThread { } impl VisualContext for MainThread { - type ViewContext<'a, 'w, V> = MainThread>; + type ViewContext<'a, 'w, V: 'static> = MainThread>; fn build_view( &mut self, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 2cdd933ae5..bd62ff44f7 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -7,7 +7,7 @@ use crate::{ 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, View, VisualContext, WeakModel, WeakView, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; @@ -1305,7 +1305,7 @@ impl Context for WindowContext<'_, '_> { } impl VisualContext for WindowContext<'_, '_> { - type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>; + type ViewContext<'a, 'w, V: 'static> = ViewContext<'a, 'w, V>; fn build_view( &mut self, @@ -1318,7 +1318,7 @@ impl VisualContext for WindowContext<'_, '_> { let view = View { model: slot.clone(), }; - let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade()); + let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); let entity = build_view_state(&mut cx); self.entities.insert(slot, entity); view @@ -1331,7 +1331,7 @@ impl VisualContext for WindowContext<'_, '_> { update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, '_, T>) -> R, ) -> Self::Result { let mut lease = self.app.entities.lease(&view.model); - let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade()); + let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); let result = update(&mut *lease, &mut cx); cx.app.entities.end_lease(lease); result @@ -1542,7 +1542,7 @@ impl BorrowWindow for T where T: BorrowMut + BorrowMut {} pub struct ViewContext<'a, 'w, V> { window_cx: WindowContext<'a, 'w>, - view: WeakView, + view: &'w View, } impl Borrow for ViewContext<'_, '_, V> { @@ -1570,22 +1570,18 @@ impl BorrowMut for ViewContext<'_, '_, V> { } impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { - pub(crate) fn mutable( - app: &'a mut AppContext, - window: &'w mut Window, - view: WeakView, - ) -> Self { + pub(crate) fn new(app: &'a mut AppContext, window: &'w mut Window, view: &'w View) -> Self { Self { window_cx: WindowContext::mutable(app, window), view, } } - pub fn view(&self) -> WeakView { + pub fn view(&self) -> View { self.view.clone() } - pub fn model(&self) -> WeakModel { + pub fn model(&self) -> Model { self.view.model.clone() } @@ -1600,14 +1596,14 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { where V: Any + Send, { - let view = self.view().upgrade().unwrap(); + let view = self.view(); self.window_cx.on_next_frame(move |cx| view.update(cx, f)); } /// Schedules the given function to be run at the end of the current effect cycle, allowing entities /// that are currently on the stack to be returned to the app. pub fn defer(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static + Send) { - let view = self.view(); + let view = self.view().downgrade(); self.window_cx.defer(move |cx| { view.update(cx, f).ok(); }); @@ -1623,7 +1619,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { V: 'static + Send, E: Entity, { - let view = self.view(); + let view = self.view().downgrade(); let entity_id = entity.entity_id(); let entity = entity.downgrade(); let window_handle = self.window.handle; @@ -1652,7 +1648,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { V2: EventEmitter, E: Entity, { - let view = self.view(); + let view = self.view().downgrade(); let entity_id = entity.entity_id(); let handle = entity.downgrade(); let window_handle = self.window.handle; @@ -1698,7 +1694,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { V2: 'static, E: Entity, { - let view = self.view(); + let view = self.view().downgrade(); let entity_id = entity.entity_id(); let window_handle = self.window.handle; self.app.release_listeners.insert( @@ -1723,7 +1719,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { &mut self, listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, ) { - let handle = self.view(); + let handle = self.view().downgrade(); self.window.focus_listeners.push(Box::new(move |event, cx| { handle .update(cx, |view, cx| listener(view, event, cx)) @@ -1739,7 +1735,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { let old_stack_len = self.window.key_dispatch_stack.len(); if !self.window.freeze_key_dispatch_stack { for (event_type, listener) in key_listeners { - let handle = self.view(); + let handle = self.view().downgrade(); let listener = Box::new( move |event: &dyn Any, context_stack: &[&DispatchContext], @@ -1829,7 +1825,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) }; Task::ready(Ok(f(view, cx))) } else { - let view = self.view().upgrade().unwrap(); + let view = self.view(); self.window_cx.run_on_main(move |cx| view.update(cx, f)) } } @@ -1842,7 +1838,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { R: Send + 'static, Fut: Future + Send + 'static, { - let view = self.view(); + let view = self.view().downgrade(); self.window_cx.spawn(move |_, cx| { let result = f(view, cx); async move { result.await } @@ -1864,12 +1860,12 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { f: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) + Send + 'static, ) -> Subscription { let window_handle = self.window.handle; - let handle = self.view(); + let view = self.view().downgrade(); self.global_observers.insert( TypeId::of::(), Box::new(move |cx| { cx.update_window(window_handle, |cx| { - handle.update(cx, |view, cx| f(view, cx)).is_ok() + view.update(cx, |view, cx| f(view, cx)).is_ok() }) .unwrap_or(false) }), @@ -1880,7 +1876,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + Send + 'static, ) { - let handle = self.view().upgrade().unwrap(); + let handle = self.view(); self.window_cx.on_mouse_event(move |event, phase, cx| { handle.update(cx, |view, cx| { handler(view, event, phase, cx); @@ -1937,7 +1933,7 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { } impl VisualContext for ViewContext<'_, '_, V> { - type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>; + type ViewContext<'a, 'w, V2: 'static> = ViewContext<'a, 'w, V2>; fn build_view( &mut self, diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index fc74139238..22c3833719 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -323,7 +323,7 @@ impl Pane { // menu.set_position_mode(OverlayPositionMode::Local) // }); - let handle = cx.view(); + let handle = cx.view().downgrade(); Self { items: Vec::new(), activation_history: Vec::new(), diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e7745dc763..3b009f4233 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -416,8 +416,12 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn(Option, Option, MainThread) -> WindowOptions, - pub initialize_workspace: - fn(WeakModel, bool, Arc, AsyncAppContext) -> Task>, + pub initialize_workspace: fn( + WeakView, + bool, + Arc, + AsyncWindowContext, + ) -> Task>, pub node_runtime: Arc, } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 8b1d087687..c982a735c5 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -46,7 +46,7 @@ use util::{ }; use uuid::Uuid; use workspace2::{AppState, WorkspaceStore}; -use zed2::{build_window_options, languages}; +use zed2::{build_window_options, initialize_workspace, languages}; use zed2::{ensure_only_instance, Assets, IsOnlyInstance}; mod open_listener; @@ -172,7 +172,7 @@ fn main() { fs, build_window_options, initialize_workspace, - background_actions, + // background_actions: todo!("ask Mikayla"), workspace_store, node_runtime, }); diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 478e3a20d2..194dbb9b25 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -6,8 +6,8 @@ mod open_listener; pub use assets::*; use collections::HashMap; use gpui2::{ - point, px, AppContext, AsyncAppContext, MainThread, Point, TitlebarOptions, WindowBounds, - WindowKind, WindowOptions, + point, px, AppContext, AsyncAppContext, AsyncWindowContext, MainThread, Point, Task, + TitlebarOptions, WeakView, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; @@ -24,7 +24,7 @@ use futures::{ use std::{path::Path, sync::Arc, thread, time::Duration}; use util::{paths::PathLikeWithPosition, ResultExt}; use uuid::Uuid; -use workspace2::AppState; +use workspace2::{AppState, Workspace}; pub fn connect_to_cli( server_name: &str, @@ -239,3 +239,162 @@ pub fn build_window_options( display_id: display.map(|display| display.id()), } } + +pub fn initialize_workspace( + workspace_handle: WeakView, + was_deserialized: bool, + app_state: Arc, + cx: AsyncWindowContext, +) -> Task> { + cx.spawn(|mut cx| async move { + workspace_handle.update(&mut cx, |workspace, cx| { + let workspace_handle = cx.view(); + cx.subscribe(&workspace_handle, { + move |workspace, _, event, cx| { + if let workspace2::Event::PaneAdded(pane) = event { + pane.update(cx, |pane, cx| { + // todo!() + // pane.toolbar().update(cx, |toolbar, cx| { + // let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); + // toolbar.add_item(breadcrumbs, cx); + // let buffer_search_bar = cx.add_view(BufferSearchBar::new); + // toolbar.add_item(buffer_search_bar.clone(), cx); + // let quick_action_bar = cx.add_view(|_| { + // QuickActionBar::new(buffer_search_bar, workspace) + // }); + // toolbar.add_item(quick_action_bar, cx); + // let diagnostic_editor_controls = + // cx.add_view(|_| diagnostics2::ToolbarControls::new()); + // toolbar.add_item(diagnostic_editor_controls, cx); + // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); + // toolbar.add_item(project_search_bar, cx); + // let submit_feedback_button = + // cx.add_view(|_| SubmitFeedbackButton::new()); + // toolbar.add_item(submit_feedback_button, cx); + // let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); + // toolbar.add_item(feedback_info_text, cx); + // let lsp_log_item = + // cx.add_view(|_| language_tools::LspLogToolbarItemView::new()); + // toolbar.add_item(lsp_log_item, cx); + // let syntax_tree_item = cx + // .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new()); + // toolbar.add_item(syntax_tree_item, cx); + // }) + }); + } + } + }) + .detach(); + + // cx.emit(workspace2::Event::PaneAdded( + // workspace.active_pane().clone(), + // )); + + // let collab_titlebar_item = + // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); + // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); + + // let copilot = + // cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx)); + // let diagnostic_summary = + // cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); + // let activity_indicator = activity_indicator::ActivityIndicator::new( + // workspace, + // app_state.languages.clone(), + // cx, + // ); + // let active_buffer_language = + // cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); + // let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx)); + // let feedback_button = cx.add_view(|_| { + // feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) + // }); + // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); + // workspace.status_bar().update(cx, |status_bar, cx| { + // status_bar.add_left_item(diagnostic_summary, cx); + // status_bar.add_left_item(activity_indicator, cx); + + // status_bar.add_right_item(feedback_button, cx); + // status_bar.add_right_item(copilot, cx); + // status_bar.add_right_item(active_buffer_language, cx); + // status_bar.add_right_item(vim_mode_indicator, cx); + // status_bar.add_right_item(cursor_position, cx); + // }); + + // auto_update::notify_of_any_new_update(cx.weak_handle(), cx); + + // vim::observe_keystrokes(cx); + + // cx.on_window_should_close(|workspace, cx| { + // if let Some(task) = workspace.close(&Default::default(), cx) { + // task.detach_and_log_err(cx); + // } + // false + // }); + // })?; + + // let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); + // let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); + // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone()); + // let channels_panel = + // collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone()); + // let chat_panel = + // collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone()); + // let notification_panel = collab_ui::notification_panel::NotificationPanel::load( + // workspace_handle.clone(), + // cx.clone(), + // ); + // let ( + // project_panel, + // terminal_panel, + // assistant_panel, + // channels_panel, + // chat_panel, + // notification_panel, + // ) = futures::try_join!( + // project_panel, + // terminal_panel, + // assistant_panel, + // channels_panel, + // chat_panel, + // notification_panel, + // )?; + // workspace_handle.update(&mut cx, |workspace, cx| { + // let project_panel_position = project_panel.position(cx); + // workspace.add_panel_with_extra_event_handler( + // project_panel, + // cx, + // |workspace, _, event, cx| match event { + // project_panel::Event::NewSearchInDirectory { dir_entry } => { + // search::ProjectSearchView::new_search_in_directory(workspace, dir_entry, cx) + // } + // project_panel::Event::ActivatePanel => { + // workspace.focus_panel::(cx); + // } + // _ => {} + // }, + // ); + // workspace.add_panel(terminal_panel, cx); + // workspace.add_panel(assistant_panel, cx); + // workspace.add_panel(channels_panel, cx); + // workspace.add_panel(chat_panel, cx); + // workspace.add_panel(notification_panel, cx); + + // if !was_deserialized + // && workspace + // .project() + // .read(cx) + // .visible_worktrees(cx) + // .any(|tree| { + // tree.read(cx) + // .root_entry() + // .map_or(false, |entry| entry.is_dir()) + // }) + // { + // workspace.toggle_dock(project_panel_position, cx); + // } + // cx.focus_self(); + })?; + Ok(()) + }) +} From 9798d65cf917d9d4cea6ca076d25e3f6a1d1f943 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 11:22:40 -0600 Subject: [PATCH 25/66] Checkpoint --- crates/feature_flags2/src/feature_flags2.rs | 2 +- crates/gpui2/src/app.rs | 4 +- crates/gpui2/src/app/async_context.rs | 8 +-- crates/gpui2/src/element.rs | 6 +-- crates/gpui2/src/gpui2.rs | 20 ++++---- crates/gpui2/src/view.rs | 4 +- crates/gpui2/src/window.rs | 56 ++++++++++----------- crates/gpui2_macros/src/derive_component.rs | 2 +- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/crates/feature_flags2/src/feature_flags2.rs b/crates/feature_flags2/src/feature_flags2.rs index 7b1c0dd4d7..446a2867e5 100644 --- a/crates/feature_flags2/src/feature_flags2.rs +++ b/crates/feature_flags2/src/feature_flags2.rs @@ -28,7 +28,7 @@ pub trait FeatureFlagViewExt { F: Fn(bool, &mut V, &mut ViewContext) + Send + Sync + 'static; } -impl FeatureFlagViewExt for ViewContext<'_, '_, V> +impl FeatureFlagViewExt for ViewContext<'_, V> where V: 'static + Send + Sync, { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 1e09136bdb..1b9bc023fc 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -308,7 +308,7 @@ impl AppContext { pub fn update_window_root( &mut self, handle: &WindowHandle, - update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Result where V: 'static + Send, @@ -882,7 +882,7 @@ impl MainThread { pub fn update_window_root( &mut self, handle: &WindowHandle, - update: impl FnOnce(&mut V, &mut MainThread>) -> R, + update: impl FnOnce(&mut V, &mut MainThread>) -> R, ) -> Result where V: 'static + Send, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 3a9a68a033..ae0a20d8fa 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -81,7 +81,7 @@ impl AsyncAppContext { pub fn update_window_root( &mut self, handle: &WindowHandle, - update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Result where V: 'static + Send, @@ -259,11 +259,11 @@ impl Context for AsyncWindowContext { } impl VisualContext for AsyncWindowContext { - type ViewContext<'a, 'w, V: 'static> = ViewContext<'a, 'w, V>; + type ViewContext<'a, V: 'static> = ViewContext<'a, V>; fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send, @@ -275,7 +275,7 @@ impl VisualContext for AsyncWindowContext { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, ) -> Self::Result { self.app .update_window(self.window, |cx| cx.update_view(view, update)) diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a715ed30ee..d11c007186 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -221,7 +221,7 @@ impl Element for Option where V: 'static, E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + Send + 'static, { type ElementState = AnyElement; @@ -265,7 +265,7 @@ impl Component for Option where V: 'static, E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + Send + 'static, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -276,7 +276,7 @@ impl Component for F where V: 'static, E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + Send + 'static, { fn render(self) -> AnyElement { AnyElement::new(Some(self)) diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 74d87449b9..704ff6c5b1 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -95,11 +95,11 @@ pub trait Context { } pub trait VisualContext: Context { - type ViewContext<'a, 'w, V: 'static>; + type ViewContext<'a, V: 'static>; fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send; @@ -107,7 +107,7 @@ pub trait VisualContext: Context { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, ) -> Self::Result; } @@ -184,11 +184,11 @@ impl Context for MainThread { } impl VisualContext for MainThread { - type ViewContext<'a, 'w, V: 'static> = MainThread>; + type ViewContext<'a, V: 'static> = MainThread>; fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send, @@ -196,8 +196,8 @@ impl VisualContext for MainThread { self.0.build_view(|cx| { let cx = unsafe { mem::transmute::< - &mut C::ViewContext<'_, '_, V>, - &mut MainThread>, + &mut C::ViewContext<'_, V>, + &mut MainThread>, >(cx) }; build_view_state(cx) @@ -207,13 +207,13 @@ impl VisualContext for MainThread { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, ) -> Self::Result { self.0.update_view(view, |view_state, cx| { let cx = unsafe { mem::transmute::< - &mut C::ViewContext<'_, '_, V>, - &mut MainThread>, + &mut C::ViewContext<'_, V>, + &mut MainThread>, >(cx) }; update(view_state, cx) diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 366172889a..89757434a9 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -53,7 +53,7 @@ impl View { pub fn update( &self, cx: &mut C, - f: impl FnOnce(&mut V, &mut C::ViewContext<'_, '_, V>) -> R, + f: impl FnOnce(&mut V, &mut C::ViewContext<'_, V>) -> R, ) -> C::Result where C: VisualContext, @@ -152,7 +152,7 @@ impl WeakView { pub fn update( &self, cx: &mut C, - f: impl FnOnce(&mut V, &mut C::ViewContext<'_, '_, V>) -> R, + f: impl FnOnce(&mut V, &mut C::ViewContext<'_, V>) -> R, ) -> Result where C: VisualContext, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index bd62ff44f7..c90fce8003 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1305,11 +1305,11 @@ impl Context for WindowContext<'_, '_> { } impl VisualContext for WindowContext<'_, '_> { - type ViewContext<'a, 'w, V: 'static> = ViewContext<'a, 'w, V>; + type ViewContext<'a, V: 'static> = ViewContext<'a, V>; fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send, @@ -1328,7 +1328,7 @@ impl VisualContext for WindowContext<'_, '_> { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, '_, T>) -> R, + update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, T>) -> R, ) -> Self::Result { let mut lease = self.app.entities.lease(&view.model); let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); @@ -1540,37 +1540,37 @@ impl BorrowMut for WindowContext<'_, '_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} -pub struct ViewContext<'a, 'w, V> { - window_cx: WindowContext<'a, 'w>, - view: &'w View, +pub struct ViewContext<'a, V> { + window_cx: WindowContext<'a, 'a>, + view: &'a View, } -impl Borrow for ViewContext<'_, '_, V> { +impl Borrow for ViewContext<'_, V> { fn borrow(&self) -> &AppContext { &*self.window_cx.app } } -impl BorrowMut for ViewContext<'_, '_, V> { +impl BorrowMut for ViewContext<'_, V> { fn borrow_mut(&mut self) -> &mut AppContext { &mut *self.window_cx.app } } -impl Borrow for ViewContext<'_, '_, V> { +impl Borrow for ViewContext<'_, V> { fn borrow(&self) -> &Window { &*self.window_cx.window } } -impl BorrowMut for ViewContext<'_, '_, V> { +impl BorrowMut for ViewContext<'_, V> { fn borrow_mut(&mut self) -> &mut Window { &mut *self.window_cx.window } } -impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { - pub(crate) fn new(app: &'a mut AppContext, window: &'w mut Window, view: &'w View) -> Self { +impl<'a, V: 'static> ViewContext<'a, V> { + pub(crate) fn new(app: &'a mut AppContext, window: &'a mut Window, view: &'a View) -> Self { Self { window_cx: WindowContext::mutable(app, window), view, @@ -1612,7 +1612,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn observe( &mut self, entity: &E, - mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, '_, V>) + Send + 'static, + mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, V>) + Send + 'static, ) -> Subscription where V2: 'static, @@ -1642,7 +1642,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, '_, V>) + Send + 'static, + mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, V>) + Send + 'static, ) -> Subscription where V2: EventEmitter, @@ -1687,7 +1687,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn observe_release( &mut self, entity: &E, - mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, '_, V>) + Send + 'static, + mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, V>) + Send + 'static, ) -> Subscription where V: Any + Send, @@ -1816,7 +1816,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn run_on_main( &mut self, view: &mut V, - f: impl FnOnce(&mut V, &mut MainThread>) -> R + Send + 'static, + f: impl FnOnce(&mut V, &mut MainThread>) -> R + Send + 'static, ) -> Task> where R: Send + 'static, @@ -1857,7 +1857,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn observe_global( &mut self, - f: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) + Send + 'static, + f: impl Fn(&mut V, &mut ViewContext<'_, V>) + Send + 'static, ) -> Subscription { let window_handle = self.window.handle; let view = self.view().downgrade(); @@ -1885,10 +1885,10 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { } } -impl<'a, 'w, V> ViewContext<'a, 'w, V> +impl ViewContext<'_, V> where V: EventEmitter, - V::Event: Any + Send, + V::Event: 'static + Send, { pub fn emit(&mut self, event: V::Event) { let emitter = self.view.model.entity_id; @@ -1899,7 +1899,7 @@ where } } -impl<'a, 'w, V: 'static> MainThread> { +impl MainThread> { fn platform_window(&self) -> &dyn PlatformWindow { self.window.platform_window.borrow_on_main_thread().as_ref() } @@ -1909,7 +1909,7 @@ impl<'a, 'w, V: 'static> MainThread> { } } -impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { +impl Context for ViewContext<'_, V> { type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; @@ -1932,12 +1932,12 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { } } -impl VisualContext for ViewContext<'_, '_, V> { - type ViewContext<'a, 'w, V2: 'static> = ViewContext<'a, 'w, V2>; +impl VisualContext for ViewContext<'_, V> { + type ViewContext<'a, W: 'static> = ViewContext<'a, W>; fn build_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W, + build_view: impl FnOnce(&mut Self::ViewContext<'_, W>) -> W, ) -> Self::Result> { self.window_cx.build_view(build_view) } @@ -1945,21 +1945,21 @@ impl VisualContext for ViewContext<'_, '_, V> { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V2, &mut Self::ViewContext<'_, '_, V2>) -> R, + update: impl FnOnce(&mut V2, &mut Self::ViewContext<'_, V2>) -> R, ) -> Self::Result { self.window_cx.update_view(view, update) } } -impl<'a, 'w, V> std::ops::Deref for ViewContext<'a, 'w, V> { - type Target = WindowContext<'a, 'w>; +impl<'a, V> std::ops::Deref for ViewContext<'a, V> { + type Target = WindowContext<'a, 'a>; fn deref(&self) -> &Self::Target { &self.window_cx } } -impl<'a, 'w, V> std::ops::DerefMut for ViewContext<'a, 'w, V> { +impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.window_cx } diff --git a/crates/gpui2_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index d1919c8bc4..a946703310 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -30,7 +30,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let expanded = quote! { impl #impl_generics gpui2::Component<#view_type> for #name #ty_generics #where_clause { fn render(self) -> gpui2::AnyElement<#view_type> { - (move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, '_, #view_type>| self.render(view_state, cx)) + (move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, #view_type>| self.render(view_state, cx)) .render() } } From 8f1000ea10d4805a50383a5239c88bbcb87693a6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 11:27:08 -0600 Subject: [PATCH 26/66] Checkpoint --- crates/gpui2/src/app.rs | 14 --------- crates/gpui2/src/app/async_context.rs | 12 +------- crates/gpui2/src/app/test_context.rs | 9 ------ crates/gpui2/src/window.rs | 43 +++++++++++---------------- 4 files changed, 19 insertions(+), 59 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 1b9bc023fc..0cfc85dda8 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -267,20 +267,6 @@ impl AppContext { .collect() } - pub(crate) fn read_window( - &mut self, - handle: AnyWindowHandle, - read: impl FnOnce(&WindowContext) -> R, - ) -> Result { - let window = self - .windows - .get(handle.id) - .ok_or_else(|| anyhow!("window not found"))? - .as_ref() - .unwrap(); - Ok(read(&WindowContext::immutable(self, &window))) - } - pub(crate) fn update_window( &mut self, handle: AnyWindowHandle, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index ae0a20d8fa..8afd947e74 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -58,16 +58,6 @@ impl AsyncAppContext { Ok(f(&mut *lock)) } - pub fn read_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(&WindowContext) -> R, - ) -> Result { - let app = self.app.upgrade().context("app was released")?; - let mut app_context = app.lock(); - app_context.read_window(handle, update) - } - pub fn update_window( &self, handle: AnyWindowHandle, @@ -183,7 +173,7 @@ impl AsyncWindowContext { read: impl FnOnce(&G, &WindowContext) -> R, ) -> Result { self.app - .read_window(self.window, |cx| read(cx.global(), cx)) + .update_window(self.window, |cx| read(cx.global(), cx)) } pub fn update_global( diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 59bfc17dd8..2ab61bfd51 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -67,15 +67,6 @@ impl TestAppContext { f(&mut *lock) } - pub fn read_window( - &self, - handle: AnyWindowHandle, - read: impl FnOnce(&WindowContext) -> R, - ) -> R { - let mut app_context = self.app.lock(); - app_context.read_window(handle, read).unwrap() - } - pub fn update_window( &self, handle: AnyWindowHandle, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index c90fce8003..cb1ff6c2ad 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -305,20 +305,13 @@ impl ContentMask { /// Provides access to application state in the context of a single window. Derefs /// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes /// an `AppContext` and call any `AppContext` methods. -pub struct WindowContext<'a, 'w> { +pub struct WindowContext<'a> { pub(crate) app: Reference<'a, AppContext>, - pub(crate) window: Reference<'w, Window>, + pub(crate) window: Reference<'a, Window>, } -impl<'a, 'w> WindowContext<'a, 'w> { - pub(crate) fn immutable(app: &'a AppContext, window: &'w Window) -> Self { - Self { - app: Reference::Immutable(app), - window: Reference::Immutable(window), - } - } - - pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self { +impl<'a> WindowContext<'a> { + pub(crate) fn mutable(app: &'a mut AppContext, window: &'a mut Window) -> Self { Self { app: Reference::Mutable(app), window: Reference::Mutable(window), @@ -388,7 +381,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_, '_>) + Send + 'static, + mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + Send + 'static, ) -> Subscription where Emitter: EventEmitter, @@ -419,7 +412,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// of the window. pub fn run_on_main( &mut self, - f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, + f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, ) -> Task> where R: Send + 'static, @@ -1185,7 +1178,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// is updated. pub fn observe_global( &mut self, - f: impl Fn(&mut WindowContext<'_, '_>) + Send + 'static, + f: impl Fn(&mut WindowContext<'_>) + Send + 'static, ) -> Subscription { let window_handle = self.window.handle; self.global_observers.insert( @@ -1273,7 +1266,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } -impl Context for WindowContext<'_, '_> { +impl Context for WindowContext<'_> { type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; @@ -1304,7 +1297,7 @@ impl Context for WindowContext<'_, '_> { } } -impl VisualContext for WindowContext<'_, '_> { +impl VisualContext for WindowContext<'_> { type ViewContext<'a, V: 'static> = ViewContext<'a, V>; fn build_view( @@ -1338,7 +1331,7 @@ impl VisualContext for WindowContext<'_, '_> { } } -impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> { +impl<'a> std::ops::Deref for WindowContext<'a> { type Target = AppContext; fn deref(&self) -> &Self::Target { @@ -1346,19 +1339,19 @@ impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> { } } -impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> { +impl<'a> std::ops::DerefMut for WindowContext<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.app } } -impl<'a, 'w> Borrow for WindowContext<'a, 'w> { +impl<'a> Borrow for WindowContext<'a> { fn borrow(&self) -> &AppContext { &self.app } } -impl<'a, 'w> BorrowMut for WindowContext<'a, 'w> { +impl<'a> BorrowMut for WindowContext<'a> { fn borrow_mut(&mut self) -> &mut AppContext { &mut self.app } @@ -1526,13 +1519,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } } -impl Borrow for WindowContext<'_, '_> { +impl Borrow for WindowContext<'_> { fn borrow(&self) -> &Window { &self.window } } -impl BorrowMut for WindowContext<'_, '_> { +impl BorrowMut for WindowContext<'_> { fn borrow_mut(&mut self) -> &mut Window { &mut self.window } @@ -1541,7 +1534,7 @@ impl BorrowMut for WindowContext<'_, '_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} pub struct ViewContext<'a, V> { - window_cx: WindowContext<'a, 'a>, + window_cx: WindowContext<'a>, view: &'a View, } @@ -1740,7 +1733,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { move |event: &dyn Any, context_stack: &[&DispatchContext], phase: DispatchPhase, - cx: &mut WindowContext<'_, '_>| { + cx: &mut WindowContext<'_>| { handle .update(cx, |view, cx| { listener(view, event, context_stack, phase, cx) @@ -1952,7 +1945,7 @@ impl VisualContext for ViewContext<'_, V> { } impl<'a, V> std::ops::Deref for ViewContext<'a, V> { - type Target = WindowContext<'a, 'a>; + type Target = WindowContext<'a>; fn deref(&self) -> &Self::Target { &self.window_cx From fd15551d975ea705f9b09887f8eabb0ce2ac871a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 11:29:13 -0600 Subject: [PATCH 27/66] Remove Reference --- crates/gpui2/src/app.rs | 11 ++++------- crates/gpui2/src/app/model_context.rs | 11 ++++------- crates/gpui2/src/gpui2.rs | 27 --------------------------- crates/gpui2/src/window.rs | 25 +++++++++++-------------- 4 files changed, 19 insertions(+), 55 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 0cfc85dda8..01639be82b 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -280,7 +280,7 @@ impl AppContext { .take() .unwrap(); - let result = update(&mut WindowContext::mutable(cx, &mut window)); + let result = update(&mut WindowContext::new(cx, &mut window)); cx.windows .get_mut(handle.id) @@ -765,7 +765,7 @@ impl Context for AppContext { ) -> Model { self.update(|cx| { let slot = cx.entities.reserve(); - let entity = build_model(&mut ModelContext::mutable(cx, slot.downgrade())); + let entity = build_model(&mut ModelContext::new(cx, slot.downgrade())); cx.entities.insert(slot, entity) }) } @@ -779,10 +779,7 @@ impl Context for AppContext { ) -> R { self.update(|cx| { let mut entity = cx.entities.lease(model); - let result = update( - &mut entity, - &mut ModelContext::mutable(cx, model.downgrade()), - ); + let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade())); cx.entities.end_lease(entity); result }) @@ -898,7 +895,7 @@ impl MainThread { let id = cx.windows.insert(None); let handle = WindowHandle::new(id); let mut window = Window::new(handle.into(), options, cx); - let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); + let root_view = build_root_view(&mut WindowContext::new(cx, &mut window)); window.root_view.replace(root_view.into()); cx.windows.get_mut(id).unwrap().replace(window); handle diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 8a4576c052..8fc9b5b544 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -1,6 +1,6 @@ use crate::{ AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread, - Model, Reference, Subscription, Task, WeakModel, + Model, Subscription, Task, WeakModel, }; use derive_more::{Deref, DerefMut}; use futures::FutureExt; @@ -14,16 +14,13 @@ use std::{ pub struct ModelContext<'a, T> { #[deref] #[deref_mut] - app: Reference<'a, AppContext>, + app: &'a mut AppContext, model_state: WeakModel, } impl<'a, T: 'static> ModelContext<'a, T> { - pub(crate) fn mutable(app: &'a mut AppContext, model_state: WeakModel) -> Self { - Self { - app: Reference::Mutable(app), - model_state, - } + pub(crate) fn new(app: &'a mut AppContext, model_state: WeakModel) -> Self { + Self { app, model_state } } pub fn entity_id(&self) -> EntityId { diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 704ff6c5b1..55df1dcd09 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -307,33 +307,6 @@ impl>> From for SharedString { } } -pub enum Reference<'a, T> { - Immutable(&'a T), - Mutable(&'a mut T), -} - -impl<'a, T> Deref for Reference<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - Reference::Immutable(target) => target, - Reference::Mutable(target) => target, - } - } -} - -impl<'a, T> DerefMut for Reference<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Reference::Immutable(_) => { - panic!("cannot mutably deref an immutable reference. this is a bug in GPUI."); - } - Reference::Mutable(target) => target, - } - } -} - pub(crate) struct MainThreadOnly { executor: Executor, value: Arc, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index cb1ff6c2ad..5445b284a9 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -5,10 +5,10 @@ use crate::{ Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, Model, ModelContext, 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, View, VisualContext, WeakView, - WindowOptions, SUBPIXEL_VARIANTS, + Point, PolychromeSprite, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams, + ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, + Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -306,16 +306,13 @@ impl ContentMask { /// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes /// an `AppContext` and call any `AppContext` methods. pub struct WindowContext<'a> { - pub(crate) app: Reference<'a, AppContext>, - pub(crate) window: Reference<'a, Window>, + pub(crate) app: &'a mut AppContext, + pub(crate) window: &'a mut Window, } impl<'a> WindowContext<'a> { - pub(crate) fn mutable(app: &'a mut AppContext, window: &'a mut Window) -> Self { - Self { - app: Reference::Mutable(app), - window: Reference::Mutable(window), - } + pub(crate) fn new(app: &'a mut AppContext, window: &'a mut Window) -> Self { + Self { app, window } } /// Obtain a handle to the window that belongs to this context. @@ -1278,7 +1275,7 @@ impl Context for WindowContext<'_> { T: 'static + Send, { let slot = self.app.entities.reserve(); - let model = build_model(&mut ModelContext::mutable(&mut *self.app, slot.downgrade())); + let model = build_model(&mut ModelContext::new(&mut *self.app, slot.downgrade())); self.entities.insert(slot, model) } @@ -1290,7 +1287,7 @@ impl Context for WindowContext<'_> { let mut entity = self.entities.lease(model); let result = update( &mut *entity, - &mut ModelContext::mutable(&mut *self.app, model.downgrade()), + &mut ModelContext::new(&mut *self.app, model.downgrade()), ); self.entities.end_lease(entity); result @@ -1565,7 +1562,7 @@ impl BorrowMut for ViewContext<'_, V> { impl<'a, V: 'static> ViewContext<'a, V> { pub(crate) fn new(app: &'a mut AppContext, window: &'a mut Window, view: &'a View) -> Self { Self { - window_cx: WindowContext::mutable(app, window), + window_cx: WindowContext::new(app, window), view, } } From 0ecf6bde732a8174e89d9f807474ce3c59423b46 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 16:15:30 -0600 Subject: [PATCH 28/66] WIP --- crates/workspace2/src/workspace2.rs | 112 ++++++++++++++-------------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3b009f4233..83221e8e91 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -23,9 +23,9 @@ use futures::{ FutureExt, }; use gpui2::{ - AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, DisplayId, EventEmitter, - MainThread, Model, ModelContext, Subscription, Task, View, ViewContext, VisualContext, - WeakModel, WeakView, WindowBounds, WindowHandle, WindowOptions, + AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, DisplayId, Entity, + EventEmitter, MainThread, Model, ModelContext, Subscription, Task, View, ViewContext, + VisualContext, WeakModel, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; @@ -426,7 +426,7 @@ pub struct AppState { } pub struct WorkspaceStore { - workspaces: HashSet>, + workspaces: HashSet>, followers: Vec, client: Arc, _subscriptions: Vec, @@ -3023,7 +3023,7 @@ impl Workspace { &self, project_only: bool, update: proto::update_followers::Variant, - cx: &mut AppContext, + cx: &mut WindowContext, ) -> Option<()> { let project_id = if project_only { self.project.read(cx).remote_id() @@ -3895,17 +3895,16 @@ impl EventEmitter for Workspace { impl WorkspaceStore { pub fn new(client: Arc, cx: &mut ModelContext) -> Self { - // Self { - // workspaces: Default::default(), - // followers: Default::default(), - // _subscriptions: vec![ - // client.add_request_handler(cx.weak_model(), Self::handle_follow), - // client.add_message_handler(cx.weak_model(), Self::handle_unfollow), - // client.add_message_handler(cx.weak_model(), Self::handle_update_followers), - // ], - // client, - // } - todo!() + Self { + workspaces: Default::default(), + followers: Default::default(), + _subscriptions: vec![], + // client.add_request_handler(cx.weak_model(), Self::handle_follow), + // client.add_message_handler(cx.weak_model(), Self::handle_unfollow), + // client.add_message_handler(cx.weak_model(), Self::handle_update_followers), + // ], + client, + } } pub fn update_followers( @@ -3943,53 +3942,50 @@ impl WorkspaceStore { .log_err() } - // async fn handle_follow( - // this: Model, - // envelope: TypedEnvelope, - // _: Arc, - // mut cx: AsyncAppContext, - // ) -> Result { - // this.update(&mut cx, |this, cx| { - // let follower = Follower { - // project_id: envelope.payload.project_id, - // peer_id: envelope.original_sender_id()?, - // }; - // let active_project = ActiveCall::global(cx) - // .read(cx) - // .location() - // .map(|project| project.id()); + async fn handle_follow( + this: Model, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + this.update(&mut cx, |this, cx| { + let follower = Follower { + project_id: envelope.payload.project_id, + peer_id: envelope.original_sender_id()?, + }; + let active_project = ActiveCall::global(cx).read(cx).location(); - // let mut response = proto::FollowResponse::default(); - // for workspace in &this.workspaces { - // let Some(workspace) = workspace.upgrade(cx) else { - // continue; - // }; + let mut response = proto::FollowResponse::default(); + for workspace in &this.workspaces { + let Some(workspace) = workspace.upgrade(cx) else { + continue; + }; - // workspace.update(cx.as_mut(), |workspace, cx| { - // let handler_response = workspace.handle_follow(follower.project_id, cx); - // if response.views.is_empty() { - // response.views = handler_response.views; - // } else { - // response.views.extend_from_slice(&handler_response.views); - // } + workspace.update(cx, |workspace, cx| { + let handler_response = workspace.handle_follow(follower.project_id, cx); + if response.views.is_empty() { + response.views = handler_response.views; + } else { + response.views.extend_from_slice(&handler_response.views); + } - // if let Some(active_view_id) = handler_response.active_view_id.clone() { - // if response.active_view_id.is_none() - // || Some(workspace.project.id()) == active_project - // { - // response.active_view_id = Some(active_view_id); - // } - // } - // }); - // } + if let Some(active_view_id) = handler_response.active_view_id.clone() { + if response.active_view_id.is_none() + || Some(workspace.project.downgrade()) == active_project + { + response.active_view_id = Some(active_view_id); + } + } + }); + } - // if let Err(ix) = this.followers.binary_search(&follower) { - // this.followers.insert(ix, follower); - // } + if let Err(ix) = this.followers.binary_search(&follower) { + this.followers.insert(ix, follower); + } - // Ok(response) - // }) - // } + Ok(response) + })? + } async fn handle_unfollow( model: Model, From 4da8ee1e1da730e5d06f0d4db7ab5f90348bf656 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 21:19:32 -0600 Subject: [PATCH 29/66] Remove one todo from the critical path --- crates/gpui2/src/window.rs | 17 ++++ crates/workspace2/src/workspace2.rs | 134 ++++++++++++++-------------- 2 files changed, 84 insertions(+), 67 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5445b284a9..f28544301b 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1993,6 +1993,23 @@ impl WindowHandle { state_type: PhantomData, } } + + pub fn update( + &self, + cx: &mut AppContext, + update: impl FnOnce(&mut V, &mut ViewContext) -> R, + ) -> Result { + cx.update_window(self.any_handle, |cx| { + let root_view = cx + .window + .root_view + .clone() + .unwrap() + .downcast::() + .unwrap(); + root_view.update(cx, update) + }) + } } impl Into for WindowHandle { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 83221e8e91..8e6c8c2c64 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -25,7 +25,7 @@ use futures::{ use gpui2::{ AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, DisplayId, Entity, EventEmitter, MainThread, Model, ModelContext, Subscription, Task, View, ViewContext, - VisualContext, WeakModel, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; @@ -2812,54 +2812,56 @@ impl Workspace { // // RPC handlers - // fn handle_follow( - // &mut self, - // follower_project_id: Option, - // cx: &mut ViewContext, - // ) -> proto::FollowResponse { - // let client = &self.app_state.client; - // let project_id = self.project.read(cx).remote_id(); + fn handle_follow( + &mut self, + follower_project_id: Option, + cx: &mut ViewContext, + ) -> proto::FollowResponse { + todo!() - // let active_view_id = self.active_item(cx).and_then(|i| { - // Some( - // i.to_followable_item_handle(cx)? - // .remote_id(client, cx)? - // .to_proto(), - // ) - // }); + // let client = &self.app_state.client; + // let project_id = self.project.read(cx).remote_id(); - // cx.notify(); + // let active_view_id = self.active_item(cx).and_then(|i| { + // Some( + // i.to_followable_item_handle(cx)? + // .remote_id(client, cx)? + // .to_proto(), + // ) + // }); - // 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(), - // } - // } + // 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, @@ -3942,7 +3944,7 @@ impl WorkspaceStore { .log_err() } - async fn handle_follow( + pub async fn handle_follow( this: Model, envelope: TypedEnvelope, _: Arc, @@ -3953,30 +3955,28 @@ impl WorkspaceStore { project_id: envelope.payload.project_id, peer_id: envelope.original_sender_id()?, }; - let active_project = ActiveCall::global(cx).read(cx).location(); + let active_project = ActiveCall::global(cx).read(cx).location().cloned(); let mut response = proto::FollowResponse::default(); for workspace in &this.workspaces { - let Some(workspace) = workspace.upgrade(cx) else { - continue; - }; - - workspace.update(cx, |workspace, cx| { - let handler_response = workspace.handle_follow(follower.project_id, cx); - if response.views.is_empty() { - response.views = handler_response.views; - } else { - response.views.extend_from_slice(&handler_response.views); - } - - if let Some(active_view_id) = handler_response.active_view_id.clone() { - if response.active_view_id.is_none() - || Some(workspace.project.downgrade()) == active_project - { - response.active_view_id = Some(active_view_id); + workspace + .update(cx, |workspace, cx| { + let handler_response = workspace.handle_follow(follower.project_id, cx); + if response.views.is_empty() { + response.views = handler_response.views; + } else { + response.views.extend_from_slice(&handler_response.views); } - } - }); + + if let Some(active_view_id) = handler_response.active_view_id.clone() { + if response.active_view_id.is_none() + || Some(workspace.project.downgrade()) == active_project + { + response.active_view_id = Some(active_view_id); + } + } + }) + .ok(); } if let Err(ix) = this.followers.binary_search(&follower) { From d47ef6470ba88cd62a35c9672b0c6994dd52d10f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 22:32:18 -0600 Subject: [PATCH 30/66] WIP --- crates/gpui2/src/app.rs | 48 ++--- crates/gpui2/src/app/async_context.rs | 36 +++- crates/gpui2/src/app/entity_map.rs | 2 +- crates/gpui2/src/app/model_context.rs | 13 +- crates/gpui2/src/app/test_context.rs | 9 + crates/gpui2/src/gpui2.rs | 53 ++++- crates/gpui2/src/window.rs | 78 +++++-- crates/workspace2/src/workspace2.rs | 297 +++++++++++++------------- crates/zed2/src/main.rs | 29 ++- 9 files changed, 358 insertions(+), 207 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 01639be82b..6c7b6df210 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -267,30 +267,6 @@ impl AppContext { .collect() } - pub(crate) fn update_window( - &mut self, - handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, - ) -> Result { - self.update(|cx| { - let mut window = cx - .windows - .get_mut(handle.id) - .ok_or_else(|| anyhow!("window not found"))? - .take() - .unwrap(); - - let result = update(&mut WindowContext::new(cx, &mut window)); - - cx.windows - .get_mut(handle.id) - .ok_or_else(|| anyhow!("window not found"))? - .replace(window); - - Ok(result) - }) - } - pub fn update_window_root( &mut self, handle: &WindowHandle, @@ -753,6 +729,7 @@ impl AppContext { } impl Context for AppContext { + type WindowContext<'a> = WindowContext<'a>; type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; @@ -784,6 +761,29 @@ impl Context for AppContext { result }) } + + fn update_window(&mut self, handle: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + self.update(|cx| { + let mut window = cx + .windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .take() + .unwrap(); + + let result = update(&mut WindowContext::new(cx, &mut window)); + + cx.windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .replace(window); + + Ok(result) + }) + } } impl MainThread diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 8afd947e74..ed51f73fb7 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,6 +1,6 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task, - View, ViewContext, VisualContext, WindowContext, WindowHandle, + AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Render, + Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle, }; use anyhow::Context as _; use derive_more::{Deref, DerefMut}; @@ -14,6 +14,7 @@ pub struct AsyncAppContext { } impl Context for AsyncAppContext { + type WindowContext<'a> = WindowContext<'a>; type ModelContext<'a, T> = ModelContext<'a, T>; type Result = Result; @@ -38,6 +39,13 @@ impl Context for AsyncAppContext { let mut lock = app.lock(); // Need this to compile Ok(lock.update_model(handle, update)) } + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + todo!() + } } impl AsyncAppContext { @@ -60,12 +68,12 @@ impl AsyncAppContext { pub fn update_window( &self, - handle: AnyWindowHandle, + window: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> Result { let app = self.app.upgrade().context("app was released")?; let mut app_context = app.lock(); - app_context.update_window(handle, update) + app_context.update_window(window, update) } pub fn update_window_root( @@ -224,7 +232,9 @@ impl AsyncWindowContext { } impl Context for AsyncWindowContext { + type WindowContext<'a> = WindowContext<'a>; type ModelContext<'a, T> = ModelContext<'a, T>; + type Result = Result; fn build_model( @@ -246,6 +256,13 @@ impl Context for AsyncWindowContext { self.app .update_window(self.window, |cx| cx.update_model(handle, update)) } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + self.app.update_window(window, update) + } } impl VisualContext for AsyncWindowContext { @@ -270,6 +287,17 @@ impl VisualContext for AsyncWindowContext { self.app .update_window(self.window, |cx| cx.update_view(view, update)) } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static + Send + Render, + { + self.app + .update_window(self.window, |cx| cx.replace_root_view(build_view)) + } } #[cfg(test)] diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 8ceadfe73e..3aaf1c99c3 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -1,4 +1,4 @@ -use crate::{private::Sealed, AnyBox, AppContext, Context, Entity}; +use crate::{private::Sealed, AnyBox, AppContext, AsyncAppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 8fc9b5b544..c8b3eacdbc 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -1,7 +1,8 @@ use crate::{ - AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, MainThread, - Model, Subscription, Task, WeakModel, + AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, + MainThread, Model, Subscription, Task, WeakModel, WindowContext, }; +use anyhow::Result; use derive_more::{Deref, DerefMut}; use futures::FutureExt; use std::{ @@ -228,6 +229,7 @@ where } impl<'a, T> Context for ModelContext<'a, T> { + type WindowContext<'b> = WindowContext<'b>; type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; @@ -248,6 +250,13 @@ impl<'a, T> Context for ModelContext<'a, T> { ) -> R { self.app.update_model(handle, update) } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> R, + { + self.app.update_window(window, update) + } } impl Borrow for ModelContext<'_, T> { diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 2ab61bfd51..c53aefe565 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -12,6 +12,7 @@ pub struct TestAppContext { } impl Context for TestAppContext { + type WindowContext<'a> = WindowContext<'a>; type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; @@ -34,6 +35,14 @@ impl Context for TestAppContext { let mut lock = self.app.lock(); lock.update_model(handle, update) } + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + let mut lock = self.app.lock(); + lock.update_window(window, f) + } } impl TestAppContext { diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 55df1dcd09..5e5ac119d8 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -77,6 +77,7 @@ use taffy::TaffyLayoutEngine; type AnyBox = Box; pub trait Context { + type WindowContext<'a>: VisualContext; type ModelContext<'a, T>; type Result; @@ -87,11 +88,17 @@ pub trait Context { where T: 'static + Send; - fn update_model( + fn update_model( &mut self, handle: &Model, update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, - ) -> Self::Result; + ) -> Self::Result + where + T: 'static; + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T; } pub trait VisualContext: Context { @@ -99,7 +106,7 @@ pub trait VisualContext: Context { fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send; @@ -109,6 +116,13 @@ pub trait VisualContext: Context { view: &View, update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, ) -> Self::Result; + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static + Send + Render; } pub trait Entity: Sealed { @@ -145,6 +159,7 @@ impl DerefMut for MainThread { } impl Context for MainThread { + type WindowContext<'a> = MainThread>; type ModelContext<'a, T> = MainThread>; type Result = C::Result; @@ -181,6 +196,20 @@ impl Context for MainThread { update(entity, cx) }) } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + self.0.update_window(window, |cx| { + let cx = unsafe { + mem::transmute::<&mut C::WindowContext<'_>, &mut MainThread>>( + cx, + ) + }; + update(cx) + }) + } } impl VisualContext for MainThread { @@ -219,6 +248,24 @@ impl VisualContext for MainThread { update(view_state, cx) }) } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static + Send + Render, + { + self.0.replace_root_view(|cx| { + let cx = unsafe { + mem::transmute::< + &mut C::ViewContext<'_, V>, + &mut MainThread>, + >(cx) + }; + build_view(cx) + }) + } } pub trait BorrowAppContext { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f28544301b..b2f341d396 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -5,7 +5,7 @@ use crate::{ Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, - Point, PolychromeSprite, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams, + Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, @@ -315,6 +315,8 @@ impl<'a> WindowContext<'a> { Self { app, window } } + // fn replace_root(&mut ) + /// Obtain a handle to the window that belongs to this context. pub fn window_handle(&self) -> AnyWindowHandle { self.window.handle @@ -1264,6 +1266,7 @@ impl<'a> WindowContext<'a> { } impl Context for WindowContext<'_> { + type WindowContext<'a> = WindowContext<'a>; type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; @@ -1292,6 +1295,17 @@ impl Context for WindowContext<'_> { self.entities.end_lease(entity); result } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + if window == self.window.handle { + Ok(update(self)) + } else { + self.app.update_window(window, update) + } + } } impl VisualContext for WindowContext<'_> { @@ -1326,6 +1340,24 @@ impl VisualContext for WindowContext<'_> { cx.app.entities.end_lease(lease); result } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static + Send + Render, + { + let slot = self.app.entities.reserve(); + let view = View { + model: slot.clone(), + }; + let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); + let entity = build_view(&mut cx); + self.entities.insert(slot, entity); + self.window.root_view = Some(view.clone().into()); + view + } } impl<'a> std::ops::Deref for WindowContext<'a> { @@ -1900,6 +1932,7 @@ impl MainThread> { } impl Context for ViewContext<'_, V> { + type WindowContext<'a> = WindowContext<'a>; type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; @@ -1920,6 +1953,13 @@ impl Context for ViewContext<'_, V> { ) -> R { self.window_cx.update_model(model, update) } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(&mut Self::WindowContext<'_>) -> T, + { + self.window_cx.update_window(window, update) + } } impl VisualContext for ViewContext<'_, V> { @@ -1939,6 +1979,16 @@ impl VisualContext for ViewContext<'_, V> { ) -> Self::Result { self.window_cx.update_view(view, update) } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut Self::ViewContext<'_, W>) -> W, + ) -> Self::Result> + where + W: 'static + Send + Render, + { + self.window_cx.replace_root_view(build_view) + } } impl<'a, V> std::ops::Deref for ViewContext<'a, V> { @@ -1972,18 +2022,7 @@ pub struct WindowHandle { state_type: PhantomData, } -impl Copy for WindowHandle {} - -impl Clone for WindowHandle { - fn clone(&self) -> Self { - WindowHandle { - any_handle: self.any_handle, - state_type: PhantomData, - } - } -} - -impl WindowHandle { +impl WindowHandle { pub fn new(id: WindowId) -> Self { WindowHandle { any_handle: AnyWindowHandle { @@ -1994,7 +2033,7 @@ impl WindowHandle { } } - pub fn update( + pub fn update_root( &self, cx: &mut AppContext, update: impl FnOnce(&mut V, &mut ViewContext) -> R, @@ -2012,6 +2051,17 @@ impl WindowHandle { } } +impl Copy for WindowHandle {} + +impl Clone for WindowHandle { + fn clone(&self) -> Self { + WindowHandle { + any_handle: self.any_handle, + state_type: PhantomData, + } + } +} + impl Into for WindowHandle { fn into(self) -> AnyWindowHandle { self.any_handle diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 8e6c8c2c64..99cc45572b 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -23,8 +23,8 @@ use futures::{ FutureExt, }; use gpui2::{ - AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, DisplayId, Entity, - EventEmitter, MainThread, Model, ModelContext, Subscription, Task, View, ViewContext, + div, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity, + EventEmitter, MainThread, Model, ModelContext, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; @@ -32,7 +32,10 @@ use language2::LanguageRegistry; use node_runtime::NodeRuntime; pub use pane::*; pub use pane_group::*; -use persistence::model::{ItemId, WorkspaceLocation}; +use persistence::{ + model::{ItemId, WorkspaceLocation}, + DB, +}; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; use std::{ any::TypeId, @@ -773,139 +776,137 @@ impl Workspace { // } // } - // fn new_local( - // abs_paths: Vec, - // app_state: Arc, - // requesting_window: Option>, - // cx: &mut AppContext, - // ) -> Task<( - // WeakView, - // Vec, anyhow::Error>>>, - // )> { - // let project_handle = Project::local( - // app_state.client.clone(), - // app_state.node_runtime.clone(), - // app_state.user_store.clone(), - // app_state.languages.clone(), - // app_state.fs.clone(), - // cx, - // ); + fn new_local( + abs_paths: Vec, + app_state: Arc, + requesting_window: Option>, + cx: &mut MainThread, + ) -> Task<( + WeakView, + Vec, anyhow::Error>>>, + )> { + let project_handle = Project::local( + app_state.client.clone(), + app_state.node_runtime.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx, + ); - // cx.spawn(|mut cx| async move { - // let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + cx.spawn_on_main(|mut cx| async move { + let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); - // let paths_to_open = Arc::new(abs_paths); + let paths_to_open = Arc::new(abs_paths); - // // Get project paths for all of the abs_paths - // let mut worktree_roots: HashSet> = Default::default(); - // let mut project_paths: Vec<(PathBuf, Option)> = - // Vec::with_capacity(paths_to_open.len()); - // for path in paths_to_open.iter().cloned() { - // if let Some((worktree, project_entry)) = cx - // .update(|cx| { - // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) - // }) - // .await - // .log_err() - // { - // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); - // project_paths.push((path, Some(project_entry))); - // } else { - // project_paths.push((path, None)); - // } - // } + // Get project paths for all of the abs_paths + let mut worktree_roots: HashSet> = Default::default(); + let mut project_paths: Vec<(PathBuf, Option)> = + Vec::with_capacity(paths_to_open.len()); + for path in paths_to_open.iter().cloned() { + if let Some((worktree, project_entry)) = cx + .update(|cx| { + Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) + })? + .await + .log_err() + { + worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok()); + 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 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?; + let window = if let Some(window) = requesting_window { + cx.update_window(window.into(), |cx| { + cx.replace_root_view(|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; - // } - // } + // 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.origin.x += screen_bounds.origin.x; + 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() - // }; + 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, - // ) - // }, - // ) - // } - // }; + // Use the serialized workspace to construct the new window + cx.open_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(); + // 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(); + (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()); + window.update_root(&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(); + 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) - // }) - // } + (workspace, opened_items) + }) + } pub fn weak_handle(&self) -> WeakView { self.weak_self.clone() @@ -3744,6 +3745,14 @@ impl EventEmitter for Workspace { type Event = Event; } +impl Render for Workspace { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + div() + } +} + // todo!() // impl Entity for Workspace { // type Event = Event; @@ -3960,7 +3969,7 @@ impl WorkspaceStore { let mut response = proto::FollowResponse::default(); for workspace in &this.workspaces { workspace - .update(cx, |workspace, cx| { + .update_root(cx, |workspace, cx| { let handler_response = workspace.handle_follow(follower.project_id, cx); if response.views.is_empty() { response.views = handler_response.views; @@ -4118,9 +4127,9 @@ pub async fn activate_workspace_for_project( .await } -// pub async fn last_opened_workspace_paths() -> Option { -// DB.last_workspace().await.log_err().flatten() -// } +pub async fn last_opened_workspace_paths() -> Option { + DB.last_workspace().await.log_err().flatten() +} // async fn join_channel_internal( // channel_id: u64, @@ -4345,24 +4354,24 @@ pub fn open_paths( }) } -// pub fn open_new( -// app_state: &Arc, -// cx: &mut AppContext, -// init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, -// ) -> Task<()> { -// let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); -// cx.spawn(|mut cx| async move { -// let (workspace, opened_paths) = task.await; +pub fn open_new( + app_state: &Arc, + cx: &mut AppContext, + init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, +) -> Task<()> { + let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); + cx.spawn(|mut cx| async move { + let (workspace, opened_paths) = task.await; -// workspace -// .update(&mut cx, |workspace, cx| { -// if opened_paths.is_empty() { -// init(workspace, cx) -// } -// }) -// .log_err(); -// }) -// } + workspace + .update(&mut cx, |workspace, cx| { + if opened_paths.is_empty() { + init(workspace, cx) + } + }) + .log_err(); + }) +} // pub fn create_and_open_local_file( // path: &'static Path, diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index c982a735c5..793c6d6139 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -314,21 +314,20 @@ async fn installation_id() -> Result { } async fn restore_or_create_workspace(_app_state: &Arc, mut _cx: AsyncAppContext) { - todo!("workspace") - // if let Some(location) = workspace::last_opened_workspace_paths().await { - // cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx)) - // .await - // .log_err(); - // } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { - // cx.update(|cx| show_welcome_experience(app_state, cx)); - // } else { - // cx.update(|cx| { - // workspace::open_new(app_state, cx, |workspace, cx| { - // Editor::new_file(workspace, &Default::default(), cx) - // }) - // .detach(); - // }); - // } + if let Some(location) = workspace2::last_opened_workspace_paths().await { + cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx)) + .await + .log_err(); + } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + cx.update(|cx| show_welcome_experience(app_state, cx)); + } else { + cx.update(|cx| { + workspace2::open_new(app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + }); + } } fn init_paths() { From 4d320f065ed3ba51181147826d7130d03f6b5924 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 12:47:19 +0100 Subject: [PATCH 31/66] WIP --- crates/gpui2/src/app.rs | 9 +- crates/gpui2/src/app/async_context.rs | 33 +- crates/gpui2/src/app/entity_map.rs | 2 +- crates/gpui2/src/geometry.rs | 12 + crates/gpui2/src/platform.rs | 23 +- crates/gpui2/src/window.rs | 19 +- crates/workspace2/src/dock.rs | 55 +- crates/workspace2/src/notifications.rs | 380 +++--- crates/workspace2/src/pane.rs | 168 ++- crates/workspace2/src/workspace2.rs | 1484 ++++++++++++------------ 10 files changed, 1125 insertions(+), 1060 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 6c7b6df210..bbc6bfa567 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -833,6 +833,10 @@ where self.platform().path_for_auxiliary_executable(name) } + pub fn displays(&self) -> Vec> { + self.platform().displays() + } + pub fn display_for_uuid(&self, uuid: Uuid) -> Option> { self.platform() .displays() @@ -889,13 +893,14 @@ impl MainThread { pub fn open_window( &mut self, options: crate::WindowOptions, - build_root_view: impl FnOnce(&mut WindowContext) -> View + Send + 'static, + build_root_view: impl FnOnce(&mut MainThread) -> View + Send + 'static, ) -> WindowHandle { self.update(|cx| { let id = cx.windows.insert(None); let handle = WindowHandle::new(id); let mut window = Window::new(handle.into(), options, cx); - let root_view = build_root_view(&mut WindowContext::new(cx, &mut window)); + let mut window_context = MainThread(WindowContext::new(cx, &mut window)); + let root_view = build_root_view(&mut window_context); window.root_view.replace(root_view.into()); cx.windows.get_mut(id).unwrap().replace(window); handle diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index ed51f73fb7..6802b5c1e1 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::Context as _; use derive_more::{Deref, DerefMut}; use parking_lot::Mutex; -use std::{future::Future, sync::Weak}; +use std::{future::Future, mem, sync::Weak}; #[derive(Clone)] pub struct AsyncAppContext { @@ -44,7 +44,9 @@ impl Context for AsyncAppContext { where F: FnOnce(&mut Self::WindowContext<'_>) -> T, { - todo!() + let app = self.app.upgrade().context("app was released")?; + let mut lock = app.lock(); // Need this to compile + lock.update_window(window, f) } } @@ -100,14 +102,14 @@ impl AsyncAppContext { pub fn spawn_on_main( &self, - f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static, + f: impl FnOnce(MainThread) -> Fut + Send + 'static, ) -> Task where Fut: Future + 'static, R: Send + 'static, { let this = self.clone(); - self.executor.spawn_on_main(|| f(this)) + self.executor.spawn_on_main(|| f(MainThread(this))) } pub fn run_on_main( @@ -153,6 +155,29 @@ impl AsyncAppContext { } } +impl MainThread { + pub fn update(&self, f: impl FnOnce(&mut MainThread) -> R) -> Result { + let app = self.app.upgrade().context("app was released")?; + let cx = &mut *app.lock(); + let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }; + Ok(f(cx)) + } + + /// Opens a new window with the given option and the root view returned by the given function. + /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific + /// functionality. + pub fn open_window( + &mut self, + options: crate::WindowOptions, + build_root_view: impl FnOnce(&mut MainThread) -> View + Send + 'static, + ) -> Result> { + let app = self.app.upgrade().context("app was released")?; + let cx = &mut *app.lock(); + let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }; + Ok(cx.open_window(options, build_root_view)) + } +} + #[derive(Clone, Deref, DerefMut)] pub struct AsyncWindowContext { #[deref] diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 3aaf1c99c3..8ceadfe73e 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -1,4 +1,4 @@ -use crate::{private::Sealed, AnyBox, AppContext, AsyncAppContext, Context, Entity, ModelContext}; +use crate::{private::Sealed, AnyBox, AppContext, Context, Entity}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index eedf8bbb2c..7d4073144c 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -931,6 +931,18 @@ impl From for GlobalPixels { } } +impl sqlez::bindable::StaticColumnCount for GlobalPixels {} + +impl sqlez::bindable::Bind for GlobalPixels { + fn bind( + &self, + statement: &sqlez::statement::Statement, + start_index: i32, + ) -> anyhow::Result { + self.0.bind(statement, start_index) + } +} + #[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)] pub struct Rems(f32); diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index bf047a4947..b2df16a20f 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -397,18 +397,17 @@ impl Bind for WindowBounds { } }; - // statement.bind( - // ®ion.map(|region| { - // ( - // region.origin.x, - // region.origin.y, - // region.size.width, - // region.size.height, - // ) - // }), - // next_index, - // ) - todo!() + statement.bind( + ®ion.map(|region| { + ( + region.origin.x, + region.origin.y, + region.size.width, + region.size.height, + ) + }), + next_index, + ) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index b2f341d396..1df49899ca 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -21,6 +21,7 @@ use std::{ borrow::{Borrow, BorrowMut, Cow}, fmt::Debug, future::Future, + hash::{Hash, Hasher}, marker::PhantomData, mem, sync::{ @@ -2014,7 +2015,7 @@ impl WindowId { } } -#[derive(PartialEq, Eq, Deref, DerefMut)] +#[derive(Deref, DerefMut)] pub struct WindowHandle { #[deref] #[deref_mut] @@ -2062,13 +2063,27 @@ impl Clone for WindowHandle { } } +impl PartialEq for WindowHandle { + fn eq(&self, other: &Self) -> bool { + self.any_handle == other.any_handle + } +} + +impl Eq for WindowHandle {} + +impl Hash for WindowHandle { + fn hash(&self, state: &mut H) { + self.any_handle.hash(state); + } +} + impl Into for WindowHandle { fn into(self) -> AnyWindowHandle { self.any_handle } } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, state_type: TypeId, diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 28dad63dbd..a83b133cb3 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,7 +1,7 @@ use crate::{status_bar::StatusItemView, Axis, Workspace}; use gpui2::{ - Action, AnyView, Div, EventEmitter, Render, Subscription, View, ViewContext, WeakView, - WindowContext, + Action, AnyView, Div, Entity, EntityId, EventEmitter, Render, Subscription, View, ViewContext, + WeakView, WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -40,8 +40,8 @@ pub trait Panel: Render + EventEmitter { fn is_focus_event(_: &Self::Event) -> bool; } -pub trait PanelHandle { - fn id(&self) -> usize; +pub trait PanelHandle: Send + Sync { + fn id(&self) -> EntityId; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); @@ -61,8 +61,8 @@ impl PanelHandle for View where T: Panel, { - fn id(&self) -> usize { - self.id() + fn id(&self) -> EntityId { + self.entity_id() } fn position(&self, cx: &WindowContext) -> DockPosition { @@ -178,18 +178,18 @@ pub struct PanelButtons { } impl Dock { - // pub fn new(position: DockPosition) -> Self { - // Self { - // position, - // panel_entries: Default::default(), - // active_panel_index: 0, - // is_open: false, - // } - // } + pub fn new(position: DockPosition) -> Self { + Self { + position, + panel_entries: Default::default(), + active_panel_index: 0, + is_open: false, + } + } - // pub fn position(&self) -> DockPosition { - // self.position - // } + pub fn position(&self) -> DockPosition { + self.position + } pub fn is_open(&self) -> bool { self.is_open @@ -432,17 +432,16 @@ impl Dock { // } // } -// todo!() -// impl PanelButtons { -// pub fn new( -// dock: View, -// workspace: WeakViewHandle, -// cx: &mut ViewContext, -// ) -> Self { -// cx.observe(&dock, |_, _, cx| cx.notify()).detach(); -// Self { dock, workspace } -// } -// } +impl PanelButtons { + pub fn new( + dock: View, + workspace: WeakView, + cx: &mut ViewContext, + ) -> Self { + cx.observe(&dock, |_, _, cx| cx.notify()).detach(); + Self { dock, workspace } + } +} impl EventEmitter for PanelButtons { type Event = (); diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 7846c7470a..9922bcdd26 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -1,35 +1,36 @@ use crate::{Toast, Workspace}; use collections::HashMap; -use gpui2::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle}; +use gpui2::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext}; use std::{any::TypeId, ops::DerefMut}; pub fn init(cx: &mut AppContext) { cx.set_global(NotificationTracker::new()); - simple_message_notification::init(cx); + // todo!() + // simple_message_notification::init(cx); } -pub trait Notification: View { - fn should_dismiss_notification_on_event(&self, event: &::Event) -> bool; +pub trait Notification: EventEmitter + Render { + fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool; } -pub trait NotificationHandle { - fn id(&self) -> usize; - fn as_any(&self) -> &AnyViewHandle; +pub trait NotificationHandle: Send { + fn id(&self) -> EntityId; + fn to_any(&self) -> AnyView; } -impl NotificationHandle for ViewHandle { - fn id(&self) -> usize { - self.id() +impl NotificationHandle for View { + fn id(&self) -> EntityId { + self.entity_id() } - fn as_any(&self) -> &AnyViewHandle { - self + fn to_any(&self) -> AnyView { + self.clone().into() } } -impl From<&dyn NotificationHandle> for AnyViewHandle { +impl From<&dyn NotificationHandle> for AnyView { fn from(val: &dyn NotificationHandle) -> Self { - val.as_any().clone() + val.to_any() } } @@ -75,14 +76,12 @@ impl Workspace { &mut self, id: usize, cx: &mut ViewContext, - build_notification: impl FnOnce(&mut ViewContext) -> ViewHandle, + build_notification: impl FnOnce(&mut ViewContext) -> View, ) { if !self.has_shown_notification_once::(id, cx) { - cx.update_global::(|tracker, _| { - let entry = tracker.entry(TypeId::of::()).or_default(); - entry.push(id); - }); - + let tracker = cx.global_mut::(); + let entry = tracker.entry(TypeId::of::()).or_default(); + entry.push(id); self.show_notification::(id, cx, build_notification) } } @@ -91,7 +90,7 @@ impl Workspace { &mut self, id: usize, cx: &mut ViewContext, - build_notification: impl FnOnce(&mut ViewContext) -> ViewHandle, + build_notification: impl FnOnce(&mut ViewContext) -> View, ) { let type_id = TypeId::of::(); if self @@ -121,22 +120,24 @@ impl Workspace { } pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext) { - self.dismiss_notification::(toast.id, cx); - self.show_notification(toast.id, cx, |cx| { - cx.add_view(|_cx| match toast.on_click.as_ref() { - Some((click_msg, on_click)) => { - let on_click = on_click.clone(); - simple_message_notification::MessageNotification::new(toast.msg.clone()) - .with_click_message(click_msg.clone()) - .on_click(move |cx| on_click(cx)) - } - None => simple_message_notification::MessageNotification::new(toast.msg.clone()), - }) - }) + todo!() + // self.dismiss_notification::(toast.id, cx); + // self.show_notification(toast.id, cx, |cx| { + // cx.add_view(|_cx| match toast.on_click.as_ref() { + // Some((click_msg, on_click)) => { + // let on_click = on_click.clone(); + // simple_message_notification::MessageNotification::new(toast.msg.clone()) + // .with_click_message(click_msg.clone()) + // .on_click(move |cx| on_click(cx)) + // } + // None => simple_message_notification::MessageNotification::new(toast.msg.clone()), + // }) + // }) } pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext) { - self.dismiss_notification::(id, cx); + todo!() + // self.dismiss_notification::(id, cx); } fn dismiss_notification_internal( @@ -159,20 +160,12 @@ impl Workspace { pub mod simple_message_notification { use super::Notification; - use crate::Workspace; - use gpui2::{ - actions, - elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, - fonts::TextStyle, - impl_actions, - platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, Entity, View, ViewContext, - }; - use menu::Cancel; + use gpui2::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext}; use serde::Deserialize; use std::{borrow::Cow, sync::Arc}; - actions!(message_notifications, [CancelMessageNotification]); + // todo!() + // actions!(message_notifications, [CancelMessageNotification]); #[derive(Clone, Default, Deserialize, PartialEq)] pub struct OsOpen(pub Cow<'static, str>); @@ -183,16 +176,18 @@ pub mod simple_message_notification { } } - impl_actions!(message_notifications, [OsOpen]); - - pub fn init(cx: &mut AppContext) { - cx.add_action(MessageNotification::dismiss); - cx.add_action( - |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext| { - cx.platform().open_url(open_action.0.as_ref()); - }, - ) - } + // todo!() + // impl_actions!(message_notifications, [OsOpen]); + // + // todo!() + // pub fn init(cx: &mut AppContext) { + // cx.add_action(MessageNotification::dismiss); + // cx.add_action( + // |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext| { + // cx.platform().open_url(open_action.0.as_ref()); + // }, + // ) + // } enum NotificationMessage { Text(Cow<'static, str>), @@ -201,7 +196,7 @@ pub mod simple_message_notification { pub struct MessageNotification { message: NotificationMessage, - on_click: Option)>>, + on_click: Option) + Send + Sync>>, click_message: Option>, } @@ -209,7 +204,7 @@ pub mod simple_message_notification { Dismiss, } - impl Entity for MessageNotification { + impl EventEmitter for MessageNotification { type Event = MessageNotificationEvent; } @@ -225,138 +220,147 @@ pub mod simple_message_notification { } } - pub fn new_element( - message: fn(TextStyle, &AppContext) -> AnyElement, - ) -> MessageNotification { - Self { - message: NotificationMessage::Element(message), - on_click: None, - click_message: None, - } - } + // todo!() + // pub fn new_element( + // message: fn(TextStyle, &AppContext) -> AnyElement, + // ) -> MessageNotification { + // Self { + // message: NotificationMessage::Element(message), + // on_click: None, + // click_message: None, + // } + // } - pub fn with_click_message(mut self, message: S) -> Self - where - S: Into>, - { - self.click_message = Some(message.into()); - self - } + // pub fn with_click_message(mut self, message: S) -> Self + // where + // S: Into>, + // { + // self.click_message = Some(message.into()); + // self + // } - pub fn on_click(mut self, on_click: F) -> Self - where - F: 'static + Fn(&mut ViewContext), - { - self.on_click = Some(Arc::new(on_click)); - self - } + // pub fn on_click(mut self, on_click: F) -> Self + // where + // F: 'static + Fn(&mut ViewContext), + // { + // self.on_click = Some(Arc::new(on_click)); + // self + // } - pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext) { - cx.emit(MessageNotificationEvent::Dismiss); - } + // pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext) { + // cx.emit(MessageNotificationEvent::Dismiss); + // } } - impl View for MessageNotification { - fn ui_name() -> &'static str { - "MessageNotification" - } + impl Render for MessageNotification { + type Element = Div; - fn render(&mut self, cx: &mut gpui2::ViewContext) -> gpui::AnyElement { - let theme = theme2::current(cx).clone(); - let theme = &theme.simple_message_notification; - - enum MessageNotificationTag {} - - let click_message = self.click_message.clone(); - let message = match &self.message { - NotificationMessage::Text(text) => { - Text::new(text.to_owned(), theme.message.text.clone()).into_any() - } - NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), - }; - let on_click = self.on_click.clone(); - let has_click_action = on_click.is_some(); - - Flex::column() - .with_child( - Flex::row() - .with_child( - message - .contained() - .with_style(theme.message.container) - .aligned() - .top() - .left() - .flex(1., true), - ) - .with_child( - MouseEventHandler::new::(0, cx, |state, _| { - let style = theme.dismiss_button.style_for(state); - Svg::new("icons/x.svg") - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .contained() - .with_style(style.container) - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_padding(Padding::uniform(5.)) - .on_click(MouseButton::Left, move |_, this, cx| { - this.dismiss(&Default::default(), cx); - }) - .with_cursor_style(CursorStyle::PointingHand) - .aligned() - .constrained() - .with_height(cx.font_cache().line_height(theme.message.text.font_size)) - .aligned() - .top() - .flex_float(), - ), - ) - .with_children({ - click_message - .map(|click_message| { - MouseEventHandler::new::( - 0, - cx, - |state, _| { - let style = theme.action_message.style_for(state); - - Flex::row() - .with_child( - Text::new(click_message, style.text.clone()) - .contained() - .with_style(style.container), - ) - .contained() - }, - ) - .on_click(MouseButton::Left, move |_, this, cx| { - if let Some(on_click) = on_click.as_ref() { - on_click(cx); - this.dismiss(&Default::default(), cx); - } - }) - // Since we're not using a proper overlay, we have to capture these extra events - .on_down(MouseButton::Left, |_, _, _| {}) - .on_up(MouseButton::Left, |_, _, _| {}) - .with_cursor_style(if has_click_action { - CursorStyle::PointingHand - } else { - CursorStyle::Arrow - }) - }) - .into_iter() - }) - .into_any() + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + todo!() } } + // todo!() + // impl View for MessageNotification { + // fn ui_name() -> &'static str { + // "MessageNotification" + // } + + // fn render(&mut self, cx: &mut gpui2::ViewContext) -> gpui::AnyElement { + // let theme = theme2::current(cx).clone(); + // let theme = &theme.simple_message_notification; + + // enum MessageNotificationTag {} + + // let click_message = self.click_message.clone(); + // let message = match &self.message { + // NotificationMessage::Text(text) => { + // Text::new(text.to_owned(), theme.message.text.clone()).into_any() + // } + // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), + // }; + // let on_click = self.on_click.clone(); + // let has_click_action = on_click.is_some(); + + // Flex::column() + // .with_child( + // Flex::row() + // .with_child( + // message + // .contained() + // .with_style(theme.message.container) + // .aligned() + // .top() + // .left() + // .flex(1., true), + // ) + // .with_child( + // MouseEventHandler::new::(0, cx, |state, _| { + // let style = theme.dismiss_button.style_for(state); + // Svg::new("icons/x.svg") + // .with_color(style.color) + // .constrained() + // .with_width(style.icon_width) + // .aligned() + // .contained() + // .with_style(style.container) + // .constrained() + // .with_width(style.button_width) + // .with_height(style.button_width) + // }) + // .with_padding(Padding::uniform(5.)) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.dismiss(&Default::default(), cx); + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .aligned() + // .constrained() + // .with_height(cx.font_cache().line_height(theme.message.text.font_size)) + // .aligned() + // .top() + // .flex_float(), + // ), + // ) + // .with_children({ + // click_message + // .map(|click_message| { + // MouseEventHandler::new::( + // 0, + // cx, + // |state, _| { + // let style = theme.action_message.style_for(state); + + // Flex::row() + // .with_child( + // Text::new(click_message, style.text.clone()) + // .contained() + // .with_style(style.container), + // ) + // .contained() + // }, + // ) + // .on_click(MouseButton::Left, move |_, this, cx| { + // if let Some(on_click) = on_click.as_ref() { + // on_click(cx); + // this.dismiss(&Default::default(), cx); + // } + // }) + // // Since we're not using a proper overlay, we have to capture these extra events + // .on_down(MouseButton::Left, |_, _, _| {}) + // .on_up(MouseButton::Left, |_, _, _| {}) + // .with_cursor_style(if has_click_action { + // CursorStyle::PointingHand + // } else { + // CursorStyle::Arrow + // }) + // }) + // .into_iter() + // }) + // .into_any() + // } + // } impl Notification for MessageNotification { - fn should_dismiss_notification_on_event(&self, event: &::Event) -> bool { + fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool { match event { MessageNotificationEvent::Dismiss => true, } @@ -384,15 +388,15 @@ where match self { Ok(value) => Some(value), Err(err) => { - workspace.show_notification(0, cx, |cx| { - cx.add_view(|_cx| { - simple_message_notification::MessageNotification::new(format!( - "Error: {:?}", - err, - )) - }) - }); - + log::error!("TODO {err:?}"); + // todo!() + // workspace.show_notification(0, cx, |cx| { + // cx.add_view(|_cx| { + // simple_message_notification::MessageNotification::new(format!( + // "Error: {err:?}", + // )) + // }) + // }); None } } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 22c3833719..dda3f70a14 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -168,7 +168,7 @@ impl fmt::Debug for Event { pub struct Pane { items: Vec>, activation_history: Vec, - // zoomed: bool, + zoomed: bool, active_item_index: usize, // last_focused_view_by_item: HashMap, autoscroll: bool, @@ -220,7 +220,7 @@ impl Default for NavigationMode { pub struct NavigationEntry { pub item: Arc, - pub data: Option>, + pub data: Option>, pub timestamp: usize, } @@ -327,7 +327,7 @@ impl Pane { Self { items: Vec::new(), activation_history: Vec::new(), - // zoomed: false, + zoomed: false, active_item_index: 0, // last_focused_view_by_item: Default::default(), autoscroll: false, @@ -648,9 +648,9 @@ impl Pane { // }) // } - // pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { - // self.items.iter().position(|i| i.id() == item.id()) - // } + pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { + self.items.iter().position(|i| i.id() == item.id()) + } // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { // // Potentially warn the user of the new keybinding @@ -994,77 +994,73 @@ impl Pane { // }) // } - // pub fn remove_item( - // &mut self, - // item_index: usize, - // activate_pane: bool, - // cx: &mut ViewContext, - // ) { - // self.activation_history - // .retain(|&history_entry| history_entry != self.items[item_index].id()); + pub fn remove_item( + &mut self, + item_index: usize, + activate_pane: bool, + cx: &mut ViewContext, + ) { + self.activation_history + .retain(|&history_entry| history_entry != self.items[item_index].id()); - // if item_index == self.active_item_index { - // let index_to_activate = self - // .activation_history - // .pop() - // .and_then(|last_activated_item| { - // self.items.iter().enumerate().find_map(|(index, item)| { - // (item.id() == last_activated_item).then_some(index) - // }) - // }) - // // We didn't have a valid activation history entry, so fallback - // // to activating the item to the left - // .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); + 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 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); + 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); - // } + 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; - // } + 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); + 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()); + if let Some(path) = item.project_path(cx) { + let abs_path = self + .nav_history + .0 + .lock() + .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()); - // } + self.nav_history + .0 + .lock() + .paths_by_item + .insert(item.id(), (path, abs_path)); + } else { + self.nav_history.0.lock().paths_by_item.remove(&item.id()); + } - // if self.items.is_empty() && self.zoomed { - // cx.emit(Event::ZoomOut); - // } + if self.items.is_empty() && self.zoomed { + cx.emit(Event::ZoomOut); + } - // cx.notify(); - // } + cx.notify(); + } // pub async fn save_item( // project: Model, @@ -1314,28 +1310,28 @@ impl Pane { // }); // } - // pub fn toolbar(&self) -> &ViewHandle { - // &self.toolbar - // } + pub fn toolbar(&self) -> &View { + &self.toolbar + } - // pub fn handle_deleted_project_item( - // &mut self, - // entry_id: ProjectEntryId, - // cx: &mut ViewContext, - // ) -> Option<()> { - // let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { - // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { - // Some((i, item.id())) - // } else { - // None - // } - // })?; + pub fn handle_deleted_project_item( + &mut self, + entry_id: ProjectEntryId, + cx: &mut ViewContext, + ) -> Option<()> { + let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + Some((i, item.id())) + } else { + None + } + })?; - // self.remove_item(item_index_to_delete, false, cx); - // self.nav_history.remove_item(item_id); + self.remove_item(item_index_to_delete, false, cx); + self.nav_history.remove_item(item_id); - // Some(()) - // } + Some(()) + } fn update_toolbar(&mut self, cx: &mut ViewContext) { let active_item = self diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 99cc45572b..504f106dfd 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,6 +1,6 @@ pub mod dock; pub mod item; -// pub mod notifications; +pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; @@ -10,6 +10,10 @@ mod status_bar; mod toolbar; mod workspace_settings; +use crate::persistence::model::{ + DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, + SerializedWorkspace, +}; use anyhow::{anyhow, Result}; use call2::ActiveCall; use client2::{ @@ -17,19 +21,22 @@ use client2::{ Client, TypedEnvelope, UserStore, }; use collections::{HashMap, HashSet}; -use dock::Dock; +use dock::{Dock, DockPosition, PanelButtons}; use futures::{ channel::{mpsc, oneshot}, - FutureExt, + FutureExt, Stream, StreamExt, }; use gpui2::{ - div, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity, - EventEmitter, MainThread, Model, ModelContext, Render, Subscription, Task, View, ViewContext, - VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, + Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; +use lazy_static::lazy_static; use node_runtime::NodeRuntime; +use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; pub use pane::*; pub use pane_group::*; use persistence::{ @@ -37,8 +44,12 @@ use persistence::{ DB, }; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; +use serde::Deserialize; +use status_bar::StatusBar; use std::{ any::TypeId, + borrow::Cow, + env, path::{Path, PathBuf}, sync::{atomic::AtomicUsize, Arc}, time::Duration, @@ -47,21 +58,16 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use uuid::Uuid; -use crate::persistence::model::{ - DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, - SerializedWorkspace, -}; - -// lazy_static! { -// static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") -// .ok() -// .as_deref() -// .and_then(parse_pixel_position_env_var); -// static ref ZED_WINDOW_POSITION: Option = env::var("ZED_WINDOW_POSITION") -// .ok() -// .as_deref() -// .and_then(parse_pixel_position_env_var); -// } +lazy_static! { + static ref ZED_WINDOW_SIZE: Option> = env::var("ZED_WINDOW_SIZE") + .ok() + .as_deref() + .and_then(parse_pixel_size_env_var); + static ref ZED_WINDOW_POSITION: Option> = env::var("ZED_WINDOW_POSITION") + .ok() + .as_deref() + .and_then(parse_pixel_position_env_var); +} // pub trait Modal: View { // fn has_focus(&self) -> bool; @@ -151,13 +157,13 @@ use crate::persistence::model::{ // pub save_intent: Option, // } -// #[derive(Deserialize)] -// pub struct Toast { -// id: usize, -// msg: Cow<'static, str>, -// #[serde(skip)] -// on_click: Option<(Cow<'static, str>, Arc)>, -// } +#[derive(Deserialize)] +pub struct Toast { + id: usize, + msg: Cow<'static, str>, + #[serde(skip)] + on_click: Option<(Cow<'static, str>, Arc)>, +} // impl Toast { // pub fn new>>(id: usize, msg: I) -> Self { @@ -336,7 +342,7 @@ pub type WorkspaceId = i64; // err.notify_err(workspace, cx); // } else { // workspace.show_notification(1, cx, |cx| { -// cx.add_view(|_| { +// cx.build_view(|_| { // MessageNotification::new("Successfully installed the `zed` binary") // }) // }); @@ -418,7 +424,7 @@ pub struct AppState { pub workspace_store: Model, pub fs: Arc, pub build_window_options: - fn(Option, Option, MainThread) -> WindowOptions, + fn(Option, Option, &mut MainThread) -> WindowOptions, pub initialize_workspace: fn( WeakView, bool, @@ -539,24 +545,24 @@ pub struct Workspace { right_dock: View, panes: Vec>, panes_by_item: HashMap>, - // active_pane: View, + active_pane: View, last_active_center_pane: Option>, // last_active_view_id: Option, // status_bar: View, // titlebar_item: Option, - // notifications: Vec<(TypeId, usize, Box)>, + notifications: Vec<(TypeId, usize, Box)>, project: Model, follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, - // window_edited: bool, + window_edited: bool, active_call: Option<(Model, 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>, + subscriptions: Vec, + _apply_leader_updates: Task>, + _observe_current_user: Task>, + _schedule_serialize: Option>, pane_history_timestamp: Arc, } @@ -581,200 +587,205 @@ struct FollowerState { enum WorkspaceBounds {} impl Workspace { - // pub fn new( - // workspace_id: WorkspaceId, - // project: ModelHandle, - // app_state: Arc, - // cx: &mut ViewContext, - // ) -> Self { - // cx.observe(&project, |_, _, cx| cx.notify()).detach(); - // cx.subscribe(&project, move |this, _, event, cx| { - // match event { - // project::Event::RemoteIdChanged(_) => { - // this.update_window_title(cx); - // } + pub fn new( + workspace_id: WorkspaceId, + project: Model, + app_state: Arc, + cx: &mut ViewContext, + ) -> Self { + cx.observe(&project, |_, _, cx| cx.notify()).detach(); + cx.subscribe(&project, move |this, _, event, cx| { + match event { + project2::Event::RemoteIdChanged(_) => { + this.update_window_title(cx); + } - // project::Event::CollaboratorLeft(peer_id) => { - // this.collaborator_left(*peer_id, cx); - // } + project2::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); - // } + project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => { + this.update_window_title(cx); + this.serialize_workspace(cx); + } - // project::Event::DisconnectedFromHost => { - // this.update_window_edited(cx); - // cx.blur(); - // } + project2::Event::DisconnectedFromHost => { + this.update_window_edited(cx); + cx.blur(); + } - // project::Event::Closed => { - // cx.remove_window(); - // } + project2::Event::Closed => { + // todo!() + // 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) - // }); - // } - // } + project2::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())) - // }), + project2::Event::Notification(message) => this.show_notification(0, cx, |cx| { + cx.build_view(|_| MessageNotification::new(message.clone())) + }), - // _ => {} - // } - // cx.notify() - // }) - // .detach(); + _ => {} + } + cx.notify() + }) + .detach(); - // let weak_handle = cx.weak_handle(); - // let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); + let weak_handle = cx.view().downgrade(); + 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())); + let center_pane = cx.build_view(|cx| { + Pane::new( + weak_handle.clone(), + project.clone(), + pane_history_timestamp.clone(), + cx, + ) + }); + cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); + // todo!() + // 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 window_handle = cx.window_handle().downcast::().unwrap(); + app_state.workspace_store.update(cx, |store, _| { + store.workspaces.insert(window_handle); + }); - // 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)); + 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.next().await; + connection_status.next().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(()) - // }); + 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(); - // } + // 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(()) - // }); + Ok(()) + }); - // cx.emit_global(WorkspaceCreated(weak_handle.clone())); + // todo!("replace with a different mechanism") + // 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 - // }); + let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left)); + let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom)); + let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right)); + let left_dock_buttons = + cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); + let bottom_dock_buttons = + cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); + let right_dock_buttons = + cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); + let status_bar = cx.build_view(|cx| { + let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); + status_bar.add_left_item(left_dock_buttons, cx); + status_bar.add_right_item(right_dock_buttons, cx); + status_bar.add_right_item(bottom_dock_buttons, cx); + status_bar + }); - // cx.update_default_global::, _, _>(|drag_and_drop, _| { - // drag_and_drop.register_container(weak_handle.clone()); - // }); + // todo!() + // cx.update_default_global::, _, _>(|drag_and_drop, _| { + // drag_and_drop.register_container(weak_handle.clone()); + // }); - // let mut active_call = None; - // if cx.has_global::>() { - // let call = cx.global::>().clone(); - // let mut subscriptions = Vec::new(); - // subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); - // active_call = Some((call, subscriptions)); - // } + let mut active_call = None; + if cx.has_global::>() { + let call = cx.global::>().clone(); + let mut subscriptions = Vec::new(); + subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); + active_call = Some((call, subscriptions)); + } - // let subscriptions = vec![ - // cx.observe_fullscreen(|_, _, cx| cx.notify()), - // cx.observe_window_activation(Self::on_window_activation_changed), - // cx.observe_window_bounds(move |_, mut bounds, display, cx| { - // // Transform fixed bounds to be stored in terms of the containing display - // if let WindowBounds::Fixed(mut window_bounds) = bounds { - // if let Some(screen) = cx.platform().screen_by_id(display) { - // let screen_bounds = screen.bounds(); - // window_bounds - // .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x()); - // window_bounds - // .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y()); - // bounds = WindowBounds::Fixed(window_bounds); - // } - // } + 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.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, - // } - // } + 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, @@ -832,55 +843,40 @@ impl Workspace { }); 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?; + 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.origin.x += screen_bounds.origin.x; - window_bounds.origin.y += screen_bounds.origin.y; - bounds = WindowBounds::Fixed(window_bounds); - } else { - // Screen no longer exists. Return none here. - return None; - } - } + // 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 { + let screen = + cx.update(|cx| cx.display_for_uuid(display)).ok()??; + let screen_bounds = screen.bounds(); + window_bounds.origin.x += screen_bounds.origin.x; + window_bounds.origin.y += screen_bounds.origin.y; + bounds = WindowBounds::Fixed(window_bounds); + } - Some((bounds, display)) - }) - .unzip() - }; + Some((bounds, display)) + }) + .unzip() + }; - // Use the serialized workspace to construct the new window - cx.open_window( - (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), - |cx| { - Workspace::new( - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ) - }, - ) - } + // Use the serialized workspace to construct the new window + let options = + cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?; + cx.open_window(options, |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(), @@ -1594,7 +1590,7 @@ impl Workspace { // pub fn toggle_modal( // &mut self, // cx: &mut ViewContext, - // add_view: F, + // build_view: F, // ) -> Option> // where // V: 'static + Modal, @@ -1610,7 +1606,7 @@ impl Workspace { // cx.focus_self(); // Some(already_open_modal) // } else { - // let modal = add_view(self, cx); + // let modal = build_view(self, cx); // cx.subscribe(&modal, |this, _, event, cx| { // if V::dismiss_on_event(event) { // this.dismiss_modal(cx); @@ -1647,12 +1643,12 @@ impl Workspace { // } // } - // pub fn items<'a>( - // &'a self, - // cx: &'a AppContext, - // ) -> impl 'a + Iterator> { - // self.panes.iter().flat_map(|pane| pane.read(cx).items()) - // } + pub fn items<'a>( + &'a self, + cx: &'a AppContext, + ) -> impl 'a + Iterator> { + self.panes.iter().flat_map(|pane| pane.read(cx).items()) + } // pub fn item_of_type(&self, cx: &AppContext) -> Option> { // self.items_of_type(cx).max_by_key(|item| item.id()) @@ -1667,9 +1663,9 @@ impl Workspace { // .flat_map(|pane| pane.read(cx).items_of_type()) // } - // pub fn active_item(&self, cx: &AppContext) -> Option> { - // self.active_pane().read(cx).active_item() - // } + pub fn active_item(&self, cx: &AppContext) -> Option> { + self.active_pane().read(cx).active_item() + } // fn active_project_path(&self, cx: &ViewContext) -> Option { // self.active_item(cx).and_then(|item| item.project_path(cx)) @@ -2133,7 +2129,7 @@ impl Workspace { // return item; // } - // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + // let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); // self.add_item(Box::new(item.clone()), cx); // item // } @@ -2157,7 +2153,7 @@ impl Workspace { // return item; // } - // let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + // let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); // self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); // item // } @@ -2282,64 +2278,65 @@ impl Workspace { // cx.notify(); // } - // fn handle_pane_event( - // &mut self, - // pane: View, - // event: &pane::Event, - // cx: &mut ViewContext, - // ) { - // match event { - // pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), - // pane::Event::Split(direction) => { - // self.split_and_clone(pane, *direction, cx); - // } - // pane::Event::Remove => self.remove_pane(pane, cx), - // pane::Event::ActivateItem { local } => { - // if *local { - // self.unfollow(&pane, cx); - // } - // if &pane == self.active_pane() { - // self.active_item_path_changed(cx); - // } - // } - // pane::Event::ChangeItemTitle => { - // if pane == self.active_pane { - // self.active_item_path_changed(cx); - // } - // self.update_window_edited(cx); - // } - // pane::Event::RemoveItem { item_id } => { - // self.update_window_edited(cx); - // if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { - // if entry.get().id() == pane.id() { - // entry.remove(); - // } - // } - // } - // pane::Event::Focus => { - // self.handle_pane_focused(pane.clone(), cx); - // } - // pane::Event::ZoomIn => { - // if pane == self.active_pane { - // pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - // if pane.read(cx).has_focus() { - // self.zoomed = Some(pane.downgrade().into_any()); - // self.zoomed_position = None; - // } - // cx.notify(); - // } - // } - // pane::Event::ZoomOut => { - // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - // if self.zoomed_position.is_none() { - // self.zoomed = None; - // } - // cx.notify(); - // } - // } + fn handle_pane_event( + &mut self, + pane: View, + event: &pane::Event, + cx: &mut ViewContext, + ) { + todo!() + // 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); - // } + // self.serialize_workspace(cx); + } // pub fn split_pane( // &mut self, @@ -2468,27 +2465,27 @@ impl Workspace { // } // } - // pub fn panes(&self) -> &[View] { - // &self.panes - // } + pub fn panes(&self) -> &[View] { + &self.panes + } - // pub fn active_pane(&self) -> &View { - // &self.active_pane - // } + pub fn active_pane(&self) -> &View { + &self.active_pane + } - // fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - // self.follower_states.retain(|_, state| { - // if state.leader_id == peer_id { - // for item in state.items_by_leader_view_id.values() { - // item.set_leader_peer_id(None, cx); - // } - // false - // } else { - // true - // } - // }); - // cx.notify(); - // } + fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + self.follower_states.retain(|_, state| { + if state.leader_id == peer_id { + for item in state.items_by_leader_view_id.values() { + item.set_leader_peer_id(None, cx); + } + false + } else { + true + } + }); + cx.notify(); + } // fn start_following( // &mut self, @@ -2703,60 +2700,62 @@ impl Workspace { // self.update_window_title(cx); // } - // fn update_window_title(&mut self, cx: &mut ViewContext) { - // let project = self.project().read(cx); - // let mut title = String::new(); + fn update_window_title(&mut self, cx: &mut ViewContext) { + let project = self.project().read(cx); + let mut title = String::new(); - // if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { - // let filename = path - // .path - // .file_name() - // .map(|s| s.to_string_lossy()) - // .or_else(|| { - // Some(Cow::Borrowed( - // project - // .worktree_for_id(path.worktree_id, cx)? - // .read(cx) - // .root_name(), - // )) - // }); + if let Some(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(" — "); - // } - // } + 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); - // } + 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 title.is_empty() { + title = "empty project".to_string(); + } - // if project.is_remote() { - // title.push_str(" ↙"); - // } else if project.is_shared() { - // title.push_str(" ↗"); - // } + if project.is_remote() { + title.push_str(" ↙"); + } else if project.is_shared() { + title.push_str(" ↗"); + } - // cx.set_window_title(&title); - // } + todo!() + // cx.set_window_title(&title); + } - // fn update_window_edited(&mut self, cx: &mut ViewContext) { - // let is_edited = !self.project.read(cx).is_read_only() - // && self - // .items(cx) - // .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); - // if is_edited != self.window_edited { - // self.window_edited = is_edited; - // cx.set_window_edited(self.window_edited) - // } - // } + fn update_window_edited(&mut self, cx: &mut ViewContext) { + let is_edited = !self.project.read(cx).is_read_only() + && self + .items(cx) + .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); + if is_edited != self.window_edited { + self.window_edited = is_edited; + todo!() + // cx.set_window_edited(self.window_edited) + } + } // fn render_disconnected_overlay( // &self, @@ -2875,64 +2874,64 @@ impl Workspace { .ok(); } - // async fn process_leader_update( - // this: &WeakView, - // 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 process_leader_update( + this: &WeakView, + 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: WeakView, @@ -3042,71 +3041,72 @@ impl Workspace { self.follower_states.get(pane).map(|state| state.leader_id) } - // fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - // cx.notify(); + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); - // let call = self.active_call()?; - // let room = call.read(cx).room()?.read(cx); - // let participant = room.remote_participant_for_peer_id(leader_id)?; - // let mut items_to_activate = Vec::new(); + let 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; - // } - // }; + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + call2::ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); + } + call2::ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + call2::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, 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; + } + // todo!() + // 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) - // }); - // } + 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)); - // } - // } + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } - // None - // } + None + } // fn shared_screen_for_peer( // &self, @@ -3126,7 +3126,7 @@ impl Workspace { // } // } - // Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) // } // pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { @@ -3159,24 +3159,24 @@ impl Workspace { self.active_call.as_ref().map(|(call, _)| call) } - // fn on_active_call_event( - // &mut self, - // _: ModelHandle, - // event: &call::room::Event, - // cx: &mut ViewContext, - // ) { - // match event { - // call::room::Event::ParticipantLocationChanged { participant_id } - // | call::room::Event::RemoteVideoTracksChanged { participant_id } => { - // self.leader_updated(*participant_id, cx); - // } - // _ => {} - // } - // } + fn on_active_call_event( + &mut self, + _: Model, + event: &call2::room::Event, + cx: &mut ViewContext, + ) { + match event { + call2::room::Event::ParticipantLocationChanged { participant_id } + | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + self.leader_updated(*participant_id, cx); + } + _ => {} + } + } - // pub fn database_id(&self) -> WorkspaceId { - // self.database_id - // } + pub fn database_id(&self) -> WorkspaceId { + self.database_id + } fn location(&self, cx: &AppContext) -> Option { let project = self.project().read(cx); @@ -3538,191 +3538,194 @@ impl Workspace { // ) // } // } - - // fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { - // ZED_WINDOW_POSITION - // .zip(*ZED_WINDOW_SIZE) - // .map(|(position, size)| { - // WindowBounds::Fixed(RectF::new( - // cx.platform().screens()[0].bounds().origin() + position, - // size, - // )) - // }) - // } - - // async fn open_items( - // serialized_workspace: Option, - // workspace: &WeakView, - // mut project_paths_to_open: Vec<(PathBuf, Option)>, - // app_state: Arc, - // mut cx: AsyncAppContext, - // ) -> Result>>>> { - // let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - - // if let Some(serialized_workspace) = serialized_workspace { - // let workspace = workspace.clone(); - // let restored_items = cx - // .update(|cx| { - // Workspace::load_workspace( - // workspace, - // serialized_workspace, - // project_paths_to_open - // .iter() - // .map(|(_, project_path)| project_path) - // .cloned() - // .collect(), - // cx, - // ) - // }) - // .await?; - - // let restored_project_paths = cx.read(|cx| { - // restored_items - // .iter() - // .filter_map(|item| item.as_ref()?.project_path(cx)) - // .collect::>() - // }); - - // for restored_item in restored_items { - // opened_items.push(restored_item.map(Ok)); - // } - - // project_paths_to_open - // .iter_mut() - // .for_each(|(_, project_path)| { - // if let Some(project_path_to_open) = project_path { - // if restored_project_paths.contains(project_path_to_open) { - // *project_path = None; - // } - // } - // }); - // } else { - // for _ in 0..project_paths_to_open.len() { - // opened_items.push(None); - // } - // } - // assert!(opened_items.len() == project_paths_to_open.len()); - - // let tasks = - // project_paths_to_open - // .into_iter() - // .enumerate() - // .map(|(i, (abs_path, project_path))| { - // let workspace = workspace.clone(); - // cx.spawn(|mut cx| { - // let fs = app_state.fs.clone(); - // async move { - // let file_project_path = project_path?; - // if fs.is_file(&abs_path).await { - // Some(( - // i, - // workspace - // .update(&mut cx, |workspace, cx| { - // workspace.open_path(file_project_path, None, true, cx) - // }) - // .log_err()? - // .await, - // )) - // } else { - // None - // } - // } - // }) - // }); - - // for maybe_opened_path in futures::future::join_all(tasks.into_iter()) - // .await - // .into_iter() - // { - // if let Some((i, path_open_result)) = maybe_opened_path { - // opened_items[i] = Some(path_open_result); - // } - // } - - // Ok(opened_items) - // } - - // fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { - // const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; - // const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; - // const MESSAGE_ID: usize = 2; - - // if workspace - // .read_with(cx, |workspace, cx| { - // workspace.has_shown_notification_once::(MESSAGE_ID, cx) - // }) - // .unwrap_or(false) - // { - // return; - // } - - // if db::kvp::KEY_VALUE_STORE - // .read_kvp(NEW_DOCK_HINT_KEY) - // .ok() - // .flatten() - // .is_some() - // { - // if !workspace - // .read_with(cx, |workspace, cx| { - // workspace.has_shown_notification_once::(MESSAGE_ID, cx) - // }) - // .unwrap_or(false) - // { - // cx.update(|cx| { - // cx.update_global::(|tracker, _| { - // let entry = tracker - // .entry(TypeId::of::()) - // .or_default(); - // if !entry.contains(&MESSAGE_ID) { - // entry.push(MESSAGE_ID); - // } - // }); - // }); - // } - - // return; - // } - - // cx.spawn(|_| async move { - // db::kvp::KEY_VALUE_STORE - // .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) - // .await - // .ok(); - // }) - // .detach(); - - // workspace - // .update(cx, |workspace, cx| { - // workspace.show_notification_once(2, cx, |cx| { - // cx.add_view(|_| { - // MessageNotification::new_element(|text, _| { - // Text::new( - // "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", - // text, - // ) - // .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { - // let code_span_background_color = settings::get::(cx) - // .theme - // .editor - // .document_highlight_read_background; - - // cx.scene().push_quad(gpui::Quad { - // bounds, - // background: Some(code_span_background_color), - // border: Default::default(), - // corner_radii: (2.0).into(), - // }) - // }) - // .into_any() - // }) - // .with_click_message("Read more about the new panel system") - // .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) - // }) - // }) - // }) - // .ok(); } +fn window_bounds_env_override(cx: &MainThread) -> Option { + let display_origin = cx + .update(|cx| Some(cx.displays().first()?.bounds().origin)) + .ok()??; + ZED_WINDOW_POSITION + .zip(*ZED_WINDOW_SIZE) + .map(|(position, size)| { + WindowBounds::Fixed(Bounds { + origin: display_origin + position, + size, + }) + }) +} + +// async fn open_items( +// serialized_workspace: Option, +// workspace: &WeakView, +// mut project_paths_to_open: Vec<(PathBuf, Option)>, +// app_state: Arc, +// mut cx: AsyncAppContext, +// ) -> Result>>>> { +// let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); + +// if let Some(serialized_workspace) = serialized_workspace { +// let workspace = workspace.clone(); +// let restored_items = cx +// .update(|cx| { +// Workspace::load_workspace( +// workspace, +// serialized_workspace, +// project_paths_to_open +// .iter() +// .map(|(_, project_path)| project_path) +// .cloned() +// .collect(), +// cx, +// ) +// }) +// .await?; + +// let restored_project_paths = cx.read(|cx| { +// restored_items +// .iter() +// .filter_map(|item| item.as_ref()?.project_path(cx)) +// .collect::>() +// }); + +// for restored_item in restored_items { +// opened_items.push(restored_item.map(Ok)); +// } + +// project_paths_to_open +// .iter_mut() +// .for_each(|(_, project_path)| { +// if let Some(project_path_to_open) = project_path { +// if restored_project_paths.contains(project_path_to_open) { +// *project_path = None; +// } +// } +// }); +// } else { +// for _ in 0..project_paths_to_open.len() { +// opened_items.push(None); +// } +// } +// assert!(opened_items.len() == project_paths_to_open.len()); + +// let tasks = +// project_paths_to_open +// .into_iter() +// .enumerate() +// .map(|(i, (abs_path, project_path))| { +// let workspace = workspace.clone(); +// cx.spawn(|mut cx| { +// let fs = app_state.fs.clone(); +// async move { +// let file_project_path = project_path?; +// if fs.is_file(&abs_path).await { +// Some(( +// i, +// workspace +// .update(&mut cx, |workspace, cx| { +// workspace.open_path(file_project_path, None, true, cx) +// }) +// .log_err()? +// .await, +// )) +// } else { +// None +// } +// } +// }) +// }); + +// for maybe_opened_path in futures::future::join_all(tasks.into_iter()) +// .await +// .into_iter() +// { +// if let Some((i, path_open_result)) = maybe_opened_path { +// opened_items[i] = Some(path_open_result); +// } +// } + +// Ok(opened_items) +// } + +// fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { +// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; +// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; +// const MESSAGE_ID: usize = 2; + +// if workspace +// .read_with(cx, |workspace, cx| { +// workspace.has_shown_notification_once::(MESSAGE_ID, cx) +// }) +// .unwrap_or(false) +// { +// return; +// } + +// if db::kvp::KEY_VALUE_STORE +// .read_kvp(NEW_DOCK_HINT_KEY) +// .ok() +// .flatten() +// .is_some() +// { +// if !workspace +// .read_with(cx, |workspace, cx| { +// workspace.has_shown_notification_once::(MESSAGE_ID, cx) +// }) +// .unwrap_or(false) +// { +// cx.update(|cx| { +// cx.update_global::(|tracker, _| { +// let entry = tracker +// .entry(TypeId::of::()) +// .or_default(); +// if !entry.contains(&MESSAGE_ID) { +// entry.push(MESSAGE_ID); +// } +// }); +// }); +// } + +// return; +// } + +// cx.spawn(|_| async move { +// db::kvp::KEY_VALUE_STORE +// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) +// .await +// .ok(); +// }) +// .detach(); + +// workspace +// .update(cx, |workspace, cx| { +// workspace.show_notification_once(2, cx, |cx| { +// cx.build_view(|_| { +// MessageNotification::new_element(|text, _| { +// Text::new( +// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", +// text, +// ) +// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { +// let code_span_background_color = settings::get::(cx) +// .theme +// .editor +// .document_highlight_read_background; + +// cx.scene().push_quad(gpui::Quad { +// bounds, +// background: Some(code_span_background_color), +// border: Default::default(), +// corner_radii: (2.0).into(), +// }) +// }) +// .into_any() +// }) +// .with_click_message("Read more about the new panel system") +// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) +// }) +// }) +// }) +// .ok(); + // fn notify_if_database_failed(workspace: &WeakView, 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"; @@ -3730,7 +3733,7 @@ impl Workspace { // .update(cx, |workspace, cx| { // if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { // workspace.show_notification_once(0, cx, |cx| { -// cx.add_view(|_| { +// cx.build_view(|_| { // MessageNotification::new("Failed to load the database file.") // .with_click_message("Click to let us know about this error") // .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) @@ -4541,12 +4544,19 @@ pub fn open_new( // .detach_and_log_err(cx); // } -// fn parse_pixel_position_env_var(value: &str) -> Option { -// let mut parts = value.split(','); -// let width: usize = parts.next()?.parse().ok()?; -// let height: usize = parts.next()?.parse().ok()?; -// Some(vec2f(width as f32, height as f32)) -// } +fn parse_pixel_position_env_var(value: &str) -> Option> { + let mut parts = value.split(','); + let x: usize = parts.next()?.parse().ok()?; + let y: usize = parts.next()?.parse().ok()?; + Some(point((x as f64).into(), (y as f64).into())) +} + +fn parse_pixel_size_env_var(value: &str) -> Option> { + let mut parts = value.split(','); + let width: usize = parts.next()?.parse().ok()?; + let height: usize = parts.next()?.parse().ok()?; + Some(size((width as f64).into(), (height as f64).into())) +} // #[cfg(test)] // mod tests { @@ -4572,7 +4582,7 @@ pub fn open_new( // let workspace = window.root(cx); // // Adding an item with no ambiguity renders the tab without detail. -// let item1 = window.add_view(cx, |_| { +// let item1 = window.build_view(cx, |_| { // let mut item = TestItem::new(); // item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]); // item @@ -4584,7 +4594,7 @@ pub fn open_new( // // Adding an item that creates ambiguity increases the level of detail on // // both tabs. -// let item2 = window.add_view(cx, |_| { +// let item2 = window.build_view(cx, |_| { // let mut item = TestItem::new(); // item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); // item @@ -4598,7 +4608,7 @@ pub fn open_new( // // Adding an item that creates ambiguity increases the level of detail only // // on the ambiguous tabs. In this case, the ambiguity can't be resolved so // // we stop at the highest detail available. -// let item3 = window.add_view(cx, |_| { +// let item3 = window.build_view(cx, |_| { // let mut item = TestItem::new(); // item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); // item @@ -4640,10 +4650,10 @@ pub fn open_new( // project.worktrees(cx).next().unwrap().read(cx).id() // }); -// let item1 = window.add_view(cx, |cx| { +// let item1 = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) // }); -// let item2 = window.add_view(cx, |cx| { +// let item2 = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) // }); @@ -4716,15 +4726,15 @@ pub fn open_new( // let workspace = window.root(cx); // // When there are no dirty items, there's nothing to do. -// let item1 = window.add_view(cx, |_| TestItem::new()); +// let item1 = window.build_view(cx, |_| TestItem::new()); // workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); // let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); // assert!(task.await.unwrap()); // // When there are dirty untitled items, prompt to save each one. If the user // // cancels any prompt, then abort. -// let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true)); -// let item3 = window.add_view(cx, |cx| { +// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true)); +// let item3 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) @@ -4753,24 +4763,24 @@ pub fn open_new( // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); -// let item1 = window.add_view(cx, |cx| { +// let item1 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); -// let item2 = window.add_view(cx, |cx| { +// let item2 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_conflict(true) // .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) // }); -// let item3 = window.add_view(cx, |cx| { +// let item3 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_conflict(true) // .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) // }); -// let item4 = window.add_view(cx, |cx| { +// let item4 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new_untitled(cx)]) @@ -4864,7 +4874,7 @@ pub fn open_new( // // workspace items with multiple project entries. // let single_entry_items = (0..=4) // .map(|project_entry_id| { -// window.add_view(cx, |cx| { +// window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_project_items(&[TestProjectItem::new( @@ -4875,7 +4885,7 @@ pub fn open_new( // }) // }) // .collect::>(); -// let item_2_3 = window.add_view(cx, |cx| { +// let item_2_3 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_singleton(false) @@ -4884,7 +4894,7 @@ pub fn open_new( // single_entry_items[3].read(cx).project_items[0].clone(), // ]) // }); -// let item_3_4 = window.add_view(cx, |cx| { +// let item_3_4 = window.build_view(cx, |cx| { // TestItem::new() // .with_dirty(true) // .with_singleton(false) @@ -4971,7 +4981,7 @@ pub fn open_new( // let workspace = window.root(cx); // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); -// let item = window.add_view(cx, |cx| { +// let item = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); // let item_id = item.id(); @@ -5091,7 +5101,7 @@ pub fn open_new( // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); // let workspace = window.root(cx); -// let item = window.add_view(cx, |cx| { +// let item = window.build_view(cx, |cx| { // TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) // }); // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); @@ -5146,7 +5156,7 @@ pub fn open_new( // let workspace = window.root(cx); // let panel = workspace.update(cx, |workspace, cx| { -// let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right)); +// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right)); // workspace.add_panel(panel.clone(), cx); // workspace @@ -5158,7 +5168,7 @@ pub fn open_new( // let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // pane.update(cx, |pane, cx| { -// let item = cx.add_view(|_| TestItem::new()); +// let item = cx.build_view(|_| TestItem::new()); // pane.add_item(Box::new(item), true, true, None, cx); // }); @@ -5295,12 +5305,12 @@ pub fn open_new( // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { // // Add panel_1 on the left, panel_2 on the right. -// let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left)); +// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left)); // workspace.add_panel(panel_1.clone(), cx); // workspace // .left_dock() // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); -// let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right)); +// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right)); // workspace.add_panel(panel_2.clone(), cx); // workspace // .right_dock() @@ -5448,7 +5458,7 @@ pub fn open_new( // // If focus is transferred to another view that's not a panel or another pane, we still show // // the panel as zoomed. -// let focus_receiver = window.add_view(cx, |_| EmptyView); +// let focus_receiver = window.build_view(cx, |_| EmptyView); // focus_receiver.update(cx, |_, cx| cx.focus_self()); // workspace.read_with(cx, |workspace, _| { // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); From 73c97d0c10cc79a2674393130706d9629046aa95 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 1 Nov 2023 14:47:29 +0200 Subject: [PATCH 32/66] WIP Uncomment more methods in workspace2.rs --- crates/workspace2/src/dock.rs | 74 ++-- crates/workspace2/src/item.rs | 4 +- crates/workspace2/src/pane.rs | 12 +- crates/workspace2/src/workspace2.rs | 628 ++++++++++++++-------------- crates/zed2/src/zed2.rs | 2 +- 5 files changed, 363 insertions(+), 357 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index a83b133cb3..35aac2fb3c 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,7 +1,7 @@ use crate::{status_bar::StatusItemView, Axis, Workspace}; use gpui2::{ - Action, AnyView, Div, Entity, EntityId, EventEmitter, Render, Subscription, View, ViewContext, - WeakView, WindowContext, + Action, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, Render, Subscription, View, + ViewContext, WeakView, WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -212,28 +212,28 @@ impl Dock { // .position(|entry| entry.panel.as_any().is::()) // } - // pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option { - // todo!() - // // self.panel_entries.iter().position(|entry| { - // // let panel = entry.panel.as_any(); - // // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) - // // }) - // } + pub fn panel_index_for_ui_name(&self, _ui_name: &str, _cx: &AppContext) -> Option { + todo!() + // self.panel_entries.iter().position(|entry| { + // let panel = entry.panel.as_any(); + // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name) + // }) + } // pub fn active_panel_index(&self) -> usize { // self.active_panel_index // } - // pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { - // if open != self.is_open { - // self.is_open = open; - // if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { - // active_panel.panel.set_active(open, cx); - // } + pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { + if open != self.is_open { + self.is_open = open; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(open, cx); + } - // cx.notify(); - // } - // } + cx.notify(); + } + } // pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { // for entry in &mut self.panel_entries { @@ -314,29 +314,29 @@ impl Dock { // self.panel_entries.len() // } - // pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { - // if panel_ix != self.active_panel_index { - // if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { - // active_panel.panel.set_active(false, cx); - // } + pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext) { + if panel_ix != self.active_panel_index { + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(false, cx); + } - // self.active_panel_index = panel_ix; - // if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { - // active_panel.panel.set_active(true, cx); - // } + self.active_panel_index = panel_ix; + if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { + active_panel.panel.set_active(true, cx); + } - // cx.notify(); - // } - // } + cx.notify(); + } + } pub fn visible_panel(&self) -> Option<&Arc> { let entry = self.visible_entry()?; Some(&entry.panel) } - // pub fn active_panel(&self) -> Option<&Arc> { - // Some(&self.panel_entries.get(self.active_panel_index)?.panel) - // } + pub fn active_panel(&self) -> Option<&Arc> { + Some(&self.panel_entries.get(self.active_panel_index)?.panel) + } fn visible_entry(&self) -> Option<&PanelEntry> { if self.is_open { @@ -599,7 +599,7 @@ impl EventEmitter for PanelButtons { impl Render for PanelButtons { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { todo!() } } @@ -607,8 +607,8 @@ impl Render for PanelButtons { impl StatusItemView for PanelButtons { fn set_active_pane_item( &mut self, - active_pane_item: Option<&dyn crate::ItemHandle>, - cx: &mut ViewContext, + _active_pane_item: Option<&dyn crate::ItemHandle>, + _cx: &mut ViewContext, ) { todo!() } @@ -656,7 +656,7 @@ pub mod test { impl Render for TestPanel { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { div() } } diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 02fb7dc08a..258d32218b 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -401,7 +401,7 @@ impl ItemHandle for View { let pending_update = Arc::new(Mutex::new(None)); let pending_update_scheduled = Arc::new(AtomicBool::new(false)); - let mut event_subscription = + let mut _event_subscription = Some(cx.subscribe(self, move |workspace, item, event, cx| { let pane = if let Some(pane) = workspace .panes_by_item @@ -415,7 +415,7 @@ impl ItemHandle for View { }; if let Some(item) = item.to_followable_item_handle(cx) { - let is_project_item = item.is_project_item(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) { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index dda3f70a14..7357b2e8c2 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1968,7 +1968,7 @@ impl Pane { // } impl ItemNavHistory { - pub fn push(&mut self, data: Option, cx: &mut WindowContext) { + pub fn push(&mut self, data: Option, cx: &mut WindowContext) { self.history.push(data, self.item.clone(), cx); } @@ -2039,7 +2039,7 @@ impl NavHistory { entry } - pub fn push( + pub fn push( &mut self, data: Option, item: Arc, @@ -2054,7 +2054,7 @@ impl NavHistory { } state.backward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Box::new(data) as Box), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), }); state.forward_stack.clear(); @@ -2065,7 +2065,7 @@ impl NavHistory { } state.forward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Box::new(data) as Box), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), }); } @@ -2075,7 +2075,7 @@ impl NavHistory { } state.backward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Box::new(data) as Box), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), }); } @@ -2085,7 +2085,7 @@ impl NavHistory { } state.closed_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Box::new(data) as Box), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), }); } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 504f106dfd..3bac52e963 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -14,7 +14,7 @@ use crate::persistence::model::{ DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use call2::ActiveCall; use client2::{ proto::{self, PeerId}, @@ -24,7 +24,8 @@ use collections::{HashMap, HashSet}; use dock::{Dock, DockPosition, PanelButtons}; use futures::{ channel::{mpsc, oneshot}, - FutureExt, Stream, StreamExt, + future::try_join_all, + FutureExt, StreamExt, }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, @@ -43,6 +44,7 @@ use persistence::{ model::{ItemId, WorkspaceLocation}, DB, }; +use postage::stream::Stream; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; use serde::Deserialize; use status_bar::StatusBar; @@ -720,25 +722,24 @@ impl Workspace { } 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); - } - } + // todo!() + // 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.origin.x -= screen_bounds.origin.x; + // 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.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(); @@ -886,7 +887,12 @@ impl Workspace { .await .log_err(); - window.update_root(&mut cx, |cx| cx.activate_window()); + cx.update_global(|_, cx| { + window.update_root(&mut cx, |_, cx| { + // todo!() + // cx.activate_window() + }); + }); let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); @@ -1930,7 +1936,7 @@ impl Workspace { // cx.notify(); // } - fn add_pane(&mut self, cx: &mut ViewContext) -> View { + fn add_pane(&mut self, _cx: &mut ViewContext) -> View { todo!() // let pane = cx.build_view(|cx| { // Pane::new( @@ -2280,9 +2286,9 @@ impl Workspace { fn handle_pane_event( &mut self, - pane: View, - event: &pane::Event, - cx: &mut ViewContext, + _pane: View, + _event: &pane::Event, + _cx: &mut ViewContext, ) { todo!() // match event { @@ -2814,8 +2820,8 @@ impl Workspace { fn handle_follow( &mut self, - follower_project_id: Option, - cx: &mut ViewContext, + _follower_project_id: Option, + _cx: &mut ViewContext, ) -> proto::FollowResponse { todo!() @@ -2878,7 +2884,7 @@ impl Workspace { this: &WeakView, leader_id: PeerId, update: proto::UpdateFollowers, - cx: &mut AsyncAppContext, + cx: &mut AsyncWindowContext, ) -> Result<()> { match update.variant.ok_or_else(|| anyhow!("invalid update"))? { proto::update_followers::Variant::UpdateActiveView(update_active_view) => { @@ -2919,7 +2925,7 @@ impl Workspace { try_join_all(tasks).await.log_err(); } proto::update_followers::Variant::CreateView(view) => { - let panes = this.read_with(cx, |this, _| { + let panes = this.update(cx, |this, _| { this.follower_states .iter() .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) @@ -2933,65 +2939,64 @@ impl Workspace { Ok(()) } - // async fn add_views_from_leader( - // this: WeakView, - // leader_id: PeerId, - // panes: Vec>, - // views: Vec, - // cx: &mut AsyncAppContext, - // ) -> Result<()> { - // let this = this - // .upgrade(cx) - // .ok_or_else(|| anyhow!("workspace dropped"))?; + async fn add_views_from_leader( + this: WeakView, + leader_id: PeerId, + panes: Vec>, + views: Vec, + cx: &mut AsyncWindowContext, + ) -> Result<()> { + let this = this.upgrade().context("workspace dropped")?; - // let item_builders = cx.update(|cx| { - // cx.default_global::() - // .values() - // .map(|b| b.0) - // .collect::>() - // }); + let item_builders = cx.update(|cx| { + cx.default_global::() + .values() + .map(|b| b.0) + .collect::>() + })?; - // let mut item_tasks_by_pane = HashMap::default(); - // for pane in panes { - // let mut item_tasks = Vec::new(); - // let mut leader_view_ids = Vec::new(); - // for view in &views { - // let Some(id) = &view.id else { continue }; - // let id = ViewId::from_proto(id.clone())?; - // let mut variant = view.variant.clone(); - // if variant.is_none() { - // Err(anyhow!("missing view variant"))?; - // } - // for build_item in &item_builders { - // let task = cx - // .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx)); - // if let Some(task) = task { - // item_tasks.push(task); - // leader_view_ids.push(id); - // break; - // } else { - // assert!(variant.is_some()); - // } - // } - // } + 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)); - // } + 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); - // } + 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(()) - // } + Some(()) + }); + } + Ok(()) + } // fn update_active_view_for_followers(&mut self, cx: &AppContext) { // let mut is_project_item = true; @@ -3194,18 +3199,18 @@ impl Workspace { } } - // fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { - // match member { - // Member::Axis(PaneAxis { members, .. }) => { - // for child in members.iter() { - // self.remove_panes(child.clone(), cx) - // } - // } - // Member::Pane(pane) => { - // self.force_remove_pane(&pane, cx); - // } - // } - // } + fn remove_panes(&mut self, member: Member, cx: &mut ViewContext) { + match member { + Member::Axis(PaneAxis { members, .. }) => { + for child in members.iter() { + self.remove_panes(child.clone(), cx) + } + } + Member::Pane(pane) => { + self.force_remove_pane(&pane, cx); + } + } + } fn force_remove_pane(&mut self, pane: &View, cx: &mut ViewContext) { self.panes.retain(|p| p != pane); @@ -3361,131 +3366,131 @@ impl Workspace { } } - // pub(crate) fn load_workspace( - // workspace: WeakView, - // serialized_workspace: SerializedWorkspace, - // paths_to_open: Vec>, - // cx: &mut AppContext, - // ) -> Task>>>> { - // cx.spawn(|mut cx| async move { - // let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| { - // ( - // workspace.project().clone(), - // workspace.last_active_center_pane.clone(), - // ) - // })?; + pub(crate) fn load_workspace( + workspace: WeakView, + serialized_workspace: SerializedWorkspace, + paths_to_open: Vec>, + cx: &mut AppContext, + ) -> Task>>>> { + cx.spawn(|mut cx| async move { + let (project, old_center_pane) = workspace.update(&mut 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 center_group = None; + let mut center_items = None; + // Traverse the splits tree and add to things + if let Some((group, active_pane, items)) = serialized_workspace + .center_group + .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) + .await + { + center_items = Some(items); + center_group = Some((group, active_pane)) + } - // let mut items_by_project_path = cx.read(|cx| { - // center_items - // .unwrap_or_default() - // .into_iter() - // .filter_map(|item| { - // let item = item?; - // let project_path = item.project_path(cx)?; - // Some((project_path, item)) - // }) - // .collect::>() - // }); + let mut items_by_project_path = cx.update(|cx| { + center_items + .unwrap_or_default() + .into_iter() + .filter_map(|item| { + let item = item?; + let project_path = item.project_path(cx)?; + Some((project_path, item)) + }) + .collect::>() + })?; - // let opened_items = paths_to_open - // .into_iter() - // .map(|path_to_open| { - // path_to_open - // .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) - // }) - // .collect::>(); + let opened_items = paths_to_open + .into_iter() + .map(|path_to_open| { + path_to_open + .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) + }) + .collect::>(); - // // Remove old panes from workspace panes list - // workspace.update(&mut cx, |workspace, cx| { - // if let Some((center_group, active_pane)) = center_group { - // workspace.remove_panes(workspace.center.root.clone(), cx); + // 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); + // 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(); + // 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() - // } - // } + 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()); + 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)); + 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); - // } - // } + 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)); + dock.active_panel() + .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); - // if docks.bottom.visible && docks.bottom.zoom { - // cx.focus_self() - // } - // }); + if docks.bottom.visible && docks.bottom.zoom { + cx.focus_self() + } + }); - // cx.notify(); - // })?; + 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))?; + // Serialize ourself to make sure our timestamps and any pane / item changes are replicated + workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?; - // Ok(opened_items) - // }) - // } + Ok(opened_items) + }) + } // #[cfg(any(test, feature = "test-support"))] // pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { @@ -3554,97 +3559,97 @@ fn window_bounds_env_override(cx: &MainThread) -> Option, -// workspace: &WeakView, -// mut project_paths_to_open: Vec<(PathBuf, Option)>, -// app_state: Arc, -// mut cx: AsyncAppContext, -// ) -> Result>>>> { -// let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); +async fn open_items( + serialized_workspace: Option, + workspace: &WeakView, + mut project_paths_to_open: Vec<(PathBuf, Option)>, + app_state: Arc, + mut cx: AsyncAppContext, +) -> Result>>>> { + let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); -// if let Some(serialized_workspace) = serialized_workspace { -// let workspace = workspace.clone(); -// let restored_items = cx -// .update(|cx| { -// Workspace::load_workspace( -// workspace, -// serialized_workspace, -// project_paths_to_open -// .iter() -// .map(|(_, project_path)| project_path) -// .cloned() -// .collect(), -// cx, -// ) -// }) -// .await?; + 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::>() -// }); + let restored_project_paths = cx.update(|cx| { + restored_items + .iter() + .filter_map(|item| item.as_ref()?.project_path(cx)) + .collect::>() + })?; -// for restored_item in restored_items { -// opened_items.push(restored_item.map(Ok)); -// } + 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()); + 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 -// } -// } -// }) -// }); + 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); -// } -// } + 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) -// } + Ok(opened_items) +} // fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { // const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; @@ -3726,23 +3731,24 @@ fn window_bounds_env_override(cx: &MainThread) -> Option, 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"; +fn notify_if_database_failed(_workspace: &WeakView, _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"; -// workspace -// .update(cx, |workspace, cx| { -// if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { -// workspace.show_notification_once(0, cx, |cx| { -// cx.build_view(|_| { -// MessageNotification::new("Failed to load the database file.") -// .with_click_message("Click to let us know about this error") -// .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) -// }) -// }); -// } -// }) -// .log_err(); -// } + // todo!() + // workspace + // .update(cx, |workspace, cx| { + // if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { + // workspace.show_notification_once(0, cx, |cx| { + // cx.build_view(|_| { + // MessageNotification::new("Failed to load the database file.") + // .with_click_message("Click to let us know about this error") + // .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) + // }) + // }); + // } + // }) + // .log_err(); +} impl EventEmitter for Workspace { type Event = Event; @@ -3751,7 +3757,7 @@ impl EventEmitter for Workspace { impl Render for Workspace { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { div() } } @@ -3908,7 +3914,7 @@ impl Render for Workspace { // } impl WorkspaceStore { - pub fn new(client: Arc, cx: &mut ModelContext) -> Self { + pub fn new(client: Arc, _cx: &mut ModelContext) -> Self { Self { workspaces: Default::default(), followers: Default::default(), @@ -4018,10 +4024,10 @@ impl WorkspaceStore { } async fn handle_update_followers( - this: Model, - envelope: TypedEnvelope, + _this: Model, + _envelope: TypedEnvelope, _: Arc, - mut cx: AsyncWindowContext, + mut _cx: AsyncWindowContext, ) -> Result<()> { // let leader_id = envelope.original_sender_id()?; // let update = envelope.payload; diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 194dbb9b25..7bc90c282c 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -219,7 +219,7 @@ pub async fn handle_cli_connection( pub fn build_window_options( bounds: Option, display_uuid: Option, - cx: MainThread, + cx: &mut MainThread, ) -> WindowOptions { let bounds = bounds.unwrap_or(WindowBounds::Maximized); let display = display_uuid.and_then(|uuid| cx.display_for_uuid(uuid)); From a9d7c86307de8c5fd185ae20cb42648dc0c8c57f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 1 Nov 2023 15:37:51 +0200 Subject: [PATCH 33/66] WIP --- crates/workspace2/src/item.rs | 2 +- crates/workspace2/src/workspace2.rs | 6 +++--- crates/zed2/src/main.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 258d32218b..4c09ab06bf 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -401,7 +401,7 @@ impl ItemHandle for View { let pending_update = Arc::new(Mutex::new(None)); let pending_update_scheduled = Arc::new(AtomicBool::new(false)); - let mut _event_subscription = + let mut event_subscription = Some(cx.subscribe(self, move |workspace, item, event, cx| { let pane = if let Some(pane) = workspace .panes_by_item diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3bac52e963..08cfbc1ee5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3370,9 +3370,9 @@ impl Workspace { workspace: WeakView, serialized_workspace: SerializedWorkspace, paths_to_open: Vec>, - cx: &mut AppContext, + cx: &mut WindowContext, ) -> Task>>>> { - cx.spawn(|mut cx| async move { + cx.spawn(|_, mut cx| async move { let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { ( workspace.project().clone(), @@ -3564,7 +3564,7 @@ async fn open_items( workspace: &WeakView, mut project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, - mut cx: AsyncAppContext, + mut cx: MainThread, ) -> Result>>>> { let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 793c6d6139..b5b22db140 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -313,9 +313,9 @@ async fn installation_id() -> Result { } } -async fn restore_or_create_workspace(_app_state: &Arc, mut _cx: AsyncAppContext) { +async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { if let Some(location) = workspace2::last_opened_workspace_paths().await { - cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx)) + cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))? .await .log_err(); } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { From e8857d959b1c9c4c40fdf09ee8659b8b0b948dd9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 1 Nov 2023 16:10:07 +0200 Subject: [PATCH 34/66] WIP --- crates/gpui2/src/app/async_context.rs | 8 +++-- crates/gpui2/src/gpui2.rs | 4 +++ crates/gpui2/src/window.rs | 51 ++++++++++++++++----------- crates/workspace2/src/workspace2.rs | 8 ++--- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 6802b5c1e1..06da000ca6 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,6 +1,6 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Render, - Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle, + AnyView, AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, + Render, Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle, }; use anyhow::Context as _; use derive_more::{Deref, DerefMut}; @@ -293,6 +293,10 @@ impl Context for AsyncWindowContext { impl VisualContext for AsyncWindowContext { type ViewContext<'a, V: 'static> = ViewContext<'a, V>; + fn root_view(&self) -> Result { + self.app.update_window(self.window, |cx| cx.root_view()) + } + fn build_view( &mut self, build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 5e5ac119d8..74cfed1959 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -215,6 +215,10 @@ impl Context for MainThread { impl VisualContext for MainThread { type ViewContext<'a, V: 'static> = MainThread>; + fn root_view(&self) -> AnyView { + self.0.root_view() + } + fn build_view( &mut self, build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 1df49899ca..e6046e14cf 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, - Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, - Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, - MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, - Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, - ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, - Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, - SUBPIXEL_VARIANTS, + Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, + GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, + LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, + PlatformWindow, Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, + RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -316,8 +316,6 @@ impl<'a> WindowContext<'a> { Self { app, window } } - // fn replace_root(&mut ) - /// Obtain a handle to the window that belongs to this context. pub fn window_handle(&self) -> AnyWindowHandle { self.window.handle @@ -1312,6 +1310,13 @@ impl Context for WindowContext<'_> { impl VisualContext for WindowContext<'_> { type ViewContext<'a, V: 'static> = ViewContext<'a, V>; + fn root_view(&self) -> Self::Result { + self.window + .root_view + .clone() + .expect("we only take the root_view value when we draw") + } + fn build_view( &mut self, build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, @@ -1966,6 +1971,10 @@ impl Context for ViewContext<'_, V> { impl VisualContext for ViewContext<'_, V> { type ViewContext<'a, W: 'static> = ViewContext<'a, W>; + fn root_view(&self) -> Self::Result { + self.window_cx.root_view() + } + fn build_view( &mut self, build_view: impl FnOnce(&mut Self::ViewContext<'_, W>) -> W, @@ -2034,20 +2043,20 @@ impl WindowHandle { } } - pub fn update_root( + pub fn update_root( &self, - cx: &mut AppContext, + cx: &mut C, update: impl FnOnce(&mut V, &mut ViewContext) -> R, - ) -> Result { + ) -> Result + where + C: Context, + { cx.update_window(self.any_handle, |cx| { - let root_view = cx - .window - .root_view - .clone() - .unwrap() - .downcast::() - .unwrap(); - root_view.update(cx, update) + let x = Ok(cx.root_view()).flatten(); + + // let root_view = x.unwrap().downcast::().unwrap(); + // root_view.update(cx, update) + todo!() }) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 08cfbc1ee5..e2413c819c 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -887,11 +887,9 @@ impl Workspace { .await .log_err(); - cx.update_global(|_, cx| { - window.update_root(&mut cx, |_, cx| { - // todo!() - // cx.activate_window() - }); + window.update_root(&mut cx, |_, cx| { + // todo!() + // cx.activate_window() }); let workspace = workspace.downgrade(); From 147db607c7243d4e769e7187964a13bb9d608a45 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 16:24:19 +0100 Subject: [PATCH 35/66] WIP --- crates/gpui2/src/app.rs | 70 +++------------------ crates/gpui2/src/app/async_context.rs | 58 +++++------------ crates/gpui2/src/app/model_context.rs | 6 +- crates/gpui2/src/app/test_context.rs | 13 +--- crates/gpui2/src/gpui2.rs | 12 ++-- crates/gpui2/src/window.rs | 90 +++++++++++++-------------- crates/workspace2/src/workspace2.rs | 6 +- 7 files changed, 79 insertions(+), 176 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index bbc6bfa567..66c10f901d 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -18,8 +18,8 @@ use crate::{ AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, - SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, - WindowContext, WindowHandle, WindowId, + SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, + WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -267,27 +267,6 @@ impl AppContext { .collect() } - pub fn update_window_root( - &mut self, - handle: &WindowHandle, - update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, - ) -> Result - where - V: 'static + Send, - { - self.update_window(handle.any_handle, |cx| { - let root_view = cx - .window - .root_view - .as_ref() - .unwrap() - .clone() - .downcast() - .unwrap(); - root_view.update(cx, update) - }) - } - pub(crate) fn push_effect(&mut self, effect: Effect) { match &effect { Effect::Notify { emitter } => { @@ -354,7 +333,7 @@ impl AppContext { .collect::>(); for dirty_window_handle in dirty_window_ids { - self.update_window(dirty_window_handle, |cx| cx.draw()) + self.update_window(dirty_window_handle, |_, cx| cx.draw()) .unwrap(); } } @@ -384,7 +363,7 @@ impl AppContext { /// a window blur handler to restore focus to some logical element. fn release_dropped_focus_handles(&mut self) { for window_handle in self.windows() { - self.update_window(window_handle, |cx| { + 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| { @@ -424,7 +403,7 @@ impl AppContext { window_handle: AnyWindowHandle, focused: Option, ) { - self.update_window(window_handle, |cx| { + self.update_window(window_handle, |_, cx| { if cx.window.focus == focused { let mut listeners = mem::take(&mut cx.window.focus_listeners); let focused = @@ -764,7 +743,7 @@ impl Context for AppContext { fn update_window(&mut self, handle: AnyWindowHandle, update: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { self.update(|cx| { let mut window = cx @@ -774,8 +753,8 @@ impl Context for AppContext { .take() .unwrap(); - let result = update(&mut WindowContext::new(cx, &mut window)); - + let root_view = window.root_view.clone().unwrap(); + let result = update(root_view, &mut WindowContext::new(cx, &mut window)); cx.windows .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? @@ -854,39 +833,6 @@ impl MainThread { }) } - pub fn update_window( - &mut self, - handle: AnyWindowHandle, - update: impl FnOnce(&mut MainThread) -> R, - ) -> Result { - self.0.update_window(handle, |cx| { - update(unsafe { - std::mem::transmute::<&mut WindowContext, &mut MainThread>(cx) - }) - }) - } - - pub fn update_window_root( - &mut self, - handle: &WindowHandle, - update: impl FnOnce(&mut V, &mut MainThread>) -> R, - ) -> Result - where - V: 'static + Send, - { - self.update_window(handle.any_handle, |cx| { - let root_view = cx - .window - .root_view - .as_ref() - .unwrap() - .clone() - .downcast() - .unwrap(); - root_view.update(cx, update) - }) - } - /// Opens a new window with the given option and the root view returned by the given function. /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific /// functionality. diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 06da000ca6..d2820aa1fc 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -42,7 +42,7 @@ impl Context for AsyncAppContext { fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { let app = self.app.upgrade().context("app was released")?; let mut lock = app.lock(); // Need this to compile @@ -68,29 +68,6 @@ impl AsyncAppContext { Ok(f(&mut *lock)) } - pub fn update_window( - &self, - window: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, - ) -> Result { - let app = self.app.upgrade().context("app was released")?; - let mut app_context = app.lock(); - app_context.update_window(window, update) - } - - pub fn update_window_root( - &mut self, - handle: &WindowHandle, - update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, - ) -> Result - where - V: 'static + Send, - { - let app = self.app.upgrade().context("app was released")?; - let mut app_context = app.lock(); - app_context.update_window_root(handle, update) - } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task where Fut: Future + Send + 'static, @@ -191,22 +168,25 @@ impl AsyncWindowContext { Self { app, window } } - pub fn update(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result { + pub fn update( + &mut self, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, + ) -> Result { self.app.update_window(self.window, update) } pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { self.app - .update_window(self.window, |cx| cx.on_next_frame(f)) + .update_window(self.window, |_root, cx| cx.on_next_frame(f)) .ok(); } pub fn read_global( - &self, + &mut self, read: impl FnOnce(&G, &WindowContext) -> R, ) -> Result { self.app - .update_window(self.window, |cx| read(cx.global(), cx)) + .update_window(self.window, |_, cx| read(cx.global(), cx)) } pub fn update_global( @@ -217,7 +197,7 @@ impl AsyncWindowContext { G: 'static, { self.app - .update_window(self.window, |cx| cx.update_global(update)) + .update_window(self.window, |_, cx| cx.update_global(update)) } pub fn spawn( @@ -245,13 +225,13 @@ impl AsyncWindowContext { } pub fn run_on_main( - &self, + &mut self, f: impl FnOnce(&mut MainThread) -> R + Send + 'static, ) -> Task> where R: Send + 'static, { - self.update(|cx| cx.run_on_main(f)) + self.update(|_, cx| cx.run_on_main(f)) .unwrap_or_else(|error| Task::ready(Err(error))) } } @@ -270,7 +250,7 @@ impl Context for AsyncWindowContext { T: 'static + Send, { self.app - .update_window(self.window, |cx| cx.build_model(build_model)) + .update_window(self.window, |_, cx| cx.build_model(build_model)) } fn update_model( @@ -279,12 +259,12 @@ impl Context for AsyncWindowContext { update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Result { self.app - .update_window(self.window, |cx| cx.update_model(handle, update)) + .update_window(self.window, |_, cx| cx.update_model(handle, update)) } fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { self.app.update_window(window, update) } @@ -293,10 +273,6 @@ impl Context for AsyncWindowContext { impl VisualContext for AsyncWindowContext { type ViewContext<'a, V: 'static> = ViewContext<'a, V>; - fn root_view(&self) -> Result { - self.app.update_window(self.window, |cx| cx.root_view()) - } - fn build_view( &mut self, build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, @@ -305,7 +281,7 @@ impl VisualContext for AsyncWindowContext { V: 'static + Send, { self.app - .update_window(self.window, |cx| cx.build_view(build_view_state)) + .update_window(self.window, |_, cx| cx.build_view(build_view_state)) } fn update_view( @@ -314,7 +290,7 @@ impl VisualContext for AsyncWindowContext { update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, ) -> Self::Result { self.app - .update_window(self.window, |cx| cx.update_view(view, update)) + .update_window(self.window, |_, cx| cx.update_view(view, update)) } fn replace_root_view( @@ -325,7 +301,7 @@ impl VisualContext for AsyncWindowContext { V: 'static + Send + Render, { self.app - .update_window(self.window, |cx| cx.replace_root_view(build_view)) + .update_window(self.window, |_, cx| cx.replace_root_view(build_view)) } } diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index c8b3eacdbc..bc36d68540 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -1,6 +1,6 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, - MainThread, Model, Subscription, Task, WeakModel, WindowContext, + AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, + EventEmitter, MainThread, Model, Subscription, Task, WeakModel, WindowContext, }; use anyhow::Result; use derive_more::{Deref, DerefMut}; @@ -253,7 +253,7 @@ impl<'a, T> Context for ModelContext<'a, T> { fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> R, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> R, { self.app.update_window(window, update) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index c53aefe565..90ea7dd91d 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,5 +1,5 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, MainThread, Model, + AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; use parking_lot::Mutex; @@ -38,7 +38,7 @@ impl Context for TestAppContext { fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { let mut lock = self.app.lock(); lock.update_window(window, f) @@ -76,15 +76,6 @@ impl TestAppContext { f(&mut *lock) } - pub fn update_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, - ) -> R { - let mut app = self.app.lock(); - app.update_window(handle, update).unwrap() - } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task where Fut: Future + Send + 'static, diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 74cfed1959..58e1a7fc54 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -98,7 +98,7 @@ pub trait Context { fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T; + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T; } pub trait VisualContext: Context { @@ -199,15 +199,15 @@ impl Context for MainThread { fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { - self.0.update_window(window, |cx| { + self.0.update_window(window, |root, cx| { let cx = unsafe { mem::transmute::<&mut C::WindowContext<'_>, &mut MainThread>>( cx, ) }; - update(cx) + update(root, cx) }) } } @@ -215,10 +215,6 @@ impl Context for MainThread { impl VisualContext for MainThread { type ViewContext<'a, V: 'static> = MainThread>; - fn root_view(&self) -> AnyView { - self.0.root_view() - } - fn build_view( &mut self, build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e6046e14cf..36452bbf03 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,16 +1,16 @@ use crate::{ px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, - Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, - GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, - LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, - PlatformWindow, Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowOptions, SUBPIXEL_VARIANTS, + Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, + Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, + MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, + Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, + ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, + Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, + SUBPIXEL_VARIANTS, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; use parking_lot::RwLock; @@ -204,9 +204,9 @@ impl Window { let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); platform_window.on_resize(Box::new({ - let cx = cx.to_async(); + let mut cx = cx.to_async(); move |content_size, scale_factor| { - cx.update_window(handle, |cx| { + cx.update_window(handle, |_, cx| { cx.window.scale_factor = scale_factor; cx.window.scene_builder = SceneBuilder::new(); cx.window.content_size = content_size; @@ -223,9 +223,9 @@ impl Window { })); platform_window.on_input({ - let cx = cx.to_async(); + let mut cx = cx.to_async(); Box::new(move |event| { - cx.update_window(handle, |cx| cx.dispatch_event(event)) + cx.update_window(handle, |_, cx| cx.dispatch_event(event)) .log_err() .unwrap_or(true) }) @@ -372,7 +372,7 @@ impl<'a> WindowContext<'a> { pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static + Send) { let window = self.window.handle; self.app.defer(move |cx| { - cx.update_window(window, f).ok(); + cx.update_window(window, |_, cx| f(cx)).ok(); }); } @@ -391,7 +391,7 @@ impl<'a> WindowContext<'a> { self.app.event_listeners.insert( entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle, |cx| { + cx.update_window(window_handle, |_, cx| { if let Some(handle) = E::upgrade_from(&entity) { let event = event.downcast_ref().expect("invalid event type"); on_event(handle, event, cx); @@ -421,7 +421,8 @@ impl<'a> WindowContext<'a> { }))) } else { let handle = self.window.handle; - self.app.run_on_main(move |cx| cx.update_window(handle, f)) + self.app + .run_on_main(move |cx| cx.update_window(handle, |_, cx| f(cx))) } } @@ -443,12 +444,12 @@ impl<'a> WindowContext<'a> { return; } } else { - let async_cx = cx.to_async(); + let mut async_cx = cx.to_async(); cx.next_frame_callbacks.insert(display_id, vec![f]); cx.platform().set_display_link_output_callback( display_id, Box::new(move |_current_time, _output_time| { - let _ = async_cx.update(|cx| { + let _ = async_cx.update(|_, cx| { let callbacks = cx .next_frame_callbacks .get_mut(&display_id) @@ -1181,7 +1182,7 @@ impl<'a> WindowContext<'a> { let window_handle = self.window.handle; self.global_observers.insert( TypeId::of::(), - Box::new(move |cx| cx.update_window(window_handle, |cx| f(cx)).is_ok()), + Box::new(move |cx| cx.update_window(window_handle, |_, cx| f(cx)).is_ok()), ) } @@ -1297,10 +1298,11 @@ impl Context for WindowContext<'_> { fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { if window == self.window.handle { - Ok(update(self)) + let root_view = self.window.root_view.clone().unwrap(); + Ok(update(root_view, self)) } else { self.app.update_window(window, update) } @@ -1310,13 +1312,6 @@ impl Context for WindowContext<'_> { impl VisualContext for WindowContext<'_> { type ViewContext<'a, V: 'static> = ViewContext<'a, V>; - fn root_view(&self) -> Self::Result { - self.window - .root_view - .clone() - .expect("we only take the root_view value when we draw") - } - fn build_view( &mut self, build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, @@ -1654,7 +1649,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.app.observers.insert( entity_id, Box::new(move |cx| { - cx.update_window(window_handle, |cx| { + cx.update_window(window_handle, |_, cx| { if let Some(handle) = E::upgrade_from(&entity) { view.update(cx, |this, cx| on_notify(this, handle, cx)) .is_ok() @@ -1683,7 +1678,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.app.event_listeners.insert( entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle, |cx| { + cx.update_window(window_handle, |_, cx| { if let Some(handle) = E::upgrade_from(&handle) { let event = event.downcast_ref().expect("invalid event type"); view.update(cx, |this, cx| on_event(this, handle, event, cx)) @@ -1707,7 +1702,7 @@ impl<'a, V: 'static> ViewContext<'a, 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, |cx| on_release(this, cx)); + let _ = cx.update_window(window_handle, |_, cx| on_release(this, cx)); }), ) } @@ -1729,7 +1724,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { entity_id, Box::new(move |entity, cx| { let entity = entity.downcast_mut().expect("invalid entity type"); - let _ = cx.update_window(window_handle, |cx| { + let _ = cx.update_window(window_handle, |_, cx| { view.update(cx, |this, cx| on_release(this, entity, cx)) }); }), @@ -1892,7 +1887,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.global_observers.insert( TypeId::of::(), Box::new(move |cx| { - cx.update_window(window_handle, |cx| { + cx.update_window(window_handle, |_, cx| { view.update(cx, |view, cx| f(view, cx)).is_ok() }) .unwrap_or(false) @@ -1962,7 +1957,7 @@ impl Context for ViewContext<'_, V> { fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(&mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, { self.window_cx.update_window(window, update) } @@ -1971,10 +1966,6 @@ impl Context for ViewContext<'_, V> { impl VisualContext for ViewContext<'_, V> { type ViewContext<'a, W: 'static> = ViewContext<'a, W>; - fn root_view(&self) -> Self::Result { - self.window_cx.root_view() - } - fn build_view( &mut self, build_view: impl FnOnce(&mut Self::ViewContext<'_, W>) -> W, @@ -2043,21 +2034,24 @@ impl WindowHandle { } } - pub fn update_root( + pub fn update_root( &self, cx: &mut C, - update: impl FnOnce(&mut V, &mut ViewContext) -> R, + update: impl FnOnce( + &mut V, + &mut as VisualContext>::ViewContext<'_, V>, + ) -> R, ) -> Result where - C: Context, + C: for<'a> Context = W>, + W: VisualContext = R>, { - cx.update_window(self.any_handle, |cx| { - let x = Ok(cx.root_view()).flatten(); - - // let root_view = x.unwrap().downcast::().unwrap(); - // root_view.update(cx, update) - todo!() - }) + cx.update_window(self.any_handle, |root_view, cx| { + let view = root_view + .downcast::() + .map_err(|_| anyhow!("the type of the window's root view has changed"))?; + Ok(view.update(cx, update)) + })? } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e2413c819c..2db525a26d 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -14,7 +14,7 @@ use crate::persistence::model::{ DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context as _, Result}; use call2::ActiveCall; use client2::{ proto::{self, PeerId}, @@ -29,7 +29,7 @@ use futures::{ }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, - Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, + Context, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; @@ -837,7 +837,7 @@ impl Workspace { }; let window = if let Some(window) = requesting_window { - cx.update_window(window.into(), |cx| { + cx.update_window(window.into(), |old_workspace, cx| { cx.replace_root_view(|cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); From c3a8bab4d2d7ca067891355da31a6b1e60a020c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 17:21:58 +0100 Subject: [PATCH 36/66] WIP --- crates/gpui2/src/app.rs | 81 +++++++-------- crates/gpui2/src/gpui2.rs | 32 +++++- crates/gpui2/src/window.rs | 152 ++++++++++++++++------------ crates/workspace2/src/workspace2.rs | 17 ++-- 4 files changed, 168 insertions(+), 114 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 66c10f901d..7c62661f33 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -333,8 +333,7 @@ impl AppContext { .collect::>(); for dirty_window_handle in dirty_window_ids { - self.update_window(dirty_window_handle, |_, cx| cx.draw()) - .unwrap(); + dirty_window_handle.update(self, |_, cx| cx.draw()).unwrap(); } } @@ -363,25 +362,26 @@ impl AppContext { /// a window blur handler to restore focus to some logical element. fn release_dropped_focus_handles(&mut self) { 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| { - if count.load(SeqCst) == 0 { - if focus == Some(handle_id) { - blur_window = true; + window_handle + .update(self, |_, cx| { + let mut blur_window = false; + let focus = cx.window.focus; + cx.window.focus_handles.write().retain(|handle_id, count| { + if count.load(SeqCst) == 0 { + if focus == Some(handle_id) { + blur_window = true; + } + false + } else { + true } - false - } else { - true - } - }); + }); - if blur_window { - cx.blur(); - } - }) - .unwrap(); + if blur_window { + cx.blur(); + } + }) + .unwrap(); } } @@ -403,29 +403,30 @@ impl AppContext { 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 = - focused.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); - let blurred = cx - .window - .last_blur - .take() - .unwrap() - .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - if focused.is_some() || blurred.is_some() { - let event = FocusEvent { focused, blurred }; - for listener in &listeners { - listener(&event, cx); + window_handle + .update(self, |_, cx| { + if cx.window.focus == focused { + let mut listeners = mem::take(&mut cx.window.focus_listeners); + let focused = focused + .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); + let blurred = cx + .window + .last_blur + .take() + .unwrap() + .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); + if focused.is_some() || blurred.is_some() { + let event = FocusEvent { focused, blurred }; + for listener in &listeners { + listener(&event, cx); + } } - } - listeners.extend(cx.window.focus_listeners.drain(..)); - cx.window.focus_listeners = listeners; - } - }) - .ok(); + listeners.extend(cx.window.focus_listeners.drain(..)); + cx.window.focus_listeners = listeners; + } + }) + .ok(); } fn apply_refresh_effect(&mut self) { diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 58e1a7fc54..d016e174fe 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -77,7 +77,7 @@ use taffy::TaffyLayoutEngine; type AnyBox = Box; pub trait Context { - type WindowContext<'a>: VisualContext; + type WindowContext<'a>: UpdateView; type ModelContext<'a, T>; type Result; @@ -125,6 +125,16 @@ pub trait VisualContext: Context { V: 'static + Send + Render; } +pub trait UpdateView { + type ViewContext<'a, V: 'static>; + + fn update_view( + &mut self, + view: &View, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, + ) -> R; +} + pub trait Entity: Sealed { type Weak: 'static + Send; @@ -268,6 +278,26 @@ impl VisualContext for MainThread { } } +impl UpdateView for MainThread { + type ViewContext<'a, V: 'static> = MainThread>; + + fn update_view( + &mut self, + view: &View, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, + ) -> R { + self.0.update_view(view, |view_state, cx| { + let cx = unsafe { + mem::transmute::< + &mut C::ViewContext<'_, V>, + &mut MainThread>, + >(cx) + }; + update(view_state, cx) + }) + } +} + pub trait BorrowAppContext { fn with_text_style(&mut self, style: TextStyleRefinement, f: F) -> R where diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 36452bbf03..cf83e4e3af 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -7,7 +7,7 @@ use crate::{ MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, - Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, + Task, Underline, UnderlineStyle, UpdateView, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; @@ -206,26 +206,28 @@ impl Window { platform_window.on_resize(Box::new({ let mut cx = cx.to_async(); move |content_size, scale_factor| { - cx.update_window(handle, |_, cx| { - cx.window.scale_factor = scale_factor; - cx.window.scene_builder = SceneBuilder::new(); - cx.window.content_size = content_size; - cx.window.display_id = cx - .window - .platform_window - .borrow_on_main_thread() - .display() - .id(); - cx.window.dirty = true; - }) - .log_err(); + handle + .update(&mut cx, |_, cx| { + cx.window.scale_factor = scale_factor; + cx.window.scene_builder = SceneBuilder::new(); + cx.window.content_size = content_size; + cx.window.display_id = cx + .window + .platform_window + .borrow_on_main_thread() + .display() + .id(); + cx.window.dirty = true; + }) + .log_err(); } })); platform_window.on_input({ let mut cx = cx.to_async(); Box::new(move |event| { - cx.update_window(handle, |_, cx| cx.dispatch_event(event)) + handle + .update(&mut cx, |_, cx| cx.dispatch_event(event)) .log_err() .unwrap_or(true) }) @@ -370,9 +372,9 @@ impl<'a> WindowContext<'a> { /// Schedules the given function to be run at the end of the current effect cycle, allowing entities /// that are currently on the stack to be returned to the app. pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static + Send) { - let window = self.window.handle; + let handle = self.window.handle; self.app.defer(move |cx| { - cx.update_window(window, |_, cx| f(cx)).ok(); + handle.update(cx, |_, cx| f(cx)).ok(); }); } @@ -391,16 +393,17 @@ impl<'a> WindowContext<'a> { self.app.event_listeners.insert( entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle, |_, cx| { - if let Some(handle) = E::upgrade_from(&entity) { - let event = event.downcast_ref().expect("invalid event type"); - on_event(handle, event, cx); - true - } else { - false - } - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| { + if let Some(handle) = E::upgrade_from(&entity) { + let event = event.downcast_ref().expect("invalid event type"); + on_event(handle, event, cx); + true + } else { + false + } + }) + .unwrap_or(false) }), ) } @@ -422,7 +425,7 @@ impl<'a> WindowContext<'a> { } else { let handle = self.window.handle; self.app - .run_on_main(move |cx| cx.update_window(handle, |_, cx| f(cx))) + .run_on_main(move |cx| handle.update(cx, |_, cx| f(cx))) } } @@ -1182,7 +1185,7 @@ impl<'a> WindowContext<'a> { let window_handle = self.window.handle; self.global_observers.insert( TypeId::of::(), - Box::new(move |cx| cx.update_window(window_handle, |_, cx| f(cx)).is_ok()), + Box::new(move |cx| window_handle.update(cx, |_, cx| f(cx)).is_ok()), ) } @@ -1304,7 +1307,7 @@ impl Context for WindowContext<'_> { let root_view = self.window.root_view.clone().unwrap(); Ok(update(root_view, self)) } else { - self.app.update_window(window, update) + window.update(self.app, update) } } } @@ -1361,6 +1364,18 @@ impl VisualContext for WindowContext<'_> { } } +impl UpdateView for WindowContext<'_> { + type ViewContext<'a, V: 'static> = ViewContext<'a, V>; + + fn update_view( + &mut self, + view: &View, + update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, + ) -> R { + VisualContext::update_view(self, view, update) + } +} + impl<'a> std::ops::Deref for WindowContext<'a> { type Target = AppContext; @@ -1649,15 +1664,16 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.app.observers.insert( entity_id, Box::new(move |cx| { - cx.update_window(window_handle, |_, cx| { - if let Some(handle) = E::upgrade_from(&entity) { - view.update(cx, |this, cx| on_notify(this, handle, cx)) - .is_ok() - } else { - false - } - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| { + if let Some(handle) = E::upgrade_from(&entity) { + view.update(cx, |this, cx| on_notify(this, handle, cx)) + .is_ok() + } else { + false + } + }) + .unwrap_or(false) }), ) } @@ -1678,16 +1694,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.app.event_listeners.insert( entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle, |_, cx| { - if let Some(handle) = E::upgrade_from(&handle) { - let event = event.downcast_ref().expect("invalid event type"); - view.update(cx, |this, cx| on_event(this, handle, event, cx)) - .is_ok() - } else { - false - } - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| { + if let Some(handle) = E::upgrade_from(&handle) { + let event = event.downcast_ref().expect("invalid event type"); + view.update(cx, |this, cx| on_event(this, handle, event, cx)) + .is_ok() + } else { + false + } + }) + .unwrap_or(false) }), ) } @@ -1702,7 +1719,7 @@ impl<'a, V: 'static> ViewContext<'a, 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, |_, cx| on_release(this, cx)); + let _ = window_handle.update(cx, |_, cx| on_release(this, cx)); }), ) } @@ -1724,7 +1741,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { entity_id, Box::new(move |entity, cx| { let entity = entity.downcast_mut().expect("invalid entity type"); - let _ = cx.update_window(window_handle, |_, cx| { + let _ = window_handle.update(cx, |_, cx| { view.update(cx, |this, cx| on_release(this, entity, cx)) }); }), @@ -1887,10 +1904,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.global_observers.insert( TypeId::of::(), Box::new(move |cx| { - cx.update_window(window_handle, |_, cx| { - view.update(cx, |view, cx| f(view, cx)).is_ok() - }) - .unwrap_or(false) + window_handle + .update(cx, |_, cx| view.update(cx, |view, cx| f(view, cx)).is_ok()) + .unwrap_or(false) }), ) } @@ -1978,7 +1994,7 @@ impl VisualContext for ViewContext<'_, V> { view: &View, update: impl FnOnce(&mut V2, &mut Self::ViewContext<'_, V2>) -> R, ) -> Self::Result { - self.window_cx.update_view(view, update) + VisualContext::update_view(&mut self.window_cx, view, update) } fn replace_root_view( @@ -2034,23 +2050,20 @@ impl WindowHandle { } } - pub fn update_root( + pub fn update( &self, cx: &mut C, - update: impl FnOnce( - &mut V, - &mut as VisualContext>::ViewContext<'_, V>, - ) -> R, + update: impl FnOnce(&mut V, &mut as UpdateView>::ViewContext<'_, V>) -> R, ) -> Result where - C: for<'a> Context = W>, - W: VisualContext = R>, + C: Context, { cx.update_window(self.any_handle, |root_view, cx| { let view = root_view .downcast::() .map_err(|_| anyhow!("the type of the window's root view has changed"))?; - Ok(view.update(cx, update)) + + Ok(cx.update_view(&view, update)) })? } } @@ -2107,6 +2120,17 @@ impl AnyWindowHandle { None } } + + pub fn update( + &self, + cx: &mut C, + update: impl FnOnce(AnyView, &mut C::WindowContext<'_>) -> R, + ) -> Result + where + C: Context, + { + cx.update_window(*self, update) + } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 2db525a26d..68e5b058e9 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -428,10 +428,10 @@ pub struct AppState { pub build_window_options: fn(Option, Option, &mut MainThread) -> WindowOptions, pub initialize_workspace: fn( - WeakView, + WindowHandle, bool, Arc, - AsyncWindowContext, + AsyncAppContext, ) -> Task>, pub node_runtime: Arc, } @@ -874,12 +874,14 @@ impl Workspace { let options = cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?; cx.open_window(options, |cx| { - Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + cx.build_view(|cx| { + Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + }) })? }; (app_state.initialize_workspace)( - workspace.downgrade(), + window, serialized_workspace.is_some(), app_state.clone(), cx.clone(), @@ -887,10 +889,7 @@ impl Workspace { .await .log_err(); - window.update_root(&mut cx, |_, cx| { - // todo!() - // cx.activate_window() - }); + window.update(&mut cx, |_, cx| cx.activate_window()); let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); @@ -3976,7 +3975,7 @@ impl WorkspaceStore { let mut response = proto::FollowResponse::default(); for workspace in &this.workspaces { workspace - .update_root(cx, |workspace, cx| { + .update(cx, |workspace, cx| { let handler_response = workspace.handle_follow(follower.project_id, cx); if response.views.is_empty() { response.views = handler_response.views; From 337a79e35f17f68d71d9c8e095cc7095eaa97165 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 18:34:51 +0100 Subject: [PATCH 37/66] WIP --- crates/gpui2/src/app.rs | 8 +- crates/gpui2/src/app/model_context.rs | 5 +- crates/gpui2/src/window.rs | 12 +-- crates/workspace2/src/persistence/model.rs | 14 ++-- crates/workspace2/src/workspace2.rs | 89 ++++++++++------------ 5 files changed, 56 insertions(+), 72 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 7c62661f33..57b1d63f4b 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -504,16 +504,12 @@ impl AppContext { /// Spawns the future returned by the given function on the thread pool. The closure will be invoked /// with AsyncAppContext, which allows the application state to be accessed across await points. - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + Send + 'static, R: Send + 'static, { - let cx = self.to_async(); - self.executor.spawn(async move { - let future = f(cx); - future.await - }) + self.executor.spawn(f(self.to_async())) } /// Schedules the given function to be run at the end of the current effect cycle, allowing entities diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index bc36d68540..e4db4ab64d 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -189,10 +189,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { result } - pub fn spawn( - &self, - f: impl FnOnce(WeakModel, AsyncAppContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(WeakModel, AsyncAppContext) -> Fut) -> Task where T: 'static, Fut: Future + Send + 'static, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index cf83e4e3af..6c9c10af05 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -484,7 +484,7 @@ impl<'a> WindowContext<'a> { /// use within your future. pub fn spawn( &mut self, - f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static, + f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut, ) -> Task where R: Send + 'static, @@ -493,8 +493,7 @@ impl<'a> WindowContext<'a> { let window = self.window.handle; self.app.spawn(move |app| { let cx = AsyncWindowContext::new(app, window); - let future = f(window, cx); - async move { future.await } + f(window, cx) }) } @@ -1872,17 +1871,14 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn spawn( &mut self, - f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut + Send + 'static, + f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut, ) -> Task where R: Send + 'static, Fut: Future + Send + 'static, { let view = self.view().downgrade(); - self.window_cx.spawn(move |_, cx| { - let result = f(view, cx); - async move { result.await } - }) + self.window_cx.spawn(move |_, cx| f(view, cx)) } pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 90b8a9b3be..b1efd35b6b 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -7,7 +7,9 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; +use gpui2::{ + AsyncAppContext, AsyncWindowContext, Model, Task, View, WeakView, WindowBounds, WindowHandle, +}; use project2::Project; use std::{ path::{Path, PathBuf}, @@ -153,8 +155,8 @@ impl SerializedPaneGroup { self, project: &Model, workspace_id: WorkspaceId, - workspace: &WeakView, - cx: &mut AsyncWindowContext, + workspace: WindowHandle, + cx: &mut AsyncAppContext, ) -> Option<(Member, Option>, Vec>>)> { match self { SerializedPaneGroup::Group { @@ -231,8 +233,8 @@ impl SerializedPane { project: &Model, pane: &WeakView, workspace_id: WorkspaceId, - workspace: &WeakView, - cx: &mut AsyncWindowContext, + workspace: WindowHandle, + cx: &mut AsyncAppContext, ) -> Result>>> { let mut items = Vec::new(); let mut active_item_index = None; @@ -241,7 +243,7 @@ impl SerializedPane { let item_handle = pane .update(cx, |_, cx| { if let Some(deserializer) = cx.global::().get(&item.kind) { - deserializer(project, workspace.clone(), workspace_id, item.item_id, cx) + deserializer(project, workspace, workspace_id, item.item_id, cx) } else { Task::ready(Err(anyhow::anyhow!( "Deserializer does not exist for item kind: {}", diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 68e5b058e9..aa57b317d2 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -29,9 +29,9 @@ use futures::{ }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, - Context, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, - Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, - WindowHandle, WindowOptions, + Context, Div, Entity, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, + Render, Size, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, + WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language2::LanguageRegistry; @@ -399,7 +399,7 @@ type ItemDeserializers = HashMap< Arc, fn( Model, - WeakView, + WindowHandle, WorkspaceId, ItemId, &mut ViewContext, @@ -891,19 +891,22 @@ impl Workspace { 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(); + notify_if_database_failed(window, &mut cx); + let opened_items = window + .update(&mut cx, |_workspace, cx| { + let workspace = cx.view().downgrade(); + open_items( + serialized_workspace, + &workspace, + project_paths, + app_state, + cx, + ) + }) + .await + .unwrap_or_default(); - (workspace, opened_items) + (window, opened_items) }) } @@ -2945,7 +2948,7 @@ impl Workspace { ) -> Result<()> { let this = this.upgrade().context("workspace dropped")?; - let item_builders = cx.update(|cx| { + let item_builders = cx.update(|_, cx| { cx.default_global::() .values() .map(|b| b.0) @@ -2964,7 +2967,7 @@ impl Workspace { Err(anyhow!("missing view variant"))?; } for build_item in &item_builders { - let task = cx.update(|cx| { + let task = cx.update(|_, cx| { build_item(pane.clone(), this.clone(), id, &mut variant, cx) })?; if let Some(task) = task { @@ -3364,12 +3367,11 @@ impl Workspace { } pub(crate) fn load_workspace( - workspace: WeakView, serialized_workspace: SerializedWorkspace, paths_to_open: Vec>, - cx: &mut WindowContext, + cx: &mut ViewContext, ) -> Task>>>> { - cx.spawn(|_, mut cx| async move { + cx.spawn(|workspace, mut cx| async move { let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { ( workspace.project().clone(), @@ -3382,7 +3384,7 @@ impl Workspace { // 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) + .deserialize(&project, serialized_workspace.id, workspace, &mut cx) .await { center_items = Some(items); @@ -3558,36 +3560,28 @@ fn window_bounds_env_override(cx: &MainThread) -> Option, - workspace: &WeakView, mut project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, - mut cx: MainThread, + mut cx: &mut MainThread>, ) -> Result>>>> { let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); if let Some(serialized_workspace) = serialized_workspace { - let workspace = workspace.clone(); - let restored_items = cx - .update(|cx| { - Workspace::load_workspace( - workspace, - serialized_workspace, - project_paths_to_open - .iter() - .map(|(_, project_path)| project_path) - .cloned() - .collect(), - cx, - ) - })? - .await?; - - let restored_project_paths = cx.update(|cx| { - restored_items + let restored_items = Workspace::load_workspace( + serialized_workspace, + project_paths_to_open .iter() - .filter_map(|item| item.as_ref()?.project_path(cx)) - .collect::>() - })?; + .map(|(_, project_path)| project_path) + .cloned() + .collect(), + cx, + ) + .await?; + + let restored_project_paths = restored_items + .iter() + .filter_map(|item| item.as_ref()?.project_path(cx)) + .collect::>(); for restored_item in restored_items { opened_items.push(restored_item.map(Ok)); @@ -3614,8 +3608,7 @@ async fn open_items( .into_iter() .enumerate() .map(|(i, (abs_path, project_path))| { - let workspace = workspace.clone(); - cx.spawn(|mut cx| { + cx.spawn(|workspace, mut cx| { let fs = app_state.fs.clone(); async move { let file_project_path = project_path?; @@ -3728,7 +3721,7 @@ async fn open_items( // }) // .ok(); -fn notify_if_database_failed(_workspace: &WeakView, _cx: &mut AsyncAppContext) { +fn notify_if_database_failed(_workspace: WindowHandle, _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"; // todo!() From 1c1b53ecf643bbf901404b9c073e8b99f715b65c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 11:45:31 -0700 Subject: [PATCH 38/66] WIP --- crates/workspace2/src/item.rs | 159 ++++---- crates/workspace2/src/persistence/model.rs | 243 ++++++------ crates/workspace2/src/workspace2.rs | 418 +++++++++++---------- crates/zed2/src/main.rs | 49 ++- crates/zed2/src/zed2.rs | 4 +- 5 files changed, 452 insertions(+), 421 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 4c09ab06bf..d839f39203 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -13,7 +13,7 @@ use client2::{ }; use gpui2::{ AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render, - SharedString, Task, View, ViewContext, WeakView, WindowContext, + SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -190,7 +190,7 @@ pub trait Item: Render + EventEmitter + Send { fn deserialize( _project: Model, - _workspace: WeakView, + _workspace: WindowHandle, _workspace_id: WorkspaceId, _item_id: ItemId, _cx: &mut ViewContext, @@ -401,87 +401,86 @@ impl ItemHandle for View { let pending_update = Arc::new(Mutex::new(None)); let pending_update_scheduled = Arc::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()) - { - pane - } else { - log::error!("unexpected item event after pane was dropped"); - return; - }; + let 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()) + { + 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 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.lock(), cx) - && !pending_update_scheduled.load(Ordering::SeqCst) - { - pending_update_scheduled.store(true, Ordering::SeqCst); - todo!("replace with on_next_frame?"); - // 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, - // ); - // } - // }); - } + if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { + workspace.unfollow(&pane, 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; - } + if item.add_event_to_update_proto(event, &mut *pending_update.lock(), cx) + && !pending_update_scheduled.load(Ordering::SeqCst) + { + pending_update_scheduled.store(true, Ordering::SeqCst); + todo!("replace with on_next_frame?"); + // 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, + // ); + // } + // }); + } + } - ItemEvent::UpdateTab => { - pane.update(cx, |_, cx| { - cx.emit(pane::Event::ChangeItemTitle); - cx.notify(); + 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 = WorkspaceSettings::get_global(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) }); } - - ItemEvent::Edit => { - let autosave = WorkspaceSettings::get_global(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) - }); - } - } - - _ => {} } + + _ => {} } - })); + } + })); todo!("observe focus"); // cx.observe_focus(self, move |workspace, item, focused, cx| { @@ -494,12 +493,12 @@ impl ItemHandle for View { // }) // .detach(); - let item_id = self.id(); - cx.observe_release(self, move |workspace, _, _| { - workspace.panes_by_item.remove(&item_id); - event_subscription.take(); - }) - .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| { diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index b1efd35b6b..13b0560a19 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -1,21 +1,14 @@ -use crate::{ - item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, -}; +use crate::{Axis, WorkspaceId}; use anyhow::{Context, Result}; -use async_recursion::async_recursion; use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{ - AsyncAppContext, AsyncWindowContext, Model, Task, View, WeakView, WindowBounds, WindowHandle, -}; -use project2::Project; +use gpui2::WindowBounds; use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::ResultExt; use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Eq)] @@ -149,73 +142,75 @@ impl Default for SerializedPaneGroup { } } -impl SerializedPaneGroup { - #[async_recursion(?Send)] - pub(crate) async fn deserialize( - self, - project: &Model, - workspace_id: WorkspaceId, - workspace: WindowHandle, - cx: &mut AsyncAppContext, - ) -> Option<(Member, Option>, Vec>>)> { - match self { - SerializedPaneGroup::Group { - axis, - children, - flexes, - } => { - let mut current_active_pane = None; - let mut members = Vec::new(); - let mut items = Vec::new(); - for child in children { - if let Some((new_member, active_pane, new_items)) = child - .deserialize(project, workspace_id, workspace, cx) - .await - { - members.push(new_member); - items.extend(new_items); - current_active_pane = current_active_pane.or(active_pane); - } - } +// impl SerializedPaneGroup { +// #[async_recursion(?Send)] +// pub(crate) async fn deserialize( +// self, +// project: &Model, +// workspace_id: WorkspaceId, +// workspace: WeakView, +// cx: &mut AsyncAppContext, +// ) -> Option<(Member, Option>, Vec>>)> { +// match self { +// SerializedPaneGroup::Group { +// axis, +// children, +// flexes, +// } => { +// let mut current_active_pane = None; +// let mut members = Vec::new(); +// let mut items = Vec::new(); +// for child in children { +// if let Some((new_member, active_pane, new_items)) = child +// .deserialize(project, workspace_id, workspace, cx) +// .await +// { +// members.push(new_member); +// items.extend(new_items); +// current_active_pane = current_active_pane.or(active_pane); +// } +// } - if members.is_empty() { - return None; - } +// if members.is_empty() { +// return None; +// } - if members.len() == 1 { - return Some((members.remove(0), current_active_pane, items)); - } +// if members.len() == 1 { +// return Some((members.remove(0), current_active_pane, items)); +// } - Some(( - Member::Axis(PaneAxis::load(axis, members, flexes)), - current_active_pane, - items, - )) - } - SerializedPaneGroup::Pane(serialized_pane) => { - let pane = workspace - .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade()) - .log_err()?; - let active = serialized_pane.active; - let new_items = serialized_pane - .deserialize_to(project, &pane, workspace_id, workspace, cx) - .await - .log_err()?; +// Some(( +// Member::Axis(PaneAxis::load(axis, members, flexes)), +// current_active_pane, +// items, +// )) +// } +// SerializedPaneGroup::Pane(serialized_pane) => { +// let pane = workspace +// .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade()) +// .log_err()?; +// let active = serialized_pane.active; +// let new_items = serialized_pane +// .deserialize_to(project, &pane, workspace_id, workspace, cx) +// .await +// .log_err()?; - if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? { - let pane = pane.upgrade()?; - Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) - } else { - let pane = pane.upgrade()?; - workspace - .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx)) - .log_err()?; - None - } - } - } - } -} +// // todo!(); +// // if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? { +// // let pane = pane.upgrade()?; +// // Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) +// // } else { +// // let pane = pane.upgrade()?; +// // workspace +// // .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx)) +// // .log_err()?; +// // None +// // } +// None +// } +// } +// } +// } #[derive(Debug, PartialEq, Eq, Default, Clone)] pub struct SerializedPane { @@ -228,53 +223,55 @@ impl SerializedPane { SerializedPane { children, active } } - pub async fn deserialize_to( - &self, - project: &Model, - pane: &WeakView, - workspace_id: WorkspaceId, - workspace: WindowHandle, - cx: &mut AsyncAppContext, - ) -> Result>>> { - let mut items = Vec::new(); - let mut active_item_index = None; - for (index, item) in self.children.iter().enumerate() { - let project = project.clone(); - let item_handle = pane - .update(cx, |_, cx| { - if let Some(deserializer) = cx.global::().get(&item.kind) { - deserializer(project, workspace, workspace_id, item.item_id, cx) - } else { - Task::ready(Err(anyhow::anyhow!( - "Deserializer does not exist for item kind: {}", - item.kind - ))) - } - })? - .await - .log_err(); + // pub async fn deserialize_to( + // &self, + // _project: &Model, + // _pane: &WeakView, + // _workspace_id: WorkspaceId, + // _workspace: WindowHandle, + // _cx: &mut AsyncAppContext, + // ) -> Result>>> { + // anyhow::bail!("todo!()") + // // todo!() + // // let mut items = Vec::new(); + // // let mut active_item_index = None; + // // for (index, item) in self.children.iter().enumerate() { + // // let project = project.clone(); + // // let item_handle = pane + // // .update(cx, |_, cx| { + // // if let Some(deserializer) = cx.global::().get(&item.kind) { + // // deserializer(project, workspace, workspace_id, item.item_id, cx) + // // } else { + // // Task::ready(Err(anyhow::anyhow!( + // // "Deserializer does not exist for item kind: {}", + // // item.kind + // // ))) + // // } + // // })? + // // .await + // // .log_err(); - items.push(item_handle.clone()); + // // items.push(item_handle.clone()); - if let Some(item_handle) = item_handle { - pane.update(cx, |pane, cx| { - pane.add_item(item_handle.clone(), true, true, None, cx); - })?; - } + // // if let Some(item_handle) = item_handle { + // // pane.update(cx, |pane, cx| { + // // pane.add_item(item_handle.clone(), true, true, None, cx); + // // })?; + // // } - if item.active { - active_item_index = Some(index); - } - } + // // if item.active { + // // active_item_index = Some(index); + // // } + // // } - if let Some(active_item_index) = active_item_index { - pane.update(cx, |pane, cx| { - pane.activate_item(active_item_index, false, false, cx); - })?; - } + // // if let Some(active_item_index) = active_item_index { + // // pane.update(cx, |pane, cx| { + // // pane.activate_item(active_item_index, false, false, cx); + // // })?; + // // } - anyhow::Ok(items) - } + // // anyhow::Ok(items) + // } } pub type GroupId = i64; @@ -288,15 +285,15 @@ pub struct SerializedItem { pub active: bool, } -impl SerializedItem { - pub fn new(kind: impl AsRef, item_id: ItemId, active: bool) -> Self { - Self { - kind: Arc::from(kind.as_ref()), - item_id, - active, - } - } -} +// impl SerializedItem { +// pub fn new(kind: impl AsRef, item_id: ItemId, active: bool) -> Self { +// Self { +// kind: Arc::from(kind.as_ref()), +// item_id, +// active, +// } +// } +// } #[cfg(test)] impl Default for SerializedItem { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index aa57b317d2..f813dd5c03 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -25,16 +25,16 @@ use dock::{Dock, DockPosition, PanelButtons}; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - FutureExt, StreamExt, + Future, FutureExt, StreamExt, }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, - Context, Div, Entity, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, - Render, Size, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, - WindowContext, WindowHandle, WindowOptions, + Context, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; -use language2::LanguageRegistry; +use language2::{LanguageRegistry, LocalFile}; use lazy_static::lazy_static; use node_runtime::NodeRuntime; use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; @@ -412,7 +412,7 @@ pub fn register_deserializable_item(cx: &mut AppContext) { Arc::from(serialized_item_kind), |project, workspace, workspace_id, item_id, cx| { let task = I::deserialize(project, workspace, workspace_id, item_id, cx); - cx.spawn_on_main(|cx| async { Ok(Box::new(task.await?) as Box<_>) }) + cx.spawn_on_main(|_| async { Ok(Box::new(task.await?) as Box<_>) }) }, ); } @@ -428,10 +428,10 @@ pub struct AppState { pub build_window_options: fn(Option, Option, &mut MainThread) -> WindowOptions, pub initialize_workspace: fn( - WindowHandle, + WeakView, bool, Arc, - AsyncAppContext, + AsyncWindowContext, ) -> Task>, pub node_runtime: Arc, } @@ -568,6 +568,9 @@ pub struct Workspace { pane_history_timestamp: Arc, } +trait AssertSend: Send {} +impl AssertSend for WindowHandle {} + // struct ActiveModal { // view: Box, // previously_focused_view_id: Option, @@ -700,7 +703,7 @@ impl Workspace { cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); - let status_bar = cx.build_view(|cx| { + let _status_bar = cx.build_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); @@ -791,12 +794,14 @@ impl Workspace { fn new_local( abs_paths: Vec, app_state: Arc, - requesting_window: Option>, + _requesting_window: Option>, cx: &mut MainThread, - ) -> Task<( - WeakView, - Vec, anyhow::Error>>>, - )> { + ) -> Task< + anyhow::Result<( + WindowHandle, + Vec, anyhow::Error>>>, + )>, + > { let project_handle = Project::local( app_state.client.clone(), app_state.node_runtime.clone(), @@ -807,7 +812,7 @@ impl Workspace { ); cx.spawn_on_main(|mut cx| async move { - let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + let serialized_workspace: Option = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice()); let paths_to_open = Arc::new(abs_paths); @@ -836,14 +841,15 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; - let window = if let Some(window) = requesting_window { + // todo!() + let window = /*if let Some(window) = requesting_window { cx.update_window(window.into(), |old_workspace, cx| { cx.replace_root_view(|cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); }); window - } else { + } else */ { let window_bounds_override = window_bounds_env_override(&cx); let (bounds, display) = if let Some(bounds) = window_bounds_override { (Some(bounds), None) @@ -873,23 +879,34 @@ impl Workspace { // Use the serialized workspace to construct the new window let options = cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?; - cx.open_window(options, |cx| { + + cx.open_window(options, { + let app_state = app_state.clone(); + let workspace_id = workspace_id.clone(); + let project_handle = project_handle.clone(); + move |cx| { cx.build_view(|cx| { - Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + Workspace::new(workspace_id, project_handle, app_state, cx) }) - })? - }; + }})? + }; + + // todo!() Ask how to do this + let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?; + let async_cx = window.update(&mut cx, |_, cx| cx.to_async())?; (app_state.initialize_workspace)( - window, + weak_view, serialized_workspace.is_some(), app_state.clone(), - cx.clone(), + async_cx, ) .await .log_err(); - window.update(&mut cx, |_, cx| cx.activate_window()); + window + .update(&mut cx, |_, cx| cx.activate_window()) + .log_err(); notify_if_database_failed(window, &mut cx); let opened_items = window @@ -897,16 +914,16 @@ impl Workspace { let workspace = cx.view().downgrade(); open_items( serialized_workspace, - &workspace, + // &workspace, project_paths, app_state, cx, ) - }) + })? .await .unwrap_or_default(); - (window, opened_items) + Ok((window, opened_items)) }) } @@ -2102,9 +2119,9 @@ impl Workspace { > { let project = self.project().clone(); let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); - cx.spawn(|_, cx| async move { + cx.spawn(|_, mut cx| async move { let (project_entry_id, project_item) = project_item.await?; - let build_item = cx.update(|cx| { + let build_item = cx.update(|_, cx| { cx.default_global::() .get(&project_item.type_id()) .ok_or_else(|| anyhow!("no item builder for project item")) @@ -2747,7 +2764,7 @@ impl Workspace { title.push_str(" ↗"); } - todo!() + // todo!() // cx.set_window_title(&title); } @@ -3372,122 +3389,126 @@ impl Workspace { cx: &mut ViewContext, ) -> Task>>>> { cx.spawn(|workspace, mut cx| async move { - let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + // let (project, old_center_pane) = workspace.update(&mut 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 center_group: Option = None; + // // let mut center_items: Option>>> = None; - let mut items_by_project_path = cx.update(|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::>() - })?; + // // todo!() + // // // 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 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::>(); + // let mut items_by_project_path = cx.update(|_, 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::>() + // })?; - // 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); + // 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::>(); - // Swap workspace center group - workspace.center = PaneGroup::with_root(center_group); + // todo!() + // // 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); - // Change the focus to the workspace first so that we retrigger focus in on the pane. - cx.focus_self(); + // // Swap workspace center group + // workspace.center = PaneGroup::with_root(center_group); - 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()); - if let Some(old_center_handle) = old_center_handle { - cx.focus(&old_center_handle) - } else { - cx.focus_self() - } - } + // // Change the focus to the workspace first so that we retrigger focus in on the pane. + // 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 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()); + // if let Some(old_center_handle) = old_center_handle { + // cx.focus(&old_center_handle) + // } else { + // cx.focus_self() + // } + // } - 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); - } - } + // 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)); - dock.active_panel() - .map(|panel| panel.set_zoomed(docks.bottom.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); + // } + // } - if docks.bottom.visible && docks.bottom.zoom { - cx.focus_self() - } - }); + // dock.active_panel() + // .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); - cx.notify(); - })?; + // 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.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?; + // workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?; - Ok(opened_items) + // Ok(opened_items) + anyhow::bail!("todo") }) } @@ -3558,49 +3579,50 @@ fn window_bounds_env_override(cx: &MainThread) -> Option, - mut project_paths_to_open: Vec<(PathBuf, Option)>, +fn open_items( + _serialized_workspace: Option, + project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, - mut cx: &mut MainThread>, -) -> Result>>>> { + cx: &mut MainThread>, +) -> impl Future>>>>> { let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - if let Some(serialized_workspace) = serialized_workspace { - let restored_items = Workspace::load_workspace( - serialized_workspace, - project_paths_to_open - .iter() - .map(|(_, project_path)| project_path) - .cloned() - .collect(), - cx, - ) - .await?; + // todo!() + // if let Some(serialized_workspace) = serialized_workspace { + // let restored_items = Workspace::load_workspace( + // serialized_workspace, + // project_paths_to_open + // .iter() + // .map(|(_, project_path)| project_path) + // .cloned() + // .collect(), + // cx, + // ) + // .await?; - let restored_project_paths = restored_items - .iter() - .filter_map(|item| item.as_ref()?.project_path(cx)) - .collect::>(); + // let restored_project_paths = restored_items + // .iter() + // .filter_map(|item| item.as_ref()?.project_path(cx)) + // .collect::>(); - for restored_item in restored_items { - opened_items.push(restored_item.map(Ok)); - } + // 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); - } + // 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 = @@ -3629,16 +3651,17 @@ async fn open_items( }) }); - 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); + let tasks = tasks.collect::>(); + async move { + let tasks = futures::future::join_all(tasks.into_iter()); + for maybe_opened_path in tasks.await.into_iter() { + if let Some((i, path_open_result)) = maybe_opened_path { + opened_items[i] = Some(path_open_result); + } } - } - Ok(opened_items) + Ok(opened_items) + } } // fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { @@ -4102,8 +4125,8 @@ pub async fn activate_workspace_for_project( continue; }; - let predicate = cx - .update_window_root(&workspace, |workspace, cx| { + let predicate = workspace + .update(cx, |workspace, cx| { let project = workspace.project.read(cx); if predicate(project, cx) { cx.activate_window(); @@ -4326,10 +4349,11 @@ pub fn open_paths( > { let app_state = app_state.clone(); let abs_paths = abs_paths.to_vec(); - cx.spawn(move |mut cx| async move { + cx.spawn_on_main(move |mut cx| async move { // Open paths in existing workspace if possible - let existing = activate_workspace_for_project(&mut cx, move |project, cx| { - project.contains_paths(&abs_paths, cx) + let existing = activate_workspace_for_project(&mut cx, { + let abs_paths = abs_paths.clone(); + move |project, cx| project.contains_paths(&abs_paths, cx) }) .await; @@ -4343,32 +4367,30 @@ pub fn open_paths( // )) todo!() } else { - // Ok(cx - // .update(|cx| { - // Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) - // }) - // .await) - todo!() + cx.update(move |cx| { + Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) + })? + .await } }) } pub fn open_new( app_state: &Arc, - cx: &mut AppContext, - init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static, + cx: &mut MainThread, + init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, ) -> Task<()> { let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); - cx.spawn(|mut cx| async move { - let (workspace, opened_paths) = task.await; - - workspace - .update(&mut cx, |workspace, cx| { - if opened_paths.is_empty() { - init(workspace, cx) - } - }) - .log_err(); + cx.spawn_on_main(|mut cx| async move { + if let Some((workspace, opened_paths)) = task.await.log_err() { + workspace + .update(&mut cx, |workspace, cx| { + if opened_paths.is_empty() { + init(workspace, cx) + } + }) + .log_err(); + } }) } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index b5b22db140..a3535960cd 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -12,7 +12,7 @@ use client2::UserStore; use db2::kvp::KEY_VALUE_STORE; use fs2::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; -use gpui2::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; +use gpui2::{Action, App, AppContext, AsyncAppContext, Context, MainThread, SemanticVersion, Task}; use isahc::{prelude::Configurable, Request}; use language2::LanguageRegistry; use log::LevelFilter; @@ -24,7 +24,7 @@ use settings2::{ default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, }; use simplelog::ConfigBuilder; -use smol::process::Command; +use smol::{future::FutureExt, process::Command}; use std::{ env, ffi::OsStr, @@ -40,6 +40,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; use util::{ + async_maybe, channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, http::{self, HttpClient}, paths, ResultExt, @@ -242,7 +243,7 @@ fn main() { // .detach_and_log_err(cx) } Ok(None) | Err(_) => cx - .spawn({ + .spawn_on_main({ let app_state = app_state.clone(); |cx| async move { restore_or_create_workspace(&app_state, cx).await } }) @@ -313,21 +314,33 @@ async fn installation_id() -> Result { } } -async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { - if let Some(location) = workspace2::last_opened_workspace_paths().await { - cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))? - .await - .log_err(); - } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { - cx.update(|cx| show_welcome_experience(app_state, cx)); - } else { - cx.update(|cx| { - workspace2::open_new(app_state, cx, |workspace, cx| { - Editor::new_file(workspace, &Default::default(), cx) - }) - .detach(); - }); - } +async fn restore_or_create_workspace( + app_state: &Arc, + mut cx: MainThread, +) { + async_maybe!({ + if let Some(location) = workspace2::last_opened_workspace_paths().await { + cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))? + .await + .log_err(); + } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) { + // todo!(welcome) + //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + //todo!() + // cx.update(|cx| show_welcome_experience(app_state, cx)); + } else { + cx.update(|cx| { + workspace2::open_new(app_state, cx, |workspace, cx| { + // todo!(editor) + // Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + })?; + } + anyhow::Ok(()) + }) + .await + .log_err(); } fn init_paths() { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 7bc90c282c..d49bec8c56 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -7,7 +7,7 @@ pub use assets::*; use collections::HashMap; use gpui2::{ point, px, AppContext, AsyncAppContext, AsyncWindowContext, MainThread, Point, Task, - TitlebarOptions, WeakView, WindowBounds, WindowKind, WindowOptions, + TitlebarOptions, WeakView, WindowBounds, WindowHandle, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; @@ -165,7 +165,7 @@ pub async fn handle_cli_connection( if paths.is_empty() { let (done_tx, done_rx) = oneshot::channel(); let _subscription = - cx.update_window_root(&workspace, move |_, cx| { + workspace.update(&mut cx, move |_, cx| { cx.on_release(|_, _| { let _ = done_tx.send(()); }) From 6f38eb325251b5753c7dad9994326ee24bacd50f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 11:52:14 -0700 Subject: [PATCH 39/66] Finish main merge --- crates/storybook2/src/storybook2.rs | 4 ++-- crates/workspace2/src/item.rs | 8 ++++---- crates/workspace2/src/pane_group.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 3b5722732b..0ac84de392 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -82,8 +82,8 @@ fn main() { ..Default::default() }, move |cx| { - let theme_settings = ThemeSettings::get_global(cx); - cx.set_rem_size(theme_settings.ui_font_size); + let ui_font_size = ThemeSettings::get_global(cx).ui_font_size; + cx.set_rem_size(ui_font_size); cx.build_view(|cx| StoryWrapper::new(selector.story(cx))) }, diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index d839f39203..313a54d756 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -21,6 +21,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings2::Settings; use smallvec::SmallVec; +use theme2::ThemeVariant; use std::{ any::{Any, TypeId}, ops::Range, @@ -31,7 +32,6 @@ use std::{ }, time::Duration, }; -use theme2::Theme; #[derive(Deserialize)] pub struct ItemSettings { @@ -178,7 +178,7 @@ pub trait Item: Render + EventEmitter + Send { ToolbarItemLocation::Hidden } - fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { + fn breadcrumbs(&self, _theme: &ThemeVariant, _cx: &AppContext) -> Option> { None } @@ -259,7 +259,7 @@ pub trait ItemHandle: 'static + Send { ) -> 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 breadcrumbs(&self, theme: &ThemeVariant, 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>; @@ -585,7 +585,7 @@ impl ItemHandle for View { self.read(cx).breadcrumb_location() } - fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option> { + fn breadcrumbs(&self, theme: &ThemeVariant, cx: &AppContext) -> Option> { self.read(cx).breadcrumbs(theme, cx) } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index d537a1d2fb..f4fdb6ba16 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -11,7 +11,7 @@ use parking_lot::Mutex; use project2::Project; use serde::Deserialize; use std::sync::Arc; -use theme2::Theme; +use theme2::ThemeVariant; const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; @@ -124,7 +124,7 @@ impl PaneGroup { pub(crate) fn render( &self, project: &Model, - theme: &Theme, + theme: &ThemeVariant, follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, @@ -187,7 +187,7 @@ impl Member { &self, project: &Model, basis: usize, - theme: &Theme, + theme: &ThemeVariant, follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, @@ -510,7 +510,7 @@ impl PaneAxis { &self, project: &Model, basis: usize, - theme: &Theme, + theme: &ThemeVariant, follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, From ff277009139b840f153f02a536019bd59c865d23 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:08:47 +0100 Subject: [PATCH 40/66] Fix outstanding errors in gpui2 --- crates/gpui2/src/app.rs | 28 ++++++++++---------- crates/gpui2/src/app/async_context.rs | 37 ++++++++++++--------------- crates/gpui2/src/app/model_context.rs | 4 +-- crates/gpui2/src/app/test_context.rs | 4 +-- crates/gpui2/src/window.rs | 26 +++++++++---------- 5 files changed, 47 insertions(+), 52 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 97002a8304..f71619bc01 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -16,9 +16,9 @@ use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, - Pixels, Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, - SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, - WindowHandle, WindowId, + Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, + TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, + WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -115,8 +115,8 @@ type ActionBuilder = fn(json: Option) -> anyhow::Result; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; -type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; -type ReleaseListener = Box; +type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; +type ReleaseListener = Box; pub struct AppContext { this: Weak>, @@ -215,10 +215,9 @@ impl AppContext { pub fn quit(&mut self) { let mut futures = Vec::new(); - self.quit_observers.clone().retain(&(), |observer| { + for observer in self.quit_observers.remove(&()) { futures.push(observer(self)); - true - }); + } self.windows.clear(); self.flush_effects(); @@ -265,21 +264,22 @@ impl AppContext { pub(crate) fn update_window( &mut self, - id: WindowId, - update: impl FnOnce(&mut WindowContext) -> R, + handle: AnyWindowHandle, + update: impl FnOnce(AnyView, &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(); - let result = update(&mut WindowContext::new(cx, &mut window)); + let root_view = window.root_view.clone().unwrap(); + let result = update(root_view, &mut WindowContext::new(cx, &mut window)); cx.windows - .get_mut(id) + .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? .replace(window); @@ -432,7 +432,7 @@ impl AppContext { for (entity_id, mut entity) in dropped { self.observers.remove(&entity_id); self.event_listeners.remove(&entity_id); - for mut release_callback in self.release_listeners.remove(&entity_id) { + for release_callback in self.release_listeners.remove(&entity_id) { release_callback(entity.as_mut(), self); } } diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index ef7108584e..08ae307d1b 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,11 +1,10 @@ use crate::{ AnyView, AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, - WindowHandle, }; use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; -use std::{cell::RefCell, future::Future, mem, rc::Weak}; +use std::{cell::RefCell, future::Future, rc::Weak}; #[derive(Clone)] pub struct AsyncAppContext { @@ -88,14 +87,14 @@ impl AsyncAppContext { pub fn update_window( &self, handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, ) -> Result { let app = self .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; let mut app_context = app.borrow_mut(); - app_context.update_window(handle.id, update) + app_context.update_window(handle, update) } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task @@ -167,17 +166,14 @@ impl AsyncWindowContext { } pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { - self.app - .update_window(self.window, |cx| cx.on_next_frame(f)) - .ok(); + self.window.update(self, |_, cx| cx.on_next_frame(f)).ok(); } pub fn read_global( &mut self, read: impl FnOnce(&G, &WindowContext) -> R, ) -> Result { - self.app - .update_window(self.window, |cx| read(cx.global(), cx)) + self.window.update(self, |_, cx| read(cx.global(), cx)) } pub fn update_global( @@ -187,8 +183,7 @@ impl AsyncWindowContext { where G: 'static, { - self.app - .update_window(self.window, |cx| cx.update_global(update)) + self.window.update(self, |_, cx| cx.update_global(update)) } pub fn spawn( @@ -217,8 +212,8 @@ impl Context for AsyncWindowContext { where T: 'static, { - self.app - .update_window(self.window, |cx| cx.build_model(build_model)) + self.window + .update(self, |_, cx| cx.build_model(build_model)) } fn update_model( @@ -226,8 +221,8 @@ impl Context for AsyncWindowContext { handle: &Model, update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Result { - self.app - .update_window(self.window, |cx| cx.update_model(handle, update)) + self.window + .update(self, |_, cx| cx.update_model(handle, update)) } fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result @@ -248,8 +243,8 @@ impl VisualContext for AsyncWindowContext { where V: 'static, { - self.app - .update_window(self.window, |cx| cx.build_view(build_view_state)) + self.window + .update(self, |_, cx| cx.build_view(build_view_state)) } fn update_view( @@ -257,8 +252,8 @@ impl VisualContext for AsyncWindowContext { view: &View, update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, ) -> Self::Result { - self.app - .update_window(self.window, |cx| cx.update_view(view, update)) + self.window + .update(self, |_, cx| cx.update_view(view, update)) } fn replace_root_view( @@ -268,7 +263,7 @@ impl VisualContext for AsyncWindowContext { where V: 'static + Send + Render, { - self.app - .update_window(self.window, |cx| cx.replace_root_view(build_view)) + self.window + .update(self, |_, cx| cx.replace_root_view(build_view)) } } diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index a764cd7366..d72b039139 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -93,7 +93,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn on_release( &mut self, - mut on_release: impl FnMut(&mut T, &mut AppContext) + 'static, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, ) -> Subscription where T: 'static, @@ -110,7 +110,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn observe_release( &mut self, entity: &E, - mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static, + on_release: impl FnOnce(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where T: Any, diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9f6fb4d75a..ee1da50136 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -92,10 +92,10 @@ impl TestAppContext { pub fn update_window( &self, handle: AnyWindowHandle, - update: impl FnOnce(&mut WindowContext) -> R, + update: impl FnOnce(AnyView, &mut WindowContext) -> R, ) -> R { let mut app = self.app.borrow_mut(); - app.update_window(handle.id, update).unwrap() + app.update_window(handle, update).unwrap() } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index ab12f08a1c..f059e02a33 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -204,14 +204,15 @@ impl Window { platform_window.on_resize(Box::new({ let mut cx = cx.to_async(); move |content_size, scale_factor| { - cx.update_window(handle, |cx| { - cx.window.scale_factor = scale_factor; - cx.window.scene_builder = SceneBuilder::new(); - cx.window.content_size = content_size; - cx.window.display_id = cx.window.platform_window.display().id(); - cx.window.dirty = true; - }) - .log_err(); + handle + .update(&mut cx, |_, cx| { + cx.window.scale_factor = scale_factor; + cx.window.scene_builder = SceneBuilder::new(); + cx.window.content_size = content_size; + cx.window.display_id = cx.window.platform_window.display().id(); + cx.window.dirty = true; + }) + .log_err(); } })); @@ -416,7 +417,7 @@ impl<'a> WindowContext<'a> { return; } } else { - let async_cx = self.to_async(); + let mut async_cx = self.to_async(); self.next_frame_callbacks.insert(display_id, vec![f]); self.platform().set_display_link_output_callback( display_id, @@ -1681,7 +1682,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.view.model.entity_id, Box::new(move |this, cx| { let this = this.downcast_mut().expect("invalid entity type"); - // todo!("are we okay with silently swallowing the error?") let _ = window_handle.update(cx, |_, cx| on_release(this, cx)); }), ) @@ -1981,7 +1981,7 @@ impl WindowHandle { } pub fn update( - &self, + self, cx: &mut C, update: impl FnOnce(&mut V, &mut as UpdateView>::ViewContext<'_, V>) -> R, ) -> Result @@ -2052,14 +2052,14 @@ impl AnyWindowHandle { } pub fn update( - &self, + self, cx: &mut C, update: impl FnOnce(AnyView, &mut C::WindowContext<'_>) -> R, ) -> Result where C: Context, { - cx.update_window(*self, update) + cx.update_window(self, update) } } From 18fcb41292fe42532d15e0cfcea9c59db65b5685 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:15:14 +0100 Subject: [PATCH 41/66] Simplify contexts --- crates/gpui2/src/app.rs | 8 ++--- crates/gpui2/src/app/async_context.rs | 25 +++++-------- crates/gpui2/src/app/entity_map.rs | 6 ++-- crates/gpui2/src/app/model_context.rs | 8 ++--- crates/gpui2/src/app/test_context.rs | 8 ++--- crates/gpui2/src/gpui2.rs | 26 ++++---------- crates/gpui2/src/view.rs | 4 +-- crates/gpui2/src/window.rs | 51 ++++++++------------------- 8 files changed, 44 insertions(+), 92 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index f71619bc01..f08c7fb86b 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -753,8 +753,6 @@ impl AppContext { } impl Context for AppContext { - type WindowContext<'a> = WindowContext<'a>; - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; /// Build an entity that is owned by the application. The given function will be invoked with @@ -762,7 +760,7 @@ impl Context for AppContext { /// which can be used to access the entity in a context. fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Model { self.update(|cx| { let slot = cx.entities.reserve(); @@ -776,7 +774,7 @@ impl Context for AppContext { fn update_model( &mut self, model: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> R { self.update(|cx| { let mut entity = cx.entities.lease(model); @@ -788,7 +786,7 @@ impl Context for AppContext { fn update_window(&mut self, handle: AnyWindowHandle, update: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { self.update(|cx| { let mut window = cx diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 08ae307d1b..823031b11a 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -14,13 +14,11 @@ pub struct AsyncAppContext { } impl Context for AsyncAppContext { - type WindowContext<'a> = WindowContext<'a>; - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = Result; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Self::Result> where T: 'static, @@ -36,7 +34,7 @@ impl Context for AsyncAppContext { fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { let app = self .app @@ -48,7 +46,7 @@ impl Context for AsyncAppContext { fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { let app = self.app.upgrade().context("app was released")?; let mut lock = app.borrow_mut(); @@ -200,14 +198,11 @@ impl AsyncWindowContext { } impl Context for AsyncWindowContext { - type WindowContext<'a> = WindowContext<'a>; - type ModelContext<'a, T> = ModelContext<'a, T>; - type Result = Result; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Result> where T: 'static, @@ -219,7 +214,7 @@ impl Context for AsyncWindowContext { fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Result { self.window .update(self, |_, cx| cx.update_model(handle, update)) @@ -227,18 +222,16 @@ impl Context for AsyncWindowContext { fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { self.app.update_window(window, update) } } impl VisualContext for AsyncWindowContext { - type ViewContext<'a, V: 'static> = ViewContext<'a, V>; - fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static, @@ -250,7 +243,7 @@ impl VisualContext for AsyncWindowContext { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Self::Result { self.window .update(self, |_, cx| cx.update_view(view, update)) @@ -258,7 +251,7 @@ impl VisualContext for AsyncWindowContext { fn replace_root_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send + Render, diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 6faa85bac9..5b3462b91e 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -1,4 +1,4 @@ -use crate::{private::Sealed, AnyBox, AppContext, Context, Entity}; +use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; @@ -334,7 +334,7 @@ impl Model { pub fn update( &self, cx: &mut C, - update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> C::Result where C: Context, @@ -480,7 +480,7 @@ impl WeakModel { pub fn update( &self, cx: &mut C, - update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Result where C: Context, diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index d72b039139..cb25adfb63 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -213,13 +213,11 @@ where } impl<'a, T> Context for ModelContext<'a, T> { - type WindowContext<'b> = WindowContext<'b>; - type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U, + build_model: impl FnOnce(&mut ModelContext<'_, U>) -> U, ) -> Model { self.app.build_model(build_model) } @@ -227,14 +225,14 @@ impl<'a, T> Context for ModelContext<'a, T> { fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R, + update: impl FnOnce(&mut U, &mut ModelContext<'_, U>) -> R, ) -> R { self.app.update_model(handle, update) } fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> R, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> R, { self.app.update_window(window, update) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index ee1da50136..27275d3a04 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -15,13 +15,11 @@ pub struct TestAppContext { } impl Context for TestAppContext { - type WindowContext<'a> = WindowContext<'a>; - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Self::Result> where T: 'static, @@ -33,7 +31,7 @@ impl Context for TestAppContext { fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { let mut app = self.app.borrow_mut(); app.update_model(handle, update) @@ -41,7 +39,7 @@ impl Context for TestAppContext { fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { let mut lock = self.app.borrow_mut(); lock.update_window(window, f) diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 9801167583..bd65ade0d3 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -74,34 +74,30 @@ use taffy::TaffyLayoutEngine; type AnyBox = Box; pub trait Context { - type WindowContext<'a>: UpdateView; - type ModelContext<'a, T>; type Result; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Self::Result>; fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result where T: 'static; fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T; + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T; } pub trait VisualContext: Context { - type ViewContext<'a, V: 'static>; - fn build_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static; @@ -109,27 +105,17 @@ pub trait VisualContext: Context { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Self::Result; fn replace_root_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send + Render; } -pub trait UpdateView { - type ViewContext<'a, V: 'static>; - - fn update_view( - &mut self, - view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, - ) -> R; -} - pub trait Entity: Sealed { type Weak: 'static + Send; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 82214b381d..165eedef9c 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -53,7 +53,7 @@ impl View { pub fn update( &self, cx: &mut C, - f: impl FnOnce(&mut V, &mut C::ViewContext<'_, V>) -> R, + f: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> C::Result where C: VisualContext, @@ -152,7 +152,7 @@ impl WeakView { pub fn update( &self, cx: &mut C, - f: impl FnOnce(&mut V, &mut C::ViewContext<'_, V>) -> R, + f: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Result where C: VisualContext, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f059e02a33..4435b0e927 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -7,7 +7,7 @@ use crate::{ MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, UpdateView, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, + UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -1233,13 +1233,11 @@ impl<'a> WindowContext<'a> { } impl Context for WindowContext<'_> { - type WindowContext<'a> = WindowContext<'a>; - type ModelContext<'a, T> = ModelContext<'a, T>; type Result = T; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Model where T: 'static, @@ -1252,7 +1250,7 @@ impl Context for WindowContext<'_> { fn update_model( &mut self, model: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> R { let mut entity = self.entities.lease(model); let result = update( @@ -1265,7 +1263,7 @@ impl Context for WindowContext<'_> { fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { if window == self.window.handle { let root_view = self.window.root_view.clone().unwrap(); @@ -1277,11 +1275,9 @@ impl Context for WindowContext<'_> { } impl VisualContext for WindowContext<'_> { - type ViewContext<'a, V: 'static> = ViewContext<'a, V>; - fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static, @@ -1300,7 +1296,7 @@ impl VisualContext for WindowContext<'_> { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ViewContext<'_, T>) -> R, ) -> Self::Result { let mut lease = self.app.entities.lease(&view.model); let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view); @@ -1311,7 +1307,7 @@ impl VisualContext for WindowContext<'_> { fn replace_root_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, V>) -> V, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static + Send + Render, @@ -1328,18 +1324,6 @@ impl VisualContext for WindowContext<'_> { } } -impl UpdateView for WindowContext<'_> { - type ViewContext<'a, V: 'static> = ViewContext<'a, V>; - - fn update_view( - &mut self, - view: &View, - update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, V>) -> R, - ) -> R { - VisualContext::update_view(self, view, update) - } -} - impl<'a> std::ops::Deref for WindowContext<'a> { type Target = AppContext; @@ -1882,13 +1866,11 @@ where } impl Context for ViewContext<'_, V> { - type WindowContext<'a> = WindowContext<'a>; - type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; fn build_model( &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Model { self.window_cx.build_model(build_model) } @@ -1896,25 +1878,23 @@ impl Context for ViewContext<'_, V> { fn update_model( &mut self, model: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, + update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> R { self.window_cx.update_model(model, update) } fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result where - F: FnOnce(AnyView, &mut Self::WindowContext<'_>) -> T, + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { self.window_cx.update_window(window, update) } } impl VisualContext for ViewContext<'_, V> { - type ViewContext<'a, W: 'static> = ViewContext<'a, W>; - fn build_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, W>) -> W, + build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, ) -> Self::Result> { self.window_cx.build_view(build_view) } @@ -1922,14 +1902,14 @@ impl VisualContext for ViewContext<'_, V> { fn update_view( &mut self, view: &View, - update: impl FnOnce(&mut V2, &mut Self::ViewContext<'_, V2>) -> R, + update: impl FnOnce(&mut V2, &mut ViewContext<'_, V2>) -> R, ) -> Self::Result { VisualContext::update_view(&mut self.window_cx, view, update) } fn replace_root_view( &mut self, - build_view: impl FnOnce(&mut Self::ViewContext<'_, W>) -> W, + build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, ) -> Self::Result> where W: 'static + Send + Render, @@ -1983,7 +1963,7 @@ impl WindowHandle { pub fn update( self, cx: &mut C, - update: impl FnOnce(&mut V, &mut as UpdateView>::ViewContext<'_, V>) -> R, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, ) -> Result where C: Context, @@ -1992,7 +1972,6 @@ impl WindowHandle { let view = root_view .downcast::() .map_err(|_| anyhow!("the type of the window's root view has changed"))?; - Ok(cx.update_view(&view, update)) })? } @@ -2054,7 +2033,7 @@ impl AnyWindowHandle { pub fn update( self, cx: &mut C, - update: impl FnOnce(AnyView, &mut C::WindowContext<'_>) -> R, + update: impl FnOnce(AnyView, &mut WindowContext<'_>) -> R, ) -> Result where C: Context, From b2c7ddc41f8479eff5bb42572cac3f411cd2bdc5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:18:16 +0100 Subject: [PATCH 42/66] Remove some stray Send bounds --- crates/gpui2/src/app/async_context.rs | 2 +- crates/gpui2/src/gpui2.rs | 2 +- crates/gpui2/src/window.rs | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 823031b11a..1f46578a97 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -254,7 +254,7 @@ impl VisualContext for AsyncWindowContext { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Send + Render, + V: Render, { self.window .update(self, |_, cx| cx.replace_root_view(build_view)) diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index bd65ade0d3..9ba0cb5d8a 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -113,7 +113,7 @@ pub trait VisualContext: Context { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Send + Render; + V: Render; } pub trait Entity: Sealed { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 4435b0e927..a3d9eeb8bf 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1310,7 +1310,7 @@ impl VisualContext for WindowContext<'_> { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Send + Render, + V: Render, { let slot = self.app.entities.reserve(); let view = View { @@ -1598,7 +1598,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn observe( &mut self, entity: &E, - mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, V>) + Send + 'static, + mut on_notify: impl FnMut(&mut V, E, &mut ViewContext<'_, V>) + 'static, ) -> Subscription where V2: 'static, @@ -1629,7 +1629,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, V>) + Send + 'static, + mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, V>) + 'static, ) -> Subscription where V2: EventEmitter, @@ -1659,7 +1659,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn on_release( &mut self, - on_release: impl FnOnce(&mut V, &mut WindowContext) + Send + 'static, + on_release: impl FnOnce(&mut V, &mut WindowContext) + 'static, ) -> Subscription { let window_handle = self.window.handle; self.app.release_listeners.insert( @@ -1674,10 +1674,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn observe_release( &mut self, entity: &E, - mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, V>) + Send + 'static, + mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, V>) + 'static, ) -> Subscription where - V: Any + Send, + V: 'static, V2: 'static, E: Entity, { @@ -1704,7 +1704,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn on_focus_changed( &mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) { let handle = self.view().downgrade(); self.window.focus_listeners.push(Box::new(move |event, cx| { @@ -1814,7 +1814,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where - G: 'static + Send, + G: 'static, { let mut global = self.app.lease_global::(); let result = f(&mut global, self); @@ -1824,7 +1824,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn observe_global( &mut self, - f: impl Fn(&mut V, &mut ViewContext<'_, V>) + Send + 'static, + f: impl Fn(&mut V, &mut ViewContext<'_, V>) + 'static, ) -> Subscription { let window_handle = self.window.handle; let view = self.view().downgrade(); @@ -1854,7 +1854,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { impl ViewContext<'_, V> where V: EventEmitter, - V::Event: 'static + Send, + V::Event: 'static, { pub fn emit(&mut self, event: V::Event) { let emitter = self.view.model.entity_id; @@ -1904,7 +1904,7 @@ impl VisualContext for ViewContext<'_, V> { view: &View, update: impl FnOnce(&mut V2, &mut ViewContext<'_, V2>) -> R, ) -> Self::Result { - VisualContext::update_view(&mut self.window_cx, view, update) + self.window_cx.update_view(view, update) } fn replace_root_view( @@ -1912,7 +1912,7 @@ impl VisualContext for ViewContext<'_, V> { build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, ) -> Self::Result> where - W: 'static + Send + Render, + W: Render, { self.window_cx.replace_root_view(build_view) } From 2fb4c04fc3ca06f2ed66fe788ccfc6d15b42f4fb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:38:28 +0100 Subject: [PATCH 43/66] Remove more Send bounds and remove `EraseViewState` --- crates/gpui2/src/action.rs | 4 +- crates/gpui2/src/app.rs | 2 +- crates/gpui2/src/app/async_context.rs | 11 +- crates/gpui2/src/app/entity_map.rs | 3 - crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/elements/div.rs | 1 - crates/gpui2/src/elements/text.rs | 3 - crates/gpui2/src/focusable.rs | 12 +- crates/gpui2/src/gpui2.rs | 6 +- crates/gpui2/src/view.rs | 159 +------------------------- crates/gpui2/src/window.rs | 16 +-- 11 files changed, 26 insertions(+), 193 deletions(-) diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 638e5c6ca3..84843c9876 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -4,7 +4,7 @@ use collections::{HashMap, HashSet}; use serde::Deserialize; use std::any::{type_name, Any}; -pub trait Action: Any + Send { +pub trait Action: 'static { fn qualified_name() -> SharedString where Self: Sized; @@ -19,7 +19,7 @@ pub trait Action: Any + Send { impl Action for A where - A: for<'a> Deserialize<'a> + PartialEq + Any + Send + Clone + Default, + A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + 'static, { fn qualified_name() -> SharedString { type_name::().into() diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index f08c7fb86b..48a9324b05 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -685,7 +685,7 @@ impl AppContext { pub fn observe_release( &mut self, handle: &E, - on_release: impl FnOnce(&mut T, &mut AppContext) + Send + 'static, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, ) -> Subscription where E: Entity, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 1f46578a97..01af7ae194 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -163,7 +163,7 @@ impl AsyncWindowContext { self.app.update_window(self.window, update) } - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { self.window.update(self, |_, cx| cx.on_next_frame(f)).ok(); } @@ -184,13 +184,10 @@ impl AsyncWindowContext { self.window.update(self, |_, cx| cx.update_global(update)) } - pub fn spawn( - &self, - f: impl FnOnce(AsyncWindowContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut + 'static) -> Task where - Fut: Future + Send + 'static, - R: Send + 'static, + Fut: Future + 'static, + R: 'static, { let this = self.clone(); self.foreground_executor.spawn(async move { f(this).await }) diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 5b3462b91e..e7c062fac7 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -451,9 +451,6 @@ pub struct WeakModel { entity_type: PhantomData, } -unsafe impl Send for WeakModel {} -unsafe impl Sync for WeakModel {} - impl Clone for WeakModel { fn clone(&self) -> Self { Self { diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index 39c8562b69..baf75b8aab 100644 --- a/crates/gpui2/src/assets.rs +++ b/crates/gpui2/src/assets.rs @@ -8,7 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -pub trait AssetSource: 'static + Send + Sync { +pub trait AssetSource: 'static { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 6fe10d94a3..56940efce4 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -305,7 +305,6 @@ where impl Component for Div where - // V: Any + Send + Sync, I: ElementInteraction, F: ElementFocus, { diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 3aff568c4c..4bc3705490 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -44,9 +44,6 @@ pub struct Text { state_type: PhantomData, } -unsafe impl Send for Text {} -unsafe impl Sync for Text {} - impl Component for Text { fn render(self) -> AnyElement { AnyElement::new(self) diff --git a/crates/gpui2/src/focusable.rs b/crates/gpui2/src/focusable.rs index d7cfc5fe8f..99f8bb1dd6 100644 --- a/crates/gpui2/src/focusable.rs +++ b/crates/gpui2/src/focusable.rs @@ -8,7 +8,7 @@ use smallvec::SmallVec; pub type FocusListeners = SmallVec<[FocusListener; 2]>; pub type FocusListener = - Box) + Send + 'static>; + Box) + 'static>; pub trait Focusable: Element { fn focus_listeners(&mut self) -> &mut FocusListeners; @@ -42,7 +42,7 @@ pub trait Focusable: Element { fn on_focus( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -58,7 +58,7 @@ pub trait Focusable: Element { fn on_blur( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -74,7 +74,7 @@ pub trait Focusable: Element { fn on_focus_in( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -99,7 +99,7 @@ pub trait Focusable: Element { fn on_focus_out( mut self, - listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -122,7 +122,7 @@ pub trait Focusable: Element { } } -pub trait ElementFocus: 'static + Send { +pub trait ElementFocus: 'static { fn as_focusable(&self) -> Option<&FocusEnabled>; fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled>; diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 9ba0cb5d8a..49cc3cebc2 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -117,7 +117,7 @@ pub trait VisualContext: Context { } pub trait Entity: Sealed { - type Weak: 'static + Send; + type Weak: 'static; fn entity_id(&self) -> EntityId; fn downgrade(&self) -> Self::Weak; @@ -137,7 +137,7 @@ pub trait BorrowAppContext { where F: FnOnce(&mut Self) -> R; - fn set_global(&mut self, global: T); + fn set_global(&mut self, global: T); } impl BorrowAppContext for C @@ -154,7 +154,7 @@ where result } - fn set_global(&mut self, global: G) { + fn set_global(&mut self, global: G) { self.borrow_mut().set_global(global) } } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 165eedef9c..3cc4fdd4e3 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -7,7 +7,6 @@ use anyhow::{Context, Result}; use std::{ any::{Any, TypeId}, hash::{Hash, Hasher}, - marker::PhantomData, }; pub trait Render: 'static + Sized { @@ -90,53 +89,7 @@ impl Eq for View {} impl Component for View { fn render(self) -> AnyElement { - AnyElement::new(EraseViewState { - view: self, - parent_view_state_type: PhantomData, - }) - } -} - -impl Element<()> for View -where - V: Render, -{ - type ElementState = AnyElement; - - fn id(&self) -> Option { - Some(ElementId::View(self.model.entity_id)) - } - - fn initialize( - &mut self, - _: &mut (), - _: Option, - cx: &mut ViewContext<()>, - ) -> Self::ElementState { - self.update(cx, |state, cx| { - let mut any_element = AnyElement::new(state.render(cx)); - any_element.initialize(state, cx); - any_element - }) - } - - fn layout( - &mut self, - _: &mut (), - element: &mut Self::ElementState, - cx: &mut ViewContext<()>, - ) -> LayoutId { - self.update(cx, |state, cx| element.layout(state, cx)) - } - - fn paint( - &mut self, - _: Bounds, - _: &mut (), - element: &mut Self::ElementState, - cx: &mut ViewContext<()>, - ) { - self.update(cx, |state, cx| element.paint(state, cx)) + AnyElement::new(AnyView::from(self)) } } @@ -185,116 +138,6 @@ impl PartialEq for WeakView { impl Eq for WeakView {} -struct EraseViewState { - view: View, - parent_view_state_type: PhantomData, -} - -unsafe impl Send for EraseViewState {} - -impl Component for EraseViewState { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl Element for EraseViewState { - type ElementState = Box; - - fn id(&self) -> Option { - Element::id(&self.view) - } - - fn initialize( - &mut self, - _: &mut ParentV, - _: Option, - cx: &mut ViewContext, - ) -> Self::ElementState { - ViewObject::initialize(&mut self.view, cx) - } - - fn layout( - &mut self, - _: &mut ParentV, - element: &mut Self::ElementState, - cx: &mut ViewContext, - ) -> LayoutId { - ViewObject::layout(&mut self.view, element, cx) - } - - fn paint( - &mut self, - bounds: Bounds, - _: &mut ParentV, - element: &mut Self::ElementState, - cx: &mut ViewContext, - ) { - ViewObject::paint(&mut self.view, bounds, element, cx) - } -} - -trait ViewObject: Send + Sync { - fn entity_type(&self) -> TypeId; - fn entity_id(&self) -> EntityId; - fn model(&self) -> AnyModel; - fn initialize(&self, cx: &mut WindowContext) -> AnyBox; - fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId; - fn paint(&self, bounds: Bounds, element: &mut AnyBox, cx: &mut WindowContext); - fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; -} - -impl ViewObject for View -where - V: Render, -{ - fn entity_type(&self) -> TypeId { - TypeId::of::() - } - - fn entity_id(&self) -> EntityId { - Entity::entity_id(self) - } - - fn model(&self) -> AnyModel { - self.model.clone().into_any() - } - - fn initialize(&self, cx: &mut WindowContext) -> AnyBox { - cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| { - self.update(cx, |state, cx| { - let mut any_element = Box::new(AnyElement::new(state.render(cx))); - any_element.initialize(state, cx); - any_element - }) - }) - } - - fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId { - cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| { - self.update(cx, |state, cx| { - let element = element.downcast_mut::>().unwrap(); - element.layout(state, cx) - }) - }) - } - - fn paint(&self, _: Bounds, element: &mut AnyBox, cx: &mut WindowContext) { - cx.with_element_id(ViewObject::entity_id(self), |_global_id, cx| { - self.update(cx, |state, cx| { - let element = element.downcast_mut::>().unwrap(); - element.paint(state, cx); - }); - }); - } - - fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct(&format!("AnyView<{}>", std::any::type_name::())) - .field("entity_id", &ViewObject::entity_id(self).as_u64()) - .finish() - } -} - #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a3d9eeb8bf..5f2de2e428 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -362,7 +362,7 @@ impl<'a> WindowContext<'a> { /// Schedules the given function to be run at the end of the current effect cycle, allowing entities /// that are currently on the stack to be returned to the app. - pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static + Send) { + pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { let handle = self.window.handle; self.app.defer(move |cx| { handle.update(cx, |_, cx| f(cx)).ok(); @@ -372,7 +372,7 @@ impl<'a> WindowContext<'a> { pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + Send + 'static, + mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + 'static, ) -> Subscription where Emitter: EventEmitter, @@ -406,7 +406,7 @@ impl<'a> WindowContext<'a> { } /// Schedule the given closure to be run directly after the current frame is rendered. - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { let f = Box::new(f); let display_id = self.window.display_id; @@ -1144,7 +1144,7 @@ impl<'a> WindowContext<'a> { /// is updated. pub fn observe_global( &mut self, - f: impl Fn(&mut WindowContext<'_>) + Send + 'static, + f: impl Fn(&mut WindowContext<'_>) + 'static, ) -> Subscription { let window_handle = self.window.handle; self.global_observers.insert( @@ -1578,9 +1578,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { result } - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + Send + 'static) + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) where - V: Any + Send, + V: 'static, { let view = self.view(); self.window_cx.on_next_frame(move |cx| view.update(cx, f)); @@ -1588,7 +1588,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { /// Schedules the given function to be run at the end of the current effect cycle, allowing entities /// that are currently on the stack to be returned to the app. - pub fn defer(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static + Send) { + pub fn defer(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) { let view = self.view().downgrade(); self.window_cx.defer(move |cx| { view.update(cx, f).ok(); @@ -1602,7 +1602,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) -> Subscription where V2: 'static, - V: 'static + Send, + V: 'static, E: Entity, { let view = self.view().downgrade(); From 51338d785ca4cfb2d16a5845cc6a87ac4ae64048 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 10:09:08 +0100 Subject: [PATCH 44/66] WIP --- crates/gpui2/src/app/entity_map.rs | 3 +++ crates/gpui2/src/assets.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 3ece55fe0a..e626f8c409 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -450,6 +450,9 @@ pub struct WeakModel { entity_type: PhantomData, } +unsafe impl Send for WeakModel {} +unsafe impl Sync for WeakModel {} + impl Clone for WeakModel { fn clone(&self) -> Self { Self { diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index baf75b8aab..39c8562b69 100644 --- a/crates/gpui2/src/assets.rs +++ b/crates/gpui2/src/assets.rs @@ -8,7 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -pub trait AssetSource: 'static { +pub trait AssetSource: 'static + Send + Sync { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } From 32dded551c7edf742ac9484f7ff84f4dce884a93 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 11:05:24 +0100 Subject: [PATCH 45/66] Checkpoint --- crates/workspace2/src/item.rs | 20 +- crates/workspace2/src/pane.rs | 488 ++++++++++++++-------------- crates/workspace2/src/searchable.rs | 2 +- crates/workspace2/src/workspace2.rs | 117 ++++--- crates/zed2/src/main.rs | 9 +- crates/zed2/src/zed2.rs | 14 +- 6 files changed, 325 insertions(+), 325 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 313a54d756..ebc51942d0 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -12,8 +12,8 @@ use client2::{ Client, }; use gpui2::{ - AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render, - SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle, + AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels, + Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -21,7 +21,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings2::Settings; use smallvec::SmallVec; -use theme2::ThemeVariant; use std::{ any::{Any, TypeId}, ops::Range, @@ -32,6 +31,7 @@ use std::{ }, time::Duration, }; +use theme2::ThemeVariant; #[derive(Deserialize)] pub struct ItemSettings { @@ -237,7 +237,7 @@ pub trait ItemHandle: 'static + Send { 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 id(&self) -> EntityId; fn to_any(&self) -> AnyView; fn is_dirty(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool; @@ -266,7 +266,7 @@ pub trait ItemHandle: 'static + Send { } pub trait WeakItemHandle: Send + Sync { - fn id(&self) -> usize; + fn id(&self) -> EntityId; fn upgrade(&self) -> Option>; } @@ -518,8 +518,8 @@ impl ItemHandle for View { self.update(cx, |this, cx| this.navigate(data, cx)) } - fn id(&self) -> usize { - self.id() + fn id(&self) -> EntityId { + self.entity_id() } fn to_any(&self) -> AnyView { @@ -621,8 +621,8 @@ impl Clone for Box { } impl WeakItemHandle for WeakView { - fn id(&self) -> usize { - self.id() + fn id(&self) -> EntityId { + self.entity_id() } fn upgrade(&self) -> Option> { @@ -695,7 +695,7 @@ impl FollowableItemHandle for View { self.read(cx).remote_id().or_else(|| { client.peer_id().map(|creator| ViewId { creator, - id: self.id() as u64, + id: self.id().as_u64(), }) }) } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 7357b2e8c2..af7056e00f 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -3,26 +3,29 @@ use crate::{ item::{Item, ItemHandle, WeakItemHandle}, toolbar::Toolbar, + workspace_settings::{AutosaveSetting, WorkspaceSettings}, SplitDirection, Workspace, }; use anyhow::Result; -use collections::{HashMap, VecDeque}; +use collections::{HashMap, HashSet, VecDeque}; use gpui2::{ - AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView, - WindowContext, + AppContext, AsyncWindowContext, EntityId, EventEmitter, Model, PromptLevel, Task, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; +use settings2::Settings; use std::{ any::Any, cmp, fmt, mem, - path::PathBuf, + path::{Path, PathBuf}, sync::{ atomic::{AtomicUsize, Ordering}, Arc, }, }; +use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -132,7 +135,7 @@ pub enum Event { AddItem { item: Box }, ActivateItem { local: bool }, Remove, - RemoveItem { item_id: usize }, + RemoveItem { item_id: EntityId }, Split(SplitDirection), ChangeItemTitle, Focus, @@ -167,7 +170,7 @@ impl fmt::Debug for Event { pub struct Pane { items: Vec>, - activation_history: Vec, + activation_history: Vec, zoomed: bool, active_item_index: usize, // last_focused_view_by_item: HashMap, @@ -176,7 +179,7 @@ pub struct Pane { toolbar: View, // tab_bar_context_menu: TabBarContextMenu, // tab_context_menu: ViewHandle, - // workspace: WeakView, + workspace: WeakView, project: Model, has_focus: bool, // can_drop: Rc, &WindowContext) -> bool>, @@ -197,7 +200,7 @@ struct NavHistoryState { backward_stack: VecDeque, forward_stack: VecDeque, closed_stack: VecDeque, - paths_by_item: HashMap)>, + paths_by_item: HashMap)>, pane: WeakView, next_timestamp: Arc, } @@ -346,7 +349,7 @@ impl Pane { // handle: context_menu, // }, // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), - // workspace, + workspace, project, has_focus: false, // can_drop: Rc::new(|_, _| true), @@ -748,12 +751,11 @@ impl Pane { pub fn close_item_by_id( &mut self, - item_id_to_close: usize, + item_id_to_close: EntityId, save_intent: SaveIntent, cx: &mut ViewContext, ) -> Task> { - // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) - todo!() + self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) } // pub fn close_inactive_items( @@ -857,142 +859,142 @@ impl Pane { // ) // } - // pub(super) fn file_names_for_prompt( - // items: &mut dyn Iterator>, - // all_dirty_items: usize, - // cx: &AppContext, - // ) -> String { - // /// Quantity of item paths displayed in prompt prior to cutoff.. - // const FILE_NAMES_CUTOFF_POINT: usize = 10; - // let mut file_names: Vec<_> = items - // .filter_map(|item| { - // item.project_path(cx).and_then(|project_path| { - // project_path - // .path - // .file_name() - // .and_then(|name| name.to_str().map(ToOwned::to_owned)) - // }) - // }) - // .take(FILE_NAMES_CUTOFF_POINT) - // .collect(); - // let should_display_followup_text = - // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; - // if should_display_followup_text { - // let not_shown_files = all_dirty_items - file_names.len(); - // if not_shown_files == 1 { - // file_names.push(".. 1 file not shown".into()); - // } else { - // file_names.push(format!(".. {} files not shown", not_shown_files).into()); - // } - // } - // let file_names = file_names.join("\n"); - // format!( - // "Do you want to save changes to the following {} files?\n{file_names}", - // all_dirty_items - // ) - // } + pub(super) fn file_names_for_prompt( + items: &mut dyn Iterator>, + all_dirty_items: usize, + cx: &AppContext, + ) -> String { + /// Quantity of item paths displayed in prompt prior to cutoff.. + const FILE_NAMES_CUTOFF_POINT: usize = 10; + let mut file_names: Vec<_> = items + .filter_map(|item| { + item.project_path(cx).and_then(|project_path| { + project_path + .path + .file_name() + .and_then(|name| name.to_str().map(ToOwned::to_owned)) + }) + }) + .take(FILE_NAMES_CUTOFF_POINT) + .collect(); + let should_display_followup_text = + all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; + if should_display_followup_text { + let not_shown_files = all_dirty_items - file_names.len(); + if not_shown_files == 1 { + file_names.push(".. 1 file not shown".into()); + } else { + file_names.push(format!(".. {} files not shown", not_shown_files).into()); + } + } + let file_names = file_names.join("\n"); + format!( + "Do you want to save changes to the following {} files?\n{file_names}", + all_dirty_items + ) + } - // pub fn close_items( - // &mut self, - // cx: &mut ViewContext, - // mut save_intent: SaveIntent, - // should_close: impl 'static + Fn(usize) -> bool, - // ) -> Task> { - // // Find the items to close. - // let mut items_to_close = Vec::new(); - // let mut dirty_items = Vec::new(); - // for item in &self.items { - // if should_close(item.id()) { - // items_to_close.push(item.boxed_clone()); - // if item.is_dirty(cx) { - // dirty_items.push(item.boxed_clone()); - // } - // } - // } + pub fn close_items( + &mut self, + cx: &mut ViewContext, + mut save_intent: SaveIntent, + should_close: impl 'static + Fn(EntityId) -> bool, + ) -> Task> { + // Find the items to close. + let mut items_to_close = Vec::new(); + let mut dirty_items = Vec::new(); + for item in &self.items { + if should_close(item.id()) { + items_to_close.push(item.boxed_clone()); + if item.is_dirty(cx) { + dirty_items.push(item.boxed_clone()); + } + } + } - // // If a buffer is open both in a singleton editor and in a multibuffer, make sure - // // to focus the singleton buffer when prompting to save that buffer, as opposed - // // to focusing the multibuffer, because this gives the user a more clear idea - // // of what content they would be saving. - // items_to_close.sort_by_key(|item| !item.is_singleton(cx)); + // 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; - // }; + let workspace = self.workspace.clone(); + cx.spawn(|pane, mut cx| async move { + if save_intent == SaveIntent::Close && dirty_items.len() > 1 { + let 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.await { + Ok(0) => save_intent = SaveIntent::SaveAll, + Ok(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.update(&mut 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)); + // 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.update(&mut 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; - // } + 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); - // } - // })?; - // } + // 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(()) - // }) - // } + pane.update(&mut cx, |_, cx| cx.notify())?; + Ok(()) + }) + } pub fn remove_item( &mut self, @@ -1062,106 +1064,106 @@ impl Pane { cx.notify(); } - // pub async fn save_item( - // project: Model, - // pane: &WeakView, - // item_ix: usize, - // item: &dyn ItemHandle, - // save_intent: SaveIntent, - // cx: &mut AsyncAppContext, - // ) -> Result { - // const CONFLICT_MESSAGE: &str = - // "This file has changed on disk since you started editing it. Do you want to overwrite it?"; + pub async fn save_item( + project: Model, + pane: &WeakView, + item_ix: usize, + item: &dyn ItemHandle, + save_intent: SaveIntent, + cx: &mut AsyncWindowContext, + ) -> Result { + const CONFLICT_MESSAGE: &str = + "This file has changed on disk since you started editing it. Do you want to overwrite it?"; - // if save_intent == SaveIntent::Skip { - // return Ok(true); - // } + 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), - // ) - // }); + let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, 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; - // } + // 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::SaveAs { + is_dirty = true; + has_conflict = false; + can_save = false; + } - // if save_intent == SaveIntent::Overwrite { - // has_conflict = false; - // } + if save_intent == SaveIntent::Overwrite { + has_conflict = false; + } - // if has_conflict && can_save { - // let mut answer = pane.update(cx, |pane, cx| { - // pane.activate_item(item_ix, true, true, cx); - // cx.prompt( - // PromptLevel::Warning, - // CONFLICT_MESSAGE, - // &["Overwrite", "Discard", "Cancel"], - // ) - // })?; - // match answer.next().await { - // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, - // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, - // _ => return Ok(false), - // } - // } else if is_dirty && (can_save || can_save_as) { - // if save_intent == SaveIntent::Close { - // let will_autosave = cx.read(|cx| { - // matches!( - // settings::get::(cx).autosave, - // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - // ) && Self::can_autosave_item(&*item, cx) - // }); - // if !will_autosave { - // let mut answer = pane.update(cx, |pane, cx| { - // pane.activate_item(item_ix, true, true, cx); - // let prompt = dirty_message_for(item.project_path(cx)); - // cx.prompt( - // PromptLevel::Warning, - // &prompt, - // &["Save", "Don't Save", "Cancel"], - // ) - // })?; - // match answer.next().await { - // Some(0) => {} - // Some(1) => return Ok(true), // Don't save his file - // _ => return Ok(false), // Cancel - // } - // } - // } + if has_conflict && can_save { + let 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.await { + Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + Ok(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.update(|_, cx| { + matches!( + WorkspaceSettings::get_global(cx).autosave, + AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange + ) && Self::can_autosave_item(&*item, cx) + })?; + if !will_autosave { + let 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.await { + Ok(0) => {} + Ok(1) => return Ok(true), // Don't save this 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()); + if can_save { + pane.update(cx, |_, cx| item.save(project, cx))?.await?; + } else if can_save_as { + let start_abs_path = project + .update(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) - // } + let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?; + if let Some(abs_path) = abs_path.await.ok().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(); @@ -2093,7 +2095,7 @@ impl NavHistory { state.did_update(cx); } - pub fn remove_item(&mut self, item_id: usize) { + pub fn remove_item(&mut self, item_id: EntityId) { let mut state = self.0.lock(); state.paths_by_item.remove(&item_id); state @@ -2107,7 +2109,7 @@ impl NavHistory { .retain(|entry| entry.item.id() != item_id); } - pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option)> { + pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option)> { self.0.lock().paths_by_item.get(&item_id).cloned() } } @@ -2214,14 +2216,14 @@ impl NavHistoryState { // } // } -// fn dirty_message_for(buffer_path: Option) -> String { -// let path = buffer_path -// .as_ref() -// .and_then(|p| p.path.to_str()) -// .unwrap_or(&"This buffer"); -// let path = truncate_and_remove_front(path, 80); -// format!("{path} contains unsaved edits. Do you want to save it?") -// } +fn dirty_message_for(buffer_path: Option) -> String { + let path = buffer_path + .as_ref() + .and_then(|p| p.path.to_str()) + .unwrap_or(&"This buffer"); + let path = truncate_and_remove_front(path, 80); + format!("{path} contains unsaved edits. Do you want to save it?") +} // todo!("uncomment tests") // #[cfg(test)] diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index ff132a8d80..3935423635 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -200,7 +200,7 @@ impl SearchableItemHandle for View { cx: &mut WindowContext, ) -> Task>> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); - cx.spawn_on_main(|cx| async { + cx.spawn(|cx| async { let matches = matches.await; matches .into_iter() diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index f813dd5c03..e268c7045b 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -29,12 +29,12 @@ use futures::{ }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, - Context, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size, + Div, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, Point, Render, Size, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; -use language2::{LanguageRegistry, LocalFile}; +use language2::LanguageRegistry; use lazy_static::lazy_static; use node_runtime::NodeRuntime; use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; @@ -386,7 +386,7 @@ pub fn register_followable_item(cx: &mut AppContext) { ( |pane, workspace, id, state, cx| { I::from_state_proto(pane, workspace, id, state, cx).map(|task| { - cx.executor() + cx.foreground_executor() .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) }) }, @@ -412,7 +412,8 @@ pub fn register_deserializable_item(cx: &mut AppContext) { Arc::from(serialized_item_kind), |project, workspace, workspace_id, item_id, cx| { let task = I::deserialize(project, workspace, workspace_id, item_id, cx); - cx.spawn_on_main(|_| async { Ok(Box::new(task.await?) as Box<_>) }) + cx.foreground_executor() + .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) }, ); } @@ -426,7 +427,7 @@ pub struct AppState { pub workspace_store: Model, pub fs: Arc, pub build_window_options: - fn(Option, Option, &mut MainThread) -> WindowOptions, + fn(Option, Option, &mut AppContext) -> WindowOptions, pub initialize_workspace: fn( WeakView, bool, @@ -511,7 +512,7 @@ impl DelayedDebouncedEditAction { let previous_task = self.task.take(); self.task = Some(cx.spawn(move |workspace, mut cx| async move { - let mut timer = cx.executor().timer(delay).fuse(); + let mut timer = cx.background_executor().timer(delay).fuse(); if let Some(previous_task) = previous_task { previous_task.await; } @@ -546,7 +547,7 @@ pub struct Workspace { bottom_dock: View, right_dock: View, panes: Vec>, - panes_by_item: HashMap>, + panes_by_item: HashMap>, active_pane: View, last_active_center_pane: Option>, // last_active_view_id: Option, @@ -568,9 +569,6 @@ pub struct Workspace { pane_history_timestamp: Arc, } -trait AssertSend: Send {} -impl AssertSend for WindowHandle {} - // struct ActiveModal { // view: Box, // previously_focused_view_id: Option, @@ -795,7 +793,7 @@ impl Workspace { abs_paths: Vec, app_state: Arc, _requesting_window: Option>, - cx: &mut MainThread, + cx: &mut AppContext, ) -> Task< anyhow::Result<( WindowHandle, @@ -811,7 +809,7 @@ impl Workspace { cx, ); - cx.spawn_on_main(|mut cx| async move { + cx.spawn(|mut cx| async move { let serialized_workspace: Option = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice()); let paths_to_open = Arc::new(abs_paths); @@ -857,21 +855,25 @@ impl Workspace { serialized_workspace .as_ref() .and_then(|serialized_workspace| { - let display = serialized_workspace.display?; + let serialized_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 { let screen = - cx.update(|cx| cx.display_for_uuid(display)).ok()??; + cx.update(|cx| + cx.displays() + .into_iter() + .find(|display| display.uuid().ok() == Some(serialized_display)) + ).ok()??; let screen_bounds = screen.bounds(); window_bounds.origin.x += screen_bounds.origin.x; window_bounds.origin.y += screen_bounds.origin.y; bounds = WindowBounds::Fixed(window_bounds); } - Some((bounds, display)) + Some((bounds, serialized_display)) }) .unzip() }; @@ -885,11 +887,12 @@ impl Workspace { let workspace_id = workspace_id.clone(); let project_handle = project_handle.clone(); move |cx| { - cx.build_view(|cx| { - Workspace::new(workspace_id, project_handle, app_state, cx) - }) - }})? - }; + cx.build_view(|cx| { + Workspace::new(workspace_id, project_handle, app_state, cx) + }) + } + })? + }; // todo!() Ask how to do this let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?; @@ -2123,7 +2126,7 @@ impl Workspace { let (project_entry_id, project_item) = project_item.await?; let build_item = cx.update(|_, cx| { cx.default_global::() - .get(&project_item.type_id()) + .get(&project_item.entity_type()) .ok_or_else(|| anyhow!("no item builder for project item")) .cloned() })??; @@ -3259,7 +3262,7 @@ impl Workspace { .filter_map(|item_handle| { Some(SerializedItem { kind: Arc::from(item_handle.serialized_item_kind()?), - item_id: item_handle.id(), + item_id: item_handle.id().as_u64() as usize, active: Some(item_handle.id()) == active_item_id, }) }) @@ -3565,7 +3568,7 @@ impl Workspace { // } } -fn window_bounds_env_override(cx: &MainThread) -> Option { +fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { let display_origin = cx .update(|cx| Some(cx.displays().first()?.bounds().origin)) .ok()??; @@ -3583,7 +3586,7 @@ fn open_items( _serialized_workspace: Option, project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, - cx: &mut MainThread>, + cx: &mut ViewContext, ) -> impl Future>>>>> { let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); @@ -4115,38 +4118,34 @@ impl ViewId { // pub struct WorkspaceCreated(pub WeakView); -pub async fn activate_workspace_for_project( - cx: &mut AsyncAppContext, +pub fn activate_workspace_for_project( + cx: &mut AppContext, predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static, ) -> Option> { - cx.run_on_main(move |cx| { - for window in cx.windows() { - let Some(workspace) = window.downcast::() else { - continue; - }; + for window in cx.windows() { + let Some(workspace) = window.downcast::() else { + continue; + }; - let predicate = workspace - .update(cx, |workspace, cx| { - let project = workspace.project.read(cx); - if predicate(project, cx) { - cx.activate_window(); - true - } else { - false - } - }) - .log_err() - .unwrap_or(false); + let predicate = workspace + .update(cx, |workspace, cx| { + let project = workspace.project.read(cx); + if predicate(project, cx) { + cx.activate_window(); + true + } else { + false + } + }) + .log_err() + .unwrap_or(false); - if predicate { - return Some(workspace); - } + if predicate { + return Some(workspace); } + } - None - }) - .ok()? - .await + None } pub async fn last_opened_workspace_paths() -> Option { @@ -4349,14 +4348,12 @@ pub fn open_paths( > { let app_state = app_state.clone(); let abs_paths = abs_paths.to_vec(); - cx.spawn_on_main(move |mut cx| async move { - // Open paths in existing workspace if possible - let existing = activate_workspace_for_project(&mut cx, { - let abs_paths = abs_paths.clone(); - move |project, cx| project.contains_paths(&abs_paths, cx) - }) - .await; - + // Open paths in existing workspace if possible + let existing = activate_workspace_for_project(cx, { + let abs_paths = abs_paths.clone(); + move |project, cx| project.contains_paths(&abs_paths, cx) + }); + cx.spawn(move |mut cx| async move { if let Some(existing) = existing { // // Ok(( // existing.clone(), @@ -4377,11 +4374,11 @@ pub fn open_paths( pub fn open_new( app_state: &Arc, - cx: &mut MainThread, + cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, ) -> Task<()> { let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); - cx.spawn_on_main(|mut cx| async move { + cx.spawn(|mut cx| async move { if let Some((workspace, opened_paths)) = task.await.log_err() { workspace .update(&mut cx, |workspace, cx| { diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index a10a6c1f70..2dff4f2eff 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -12,7 +12,7 @@ use client2::UserStore; use db2::kvp::KEY_VALUE_STORE; use fs2::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; -use gpui2::{Action, App, AppContext, AsyncAppContext, Context, MainThread, SemanticVersion, Task}; +use gpui2::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; use isahc::{prelude::Configurable, Request}; use language2::LanguageRegistry; use log::LevelFilter; @@ -249,7 +249,7 @@ fn main() { // .detach_and_log_err(cx) } Ok(None) | Err(_) => cx - .spawn_on_main({ + .spawn({ let app_state = app_state.clone(); |cx| async move { restore_or_create_workspace(&app_state, cx).await } }) @@ -320,10 +320,7 @@ async fn installation_id() -> Result { } } -async fn restore_or_create_workspace( - app_state: &Arc, - mut cx: MainThread, -) { +async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { async_maybe!({ if let Some(location) = workspace2::last_opened_workspace_paths().await { cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))? diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index d49bec8c56..e2d02fe853 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -6,8 +6,8 @@ mod open_listener; pub use assets::*; use collections::HashMap; use gpui2::{ - point, px, AppContext, AsyncAppContext, AsyncWindowContext, MainThread, Point, Task, - TitlebarOptions, WeakView, WindowBounds, WindowHandle, WindowKind, WindowOptions, + point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions, + WeakView, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; @@ -160,7 +160,7 @@ pub async fn handle_cli_connection( } if wait { - let executor = cx.executor().clone(); + let executor = cx.background_executor().clone(); let wait = async move { if paths.is_empty() { let (done_tx, done_rx) = oneshot::channel(); @@ -219,10 +219,14 @@ pub async fn handle_cli_connection( pub fn build_window_options( bounds: Option, display_uuid: Option, - cx: &mut MainThread, + cx: &mut AppContext, ) -> WindowOptions { let bounds = bounds.unwrap_or(WindowBounds::Maximized); - let display = display_uuid.and_then(|uuid| cx.display_for_uuid(uuid)); + let display = display_uuid.and_then(|uuid| { + cx.displays() + .into_iter() + .find(|display| display.uuid().ok() == Some(uuid)) + }); WindowOptions { bounds, From 089bf5893437c0d950011b408701603dabe682e2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 13:34:21 +0100 Subject: [PATCH 46/66] Implement AppState::test --- crates/workspace2/src/workspace2.rs | 75 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e268c7045b..3f550e64c3 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -33,7 +33,7 @@ use gpui2::{ Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; -use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; +use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use language2::LanguageRegistry; use lazy_static::lazy_static; use node_runtime::NodeRuntime; @@ -47,6 +47,7 @@ use persistence::{ use postage::stream::Stream; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; use serde::Deserialize; +use settings2::Settings; use status_bar::StatusBar; use std::{ any::TypeId, @@ -59,6 +60,7 @@ use std::{ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use uuid::Uuid; +use workspace_settings::WorkspaceSettings; lazy_static! { static ref ZED_WINDOW_SIZE: Option> = env::var("ZED_WINDOW_SIZE") @@ -226,10 +228,10 @@ pub struct Toast { pub type WorkspaceId = i64; -// pub fn init_settings(cx: &mut AppContext) { -// settings::register::(cx); -// settings::register::(cx); -// } +pub fn init_settings(cx: &mut AppContext) { + WorkspaceSettings::register(cx); + ItemSettings::register(cx); +} // pub fn init(app_state: Arc, cx: &mut AppContext) { // init_settings(cx); @@ -450,41 +452,42 @@ struct Follower { peer_id: PeerId, } -// todo!() -// impl AppState { -// #[cfg(any(test, feature = "test-support"))] -// pub fn test(cx: &mut AppContext) -> Arc { -// use node_runtime::FakeNodeRuntime; -// use settings::SettingsStore; +impl AppState { + #[cfg(any(test, feature = "test-support"))] + pub fn test(cx: &mut AppContext) -> Arc { + use gpui2::Context; + use node_runtime::FakeNodeRuntime; + use settings2::SettingsStore; -// if !cx.has_global::() { -// cx.set_global(SettingsStore::test(cx)); -// } + if !cx.has_global::() { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + } -// 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 fs = fs2::FakeFs::new(cx.background_executor().clone()); + let languages = Arc::new(LanguageRegistry::test()); + let http_client = util::http::FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone(), cx); + let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx)); -// theme::init((), cx); -// client::init(&client, cx); -// crate::init_settings(cx); + // todo!() + // theme::init((), cx); + client2::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(), -// }) -// } -// } + Arc::new(Self { + client, + fs, + languages, + user_store, + workspace_store, + node_runtime: FakeNodeRuntime::new(), + initialize_workspace: |_, _, _, _| Task::ready(Ok(())), + build_window_options: |_, _, _| Default::default(), + }) + } +} struct DelayedDebouncedEditAction { task: Option>, From c1ca7ad41d685a0f1a51f9b3943277ca007561d2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 13:37:55 +0100 Subject: [PATCH 47/66] Implement WindowContext::remove_window --- crates/gpui2/src/app.rs | 11 +++++++---- crates/gpui2/src/window.rs | 7 +++++++ crates/workspace2/src/workspace2.rs | 3 +-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 5a6e360802..463aac1c59 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -789,10 +789,13 @@ impl Context for AppContext { let root_view = window.root_view.clone().unwrap(); let result = update(root_view, &mut WindowContext::new(cx, &mut window)); - cx.windows - .get_mut(handle.id) - .ok_or_else(|| anyhow!("window not found"))? - .replace(window); + + if !window.removed { + cx.windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .replace(window); + } Ok(result) }) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0202b7521e..055c31af16 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -159,6 +159,7 @@ impl Drop for FocusHandle { // Holds the state for a specific window. pub struct Window { pub(crate) handle: AnyWindowHandle, + pub(crate) removed: bool, platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, @@ -229,6 +230,7 @@ impl Window { Window { handle, + removed: false, platform_window, display_id, sprite_atlas, @@ -320,6 +322,11 @@ impl<'a> WindowContext<'a> { self.window.dirty = true; } + /// Close this window. + pub fn remove_window(&mut self) { + self.window.removed = true; + } + /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus /// for elements rendered within this window. pub fn focus_handle(&mut self) -> FocusHandle { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3f550e64c3..3f59312b11 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -621,8 +621,7 @@ impl Workspace { } project2::Event::Closed => { - // todo!() - // cx.remove_window(); + cx.remove_window(); } project2::Event::DeletedEntry(entry_id) => { From d4e199cab1da0a63858f3e0b99f556ba6250f2d8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 13:58:42 +0100 Subject: [PATCH 48/66] WIP --- crates/workspace2/src/item.rs | 4 +- crates/workspace2/src/persistence/model.rs | 223 ++++++++-------- crates/workspace2/src/workspace2.rs | 296 ++++++++++----------- 3 files changed, 262 insertions(+), 261 deletions(-) diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index ebc51942d0..c2d5c25781 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -13,7 +13,7 @@ use client2::{ }; use gpui2::{ AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels, - Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle, + Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -190,7 +190,7 @@ pub trait Item: Render + EventEmitter + Send { fn deserialize( _project: Model, - _workspace: WindowHandle, + _workspace: WeakView, _workspace_id: WorkspaceId, _item_id: ItemId, _cx: &mut ViewContext, diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 13b0560a19..25d5a970fa 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -1,14 +1,19 @@ -use crate::{Axis, WorkspaceId}; +use crate::{ + item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, +}; use anyhow::{Context, Result}; +use async_recursion::async_recursion; use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::WindowBounds; +use gpui2::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; +use project2::Project; use std::{ path::{Path, PathBuf}, sync::Arc, }; +use util::ResultExt; use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Eq)] @@ -142,75 +147,73 @@ impl Default for SerializedPaneGroup { } } -// impl SerializedPaneGroup { -// #[async_recursion(?Send)] -// pub(crate) async fn deserialize( -// self, -// project: &Model, -// workspace_id: WorkspaceId, -// workspace: WeakView, -// cx: &mut AsyncAppContext, -// ) -> Option<(Member, Option>, Vec>>)> { -// match self { -// SerializedPaneGroup::Group { -// axis, -// children, -// flexes, -// } => { -// let mut current_active_pane = None; -// let mut members = Vec::new(); -// let mut items = Vec::new(); -// for child in children { -// if let Some((new_member, active_pane, new_items)) = child -// .deserialize(project, workspace_id, workspace, cx) -// .await -// { -// members.push(new_member); -// items.extend(new_items); -// current_active_pane = current_active_pane.or(active_pane); -// } -// } +impl SerializedPaneGroup { + #[async_recursion(?Send)] + pub(crate) async fn deserialize( + self, + project: &Model, + workspace_id: WorkspaceId, + workspace: WeakView, + cx: &mut AsyncWindowContext, + ) -> Option<(Member, Option>, Vec>>)> { + match self { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { + let mut current_active_pane = None; + let mut members = Vec::new(); + let mut items = Vec::new(); + for child in children { + if let Some((new_member, active_pane, new_items)) = child + .deserialize(project, workspace_id, workspace, cx) + .await + { + members.push(new_member); + items.extend(new_items); + current_active_pane = current_active_pane.or(active_pane); + } + } -// if members.is_empty() { -// return None; -// } + if members.is_empty() { + return None; + } -// if members.len() == 1 { -// return Some((members.remove(0), current_active_pane, items)); -// } + if members.len() == 1 { + return Some((members.remove(0), current_active_pane, items)); + } -// Some(( -// Member::Axis(PaneAxis::load(axis, members, flexes)), -// current_active_pane, -// items, -// )) -// } -// SerializedPaneGroup::Pane(serialized_pane) => { -// let pane = workspace -// .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade()) -// .log_err()?; -// let active = serialized_pane.active; -// let new_items = serialized_pane -// .deserialize_to(project, &pane, workspace_id, workspace, cx) -// .await -// .log_err()?; + Some(( + Member::Axis(PaneAxis::load(axis, members, flexes)), + current_active_pane, + items, + )) + } + SerializedPaneGroup::Pane(serialized_pane) => { + let pane = workspace + .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade()) + .log_err()?; + let active = serialized_pane.active; + let new_items = serialized_pane + .deserialize_to(project, &pane, workspace_id, workspace, cx) + .await + .log_err()?; -// // todo!(); -// // if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? { -// // let pane = pane.upgrade()?; -// // Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) -// // } else { -// // let pane = pane.upgrade()?; -// // workspace -// // .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx)) -// // .log_err()?; -// // None -// // } -// None -// } -// } -// } -// } + if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? { + let pane = pane.upgrade()?; + Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) + } else { + let pane = pane.upgrade()?; + workspace + .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx)) + .log_err()?; + None + } + } + } + } +} #[derive(Debug, PartialEq, Eq, Default, Clone)] pub struct SerializedPane { @@ -223,55 +226,53 @@ impl SerializedPane { SerializedPane { children, active } } - // pub async fn deserialize_to( - // &self, - // _project: &Model, - // _pane: &WeakView, - // _workspace_id: WorkspaceId, - // _workspace: WindowHandle, - // _cx: &mut AsyncAppContext, - // ) -> Result>>> { - // anyhow::bail!("todo!()") - // // todo!() - // // let mut items = Vec::new(); - // // let mut active_item_index = None; - // // for (index, item) in self.children.iter().enumerate() { - // // let project = project.clone(); - // // let item_handle = pane - // // .update(cx, |_, cx| { - // // if let Some(deserializer) = cx.global::().get(&item.kind) { - // // deserializer(project, workspace, workspace_id, item.item_id, cx) - // // } else { - // // Task::ready(Err(anyhow::anyhow!( - // // "Deserializer does not exist for item kind: {}", - // // item.kind - // // ))) - // // } - // // })? - // // .await - // // .log_err(); + pub async fn deserialize_to( + &self, + project: &Model, + pane: &WeakView, + workspace_id: WorkspaceId, + workspace: WeakView, + cx: &mut AsyncWindowContext, + ) -> Result>>> { + let mut items = Vec::new(); + let mut active_item_index = None; + for (index, item) in self.children.iter().enumerate() { + let project = project.clone(); + let item_handle = pane + .update(cx, |_, cx| { + if let Some(deserializer) = cx.global::().get(&item.kind) { + deserializer(project, workspace.clone(), workspace_id, item.item_id, cx) + } else { + Task::ready(Err(anyhow::anyhow!( + "Deserializer does not exist for item kind: {}", + item.kind + ))) + } + })? + .await + .log_err(); - // // items.push(item_handle.clone()); + items.push(item_handle.clone()); - // // if let Some(item_handle) = item_handle { - // // pane.update(cx, |pane, cx| { - // // pane.add_item(item_handle.clone(), true, true, None, cx); - // // })?; - // // } + if let Some(item_handle) = item_handle { + pane.update(cx, |pane, cx| { + pane.add_item(item_handle.clone(), true, true, None, cx); + })?; + } - // // if item.active { - // // active_item_index = Some(index); - // // } - // // } + if item.active { + active_item_index = Some(index); + } + } - // // if let Some(active_item_index) = active_item_index { - // // pane.update(cx, |pane, cx| { - // // pane.activate_item(active_item_index, false, false, cx); - // // })?; - // // } + if let Some(active_item_index) = active_item_index { + pane.update(cx, |pane, cx| { + pane.activate_item(active_item_index, false, false, cx); + })?; + } - // // anyhow::Ok(items) - // } + anyhow::Ok(items) + } } pub type GroupId = i64; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3f59312b11..0782e811e5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -401,7 +401,7 @@ type ItemDeserializers = HashMap< Arc, fn( Model, - WindowHandle, + WeakView, WorkspaceId, ItemId, &mut ViewContext, @@ -936,17 +936,17 @@ impl Workspace { self.weak_self.clone() } - // pub fn left_dock(&self) -> &View { - // &self.left_dock - // } + pub fn left_dock(&self) -> &View { + &self.left_dock + } - // pub fn bottom_dock(&self) -> &View { - // &self.bottom_dock - // } + pub fn bottom_dock(&self) -> &View { + &self.bottom_dock + } - // pub fn right_dock(&self) -> &View { - // &self.right_dock - // } + pub fn right_dock(&self) -> &View { + &self.right_dock + } // pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) // where @@ -3394,126 +3394,127 @@ impl Workspace { cx: &mut ViewContext, ) -> Task>>>> { cx.spawn(|workspace, mut cx| async move { - // let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { - // ( - // workspace.project().clone(), - // workspace.last_active_center_pane.clone(), - // ) - // })?; + let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { + ( + workspace.project().clone(), + workspace.last_active_center_pane.clone(), + ) + })?; - // // let mut center_group: Option = None; - // // let mut center_items: Option>>> = None; + let mut center_group = None; + let mut center_items = None; - // // todo!() - // // // 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)) - // } + // 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.update(|_, cx| { - // center_items - // .unwrap_or_default() - // .into_iter() - // .filter_map(|item| { - // let item = item?; - // let project_path = item.project_path(cx)?; - // Some((project_path, item)) - // }) - // .collect::>() - // })?; + let mut items_by_project_path = cx.update(|_, cx| { + center_items + .unwrap_or_default() + .into_iter() + .filter_map(|item| { + let item = item?; + let project_path = item.project_path(cx)?; + Some((project_path, item)) + }) + .collect::>() + })?; - // let opened_items = paths_to_open - // .into_iter() - // .map(|path_to_open| { - // path_to_open - // .and_then(|path_to_open| items_by_project_path.remove(&path_to_open)) - // }) - // .collect::>(); + 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::>(); - // todo!() - // // 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); + // 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); + // 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(); + // Change the focus to the workspace first so that we retrigger focus in on the pane. + todo!() + // cx.focus_self(); + // if let Some(active_pane) = active_pane { + // cx.focus(&active_pane); + // } else { + // cx.focus(workspace.panes.last().unwrap()); + // } + } else { + todo!() + // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade()); + // if let Some(old_center_handle) = old_center_handle { + // cx.focus(&old_center_handle) + // } else { + // 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()); - // 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 { + todo!() + // 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)); - // 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 { + todo!() + // 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); + } + } - // 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)); - // dock.active_panel() - // .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); + if docks.bottom.visible && docks.bottom.zoom { + todo!() + // cx.focus_self() + } + }); - // if docks.bottom.visible && docks.bottom.zoom { - // cx.focus_self() - // } - // }); - - // cx.notify(); - // })?; + cx.notify(); + })?; // Serialize ourself to make sure our timestamps and any pane / item changes are replicated - // workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?; + workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?; - // Ok(opened_items) - anyhow::bail!("todo") + Ok(opened_items) }) } @@ -3585,49 +3586,48 @@ fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { } fn open_items( - _serialized_workspace: Option, + serialized_workspace: Option, project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, cx: &mut ViewContext, ) -> impl Future>>>>> { let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - // todo!() - // if let Some(serialized_workspace) = serialized_workspace { - // let restored_items = Workspace::load_workspace( - // serialized_workspace, - // project_paths_to_open - // .iter() - // .map(|(_, project_path)| project_path) - // .cloned() - // .collect(), - // cx, - // ) - // .await?; + if let Some(serialized_workspace) = serialized_workspace { + let restored_items = Workspace::load_workspace( + serialized_workspace, + project_paths_to_open + .iter() + .map(|(_, project_path)| project_path) + .cloned() + .collect(), + cx, + ) + .await?; - // let restored_project_paths = restored_items - // .iter() - // .filter_map(|item| item.as_ref()?.project_path(cx)) - // .collect::>(); + let restored_project_paths = restored_items + .iter() + .filter_map(|item| item.as_ref()?.project_path(cx)) + .collect::>(); - // for restored_item in restored_items { - // opened_items.push(restored_item.map(Ok)); - // } + 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); + 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 = From 52e195b47c734c90deb238700e73610972d933a0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Nov 2023 07:46:49 -0600 Subject: [PATCH 49/66] WIP --- crates/workspace2/src/dock.rs | 50 +++--- crates/workspace2/src/persistence/model.rs | 4 +- crates/workspace2/src/workspace2.rs | 168 ++++++++++----------- crates/zed2/src/zed2.rs | 4 +- 4 files changed, 118 insertions(+), 108 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 35aac2fb3c..b95c534257 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; pub trait Panel: Render + EventEmitter { + fn persistent_name(&self) -> &'static str; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); @@ -42,6 +43,7 @@ pub trait Panel: Render + EventEmitter { pub trait PanelHandle: Send + Sync { fn id(&self) -> EntityId; + fn persistent_name(&self, cx: &WindowContext) -> &'static str; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn set_position(&self, position: DockPosition, cx: &mut WindowContext); @@ -65,6 +67,10 @@ where self.entity_id() } + fn persistent_name(&self, cx: &WindowContext) -> &'static str { + self.read(cx).persistent_name() + } + fn position(&self, cx: &WindowContext) -> DockPosition { self.read(cx).position(cx) } @@ -77,14 +83,6 @@ where self.update(cx, |this, cx| this.set_position(position, cx)) } - fn size(&self, cx: &WindowContext) -> f32 { - self.read(cx).size(cx) - } - - fn set_size(&self, size: Option, cx: &mut WindowContext) { - self.update(cx, |this, cx| this.set_size(size, cx)) - } - fn is_zoomed(&self, cx: &WindowContext) -> bool { self.read(cx).is_zoomed(cx) } @@ -97,6 +95,14 @@ where self.update(cx, |this, cx| this.set_active(active, cx)) } + fn size(&self, cx: &WindowContext) -> f32 { + self.read(cx).size(cx) + } + + fn set_size(&self, size: Option, cx: &mut WindowContext) { + self.update(cx, |this, cx| this.set_size(size, cx)) + } + fn icon_path(&self, cx: &WindowContext) -> Option<&'static str> { self.read(cx).icon_path(cx) } @@ -662,6 +668,10 @@ pub mod test { } impl Panel for TestPanel { + fn persistent_name(&self) -> &'static str { + "TestPanel" + } + fn position(&self, _: &gpui2::WindowContext) -> super::DockPosition { self.position } @@ -675,18 +685,6 @@ pub mod test { cx.emit(TestPanelEvent::PositionChanged); } - fn is_zoomed(&self, _: &WindowContext) -> bool { - self.zoomed - } - - fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext) { - self.zoomed = zoomed; - } - - fn set_active(&mut self, active: bool, _cx: &mut ViewContext) { - self.active = active; - } - fn size(&self, _: &WindowContext) -> f32 { self.size } @@ -715,6 +713,18 @@ pub mod test { matches!(event, TestPanelEvent::ZoomOut) } + fn is_zoomed(&self, _: &WindowContext) -> bool { + self.zoomed + } + + fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext) { + self.zoomed = zoomed; + } + + fn set_active(&mut self, active: bool, _cx: &mut ViewContext) { + self.active = active; + } + fn should_activate_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Activated) } diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 25d5a970fa..de4518f68e 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -167,7 +167,7 @@ impl SerializedPaneGroup { let mut items = Vec::new(); for child in children { if let Some((new_member, active_pane, new_items)) = child - .deserialize(project, workspace_id, workspace, cx) + .deserialize(project, workspace_id, workspace.clone(), cx) .await { members.push(new_member); @@ -196,7 +196,7 @@ impl SerializedPaneGroup { .log_err()?; let active = serialized_pane.active; let new_items = serialized_pane - .deserialize_to(project, &pane, workspace_id, workspace, cx) + .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx) .await .log_err()?; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 0782e811e5..30cba5531a 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -916,10 +916,8 @@ impl Workspace { notify_if_database_failed(window, &mut cx); let opened_items = window .update(&mut cx, |_workspace, cx| { - let workspace = cx.view().downgrade(); open_items( serialized_workspace, - // &workspace, project_paths, app_state, cx, @@ -3306,13 +3304,9 @@ impl 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| { - todo!() - // Some( - // cx.view_ui_name(panel.as_any().window(), panel.id())? - // .to_string(), - // ) - }); + let left_active_panel = left_dock + .visible_panel() + .and_then(|panel| Some(panel.persistent_name(cx).to_string())); let left_dock_zoom = left_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) @@ -3320,13 +3314,9 @@ impl Workspace { 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| { - todo!() - // Some( - // cx.view_ui_name(panel.as_any().window(), panel.id())? - // .to_string(), - // ) - }); + let right_active_panel = right_dock + .visible_panel() + .and_then(|panel| Some(panel.persistent_name(cx).to_string())); let right_dock_zoom = right_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) @@ -3334,13 +3324,9 @@ impl Workspace { 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| { - todo!() - // Some( - // cx.view_ui_name(panel.as_any().window(), panel.id())? - // .to_string(), - // ) - }); + let bottom_active_panel = bottom_dock + .visible_panel() + .and_then(|panel| Some(panel.persistent_name(cx).to_string())); let bottom_dock_zoom = bottom_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) @@ -3407,7 +3393,12 @@ impl Workspace { // 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) + .deserialize( + &project, + serialized_workspace.id, + workspace.clone(), + &mut cx, + ) .await { center_items = Some(items); @@ -3443,7 +3434,7 @@ impl Workspace { workspace.center = PaneGroup::with_root(center_group); // Change the focus to the workspace first so that we retrigger focus in on the pane. - todo!() + // todo!() // cx.focus_self(); // if let Some(active_pane) = active_pane { // cx.focus(&active_pane); @@ -3451,7 +3442,7 @@ impl Workspace { // cx.focus(workspace.panes.last().unwrap()); // } } else { - todo!() + // todo!() // let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade()); // if let Some(old_center_handle) = old_center_handle { // cx.focus(&old_center_handle) @@ -3471,7 +3462,7 @@ impl Workspace { dock.active_panel() .map(|panel| panel.set_zoomed(docks.left.zoom, cx)); if docks.left.visible && docks.left.zoom { - todo!() + // todo!() // cx.focus_self() } }); @@ -3487,7 +3478,7 @@ impl Workspace { .map(|panel| panel.set_zoomed(docks.right.zoom, cx)); if docks.right.visible && docks.right.zoom { - todo!() + // todo!() // cx.focus_self() } }); @@ -3503,7 +3494,7 @@ impl Workspace { .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx)); if docks.bottom.visible && docks.bottom.zoom { - todo!() + // todo!() // cx.focus_self() } }); @@ -3587,14 +3578,12 @@ fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { fn open_items( serialized_workspace: Option, - project_paths_to_open: Vec<(PathBuf, Option)>, + mut project_paths_to_open: Vec<(PathBuf, Option)>, app_state: Arc, cx: &mut ViewContext, -) -> impl Future>>>>> { - let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - - if let Some(serialized_workspace) = serialized_workspace { - let restored_items = Workspace::load_workspace( +) -> impl 'static + Future>>>>> { + let restored_items = serialized_workspace.map(|serialized_workspace| { + Workspace::load_workspace( serialized_workspace, project_paths_to_open .iter() @@ -3603,61 +3592,72 @@ fn open_items( .collect(), cx, ) - .await?; + }); - let restored_project_paths = restored_items - .iter() - .filter_map(|item| item.as_ref()?.project_path(cx)) - .collect::>(); + cx.spawn(|workspace, mut cx| async move { + let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); - for restored_item in restored_items { - opened_items.push(restored_item.map(Ok)); - } + if let Some(restored_items) = restored_items { + let restored_items = restored_items.await?; - 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 restored_project_paths = restored_items + .iter() + .filter_map(|item| { + cx.update(|_, cx| item.as_ref()?.project_path(cx)) + .ok() + .flatten() + }) + .collect::>(); - let tasks = - project_paths_to_open - .into_iter() - .enumerate() - .map(|(i, (abs_path, project_path))| { - cx.spawn(|workspace, 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 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 + } + } + }) + }); + + let tasks = tasks.collect::>(); - let tasks = tasks.collect::>(); - async move { let tasks = futures::future::join_all(tasks.into_iter()); for maybe_opened_path in tasks.await.into_iter() { if let Some((i, path_open_result)) = maybe_opened_path { @@ -3666,7 +3666,7 @@ fn open_items( } Ok(opened_items) - } + }) } // fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index e2d02fe853..b86a7d7d2b 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -141,7 +141,8 @@ pub async fn handle_cli_connection( }), ) .detach(); - }); + }) + .ok(); item_release_futures.push(released.1); } Some(Err(err)) => { @@ -170,7 +171,6 @@ pub async fn handle_cli_connection( let _ = done_tx.send(()); }) }); - drop(workspace); let _ = done_rx.await; } else { let _ = futures::future::try_join_all(item_release_futures) From 634aba89d29c0cf04d54b17c92e622988b89edbe Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 10:03:03 -0600 Subject: [PATCH 50/66] Add back some window events for workspace Co-Authored-By: Antonio --- crates/gpui2/src/window.rs | 22 +- crates/workspace2/src/pane.rs | 52 ++-- crates/workspace2/src/workspace2.rs | 373 ++++++++++++++-------------- crates/zed2/src/main.rs | 3 +- 4 files changed, 231 insertions(+), 219 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a75c2ef319..8e1022c890 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -4,11 +4,11 @@ use crate::{ Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, - PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, - SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point, + PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, + RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, + Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, + WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -25,6 +25,7 @@ use std::{ hash::{Hash, Hasher}, marker::PhantomData, mem, + rc::Rc, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, @@ -570,6 +571,17 @@ impl<'a> WindowContext<'a> { self.window.bounds } + pub fn is_window_active(&self) -> bool { + self.window.active + } + + pub fn display(&self) -> Option> { + self.platform + .displays() + .into_iter() + .find(|display| display.id() == self.window.display_id) + } + /// The scale factor of the display associated with the window. For example, it could /// return 2.0 for a "retina" display, indicating that each logical pixel should actually /// be rendered as two pixels on screen. diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index af7056e00f..43e4aa1b01 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -104,32 +104,32 @@ pub enum SaveIntent { const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; -// todo!() -// 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)); -// } +pub fn init(cx: &mut AppContext) { + // todo!() + // 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)); +} pub enum Event { AddItem { item: Box }, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 30cba5531a..32dd09572c 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -60,7 +60,7 @@ use std::{ pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use uuid::Uuid; -use workspace_settings::WorkspaceSettings; +use workspace_settings::{AutosaveSetting, WorkspaceSettings}; lazy_static! { static ref ZED_WINDOW_SIZE: Option> = env::var("ZED_WINDOW_SIZE") @@ -233,129 +233,129 @@ pub fn init_settings(cx: &mut AppContext) { ItemSettings::register(cx); } -// pub fn init(app_state: Arc, cx: &mut AppContext) { -// init_settings(cx); -// pane::init(cx); -// notifications::init(cx); +pub fn init(app_state: Arc, cx: &mut AppContext) { + init_settings(cx); + pane::init(cx); + notifications::init(cx); -// cx.add_global_action({ -// let app_state = Arc::downgrade(&app_state); -// move |_: &Open, cx: &mut AppContext| { -// let mut paths = cx.prompt_for_paths(PathPromptOptions { -// files: true, -// directories: true, -// multiple: true, -// }); + // cx.add_global_action({ + // let app_state = Arc::downgrade(&app_state); + // move |_: &Open, cx: &mut AppContext| { + // let mut paths = cx.prompt_for_paths(PathPromptOptions { + // files: true, + // directories: true, + // multiple: true, + // }); -// if let Some(app_state) = app_state.upgrade() { -// cx.spawn(move |mut cx| async move { -// if let Some(paths) = paths.recv().await.flatten() { -// cx.update(|cx| { -// open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx) -// }); -// } -// }) -// .detach(); -// } -// } -// }); -// cx.add_async_action(Workspace::open); + // if let Some(app_state) = app_state.upgrade() { + // cx.spawn(move |mut cx| async move { + // if let Some(paths) = paths.recv().await.flatten() { + // cx.update(|cx| { + // open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx) + // }); + // } + // }) + // .detach(); + // } + // } + // }); + // cx.add_async_action(Workspace::open); -// cx.add_async_action(Workspace::follow_next_collaborator); -// cx.add_async_action(Workspace::close); -// cx.add_async_action(Workspace::close_inactive_items_and_panes); -// cx.add_async_action(Workspace::close_all_items_and_panes); -// cx.add_global_action(Workspace::close_global); -// cx.add_global_action(restart); -// cx.add_async_action(Workspace::save_all); -// cx.add_action(Workspace::add_folder_to_project); -// cx.add_action( -// |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext| { -// let pane = workspace.active_pane().clone(); -// workspace.unfollow(&pane, cx); -// }, -// ); -// cx.add_action( -// |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext| { -// workspace -// .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx) -// .detach_and_log_err(cx); -// }, -// ); -// cx.add_action( -// |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext| { -// workspace -// .save_active_item(SaveIntent::SaveAs, cx) -// .detach_and_log_err(cx); -// }, -// ); -// cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { -// workspace.activate_previous_pane(cx) -// }); -// cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { -// workspace.activate_next_pane(cx) -// }); + // cx.add_async_action(Workspace::follow_next_collaborator); + // cx.add_async_action(Workspace::close); + // cx.add_async_action(Workspace::close_inactive_items_and_panes); + // cx.add_async_action(Workspace::close_all_items_and_panes); + // cx.add_global_action(Workspace::close_global); + // cx.add_global_action(restart); + // cx.add_async_action(Workspace::save_all); + // cx.add_action(Workspace::add_folder_to_project); + // cx.add_action( + // |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext| { + // let pane = workspace.active_pane().clone(); + // workspace.unfollow(&pane, cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext| { + // workspace + // .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx) + // .detach_and_log_err(cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext| { + // workspace + // .save_active_item(SaveIntent::SaveAs, cx) + // .detach_and_log_err(cx); + // }, + // ); + // cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { + // workspace.activate_previous_pane(cx) + // }); + // cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { + // workspace.activate_next_pane(cx) + // }); -// cx.add_action( -// |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| { -// workspace.activate_pane_in_direction(action.0, cx) -// }, -// ); + // cx.add_action( + // |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| { + // workspace.activate_pane_in_direction(action.0, cx) + // }, + // ); -// cx.add_action( -// |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| { -// workspace.swap_pane_in_direction(action.0, cx) -// }, -// ); + // cx.add_action( + // |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| { + // workspace.swap_pane_in_direction(action.0, cx) + // }, + // ); -// cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { -// workspace.toggle_dock(DockPosition::Left, cx); -// }); -// cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { -// workspace.toggle_dock(DockPosition::Right, cx); -// }); -// cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { -// workspace.toggle_dock(DockPosition::Bottom, cx); -// }); -// cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| { -// workspace.close_all_docks(cx); -// }); -// cx.add_action(Workspace::activate_pane_at_index); -// cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { -// workspace.reopen_closed_item(cx).detach(); -// }); -// cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { -// workspace -// .go_back(workspace.active_pane().downgrade(), cx) -// .detach(); -// }); -// cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { -// workspace -// .go_forward(workspace.active_pane().downgrade(), cx) -// .detach(); -// }); + // cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { + // workspace.toggle_dock(DockPosition::Left, cx); + // }); + // cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { + // workspace.toggle_dock(DockPosition::Right, cx); + // }); + // cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { + // workspace.toggle_dock(DockPosition::Bottom, cx); + // }); + // cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| { + // workspace.close_all_docks(cx); + // }); + // cx.add_action(Workspace::activate_pane_at_index); + // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { + // workspace.reopen_closed_item(cx).detach(); + // }); + // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { + // workspace + // .go_back(workspace.active_pane().downgrade(), cx) + // .detach(); + // }); + // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { + // workspace + // .go_forward(workspace.active_pane().downgrade(), cx) + // .detach(); + // }); -// cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { -// cx.spawn(|workspace, mut cx| async move { -// let err = install_cli::install_cli(&cx) -// .await -// .context("Failed to create CLI symlink"); + // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { + // cx.spawn(|workspace, mut cx| async move { + // let err = install_cli::install_cli(&cx) + // .await + // .context("Failed to create CLI symlink"); -// workspace.update(&mut cx, |workspace, cx| { -// if matches!(err, Err(_)) { -// err.notify_err(workspace, cx); -// } else { -// workspace.show_notification(1, cx, |cx| { -// cx.build_view(|_| { -// MessageNotification::new("Successfully installed the `zed` binary") -// }) -// }); -// } -// }) -// }) -// .detach(); -// }); -// } + // workspace.update(&mut cx, |workspace, cx| { + // if matches!(err, Err(_)) { + // err.notify_err(workspace, cx); + // } else { + // workspace.show_notification(1, cx, |cx| { + // cx.build_view(|_| { + // MessageNotification::new("Successfully installed the `zed` binary") + // }) + // }); + // } + // }) + // }) + // .detach(); + // }); +} type ProjectItemBuilders = HashMap, AnyModel, &mut ViewContext) -> Box>; @@ -553,7 +553,7 @@ pub struct Workspace { panes_by_item: HashMap>, active_pane: View, last_active_center_pane: Option>, - // last_active_view_id: Option, + last_active_view_id: Option, // status_bar: View, // titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, @@ -725,24 +725,25 @@ impl Workspace { } let subscriptions = vec![ - // todo!() - // 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.origin.x -= screen_bounds.origin.x; - // window_bounds.origin.y -= screen_bounds.origin.y; - // bounds = WindowBounds::Fixed(window_bounds); - // } - // } + cx.observe_window_activation(Self::on_window_activation_changed), + cx.observe_window_bounds(move |_, cx| { + if let Some(display) = cx.display() { + // Transform fixed bounds to be stored in terms of the containing display + let mut bounds = cx.window_bounds(); + if let WindowBounds::Fixed(window_bounds) = &mut bounds { + let display_bounds = display.bounds(); + window_bounds.origin.x -= display_bounds.origin.x; + window_bounds.origin.y -= display_bounds.origin.y; + } - // cx.background() - // .spawn(DB.set_window_bounds(workspace_id, bounds, display)) - // .detach_and_log_err(cx); - // }), + if let Some(display_uuid) = display.uuid().log_err() { + cx.background_executor() + .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid)) + .detach_and_log_err(cx); + } + } + cx.notify(); + }), cx.observe(&left_dock, |this, _, cx| { this.serialize_workspace(cx); cx.notify(); @@ -768,7 +769,7 @@ impl Workspace { panes_by_item: Default::default(), active_pane: center_pane.clone(), last_active_center_pane: Some(center_pane.downgrade()), - // last_active_view_id: None, + last_active_view_id: None, // status_bar, // titlebar_item: None, notifications: Default::default(), @@ -3018,33 +3019,33 @@ impl Workspace { 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), - // }; - // } - // } + fn update_active_view_for_followers(&mut self, cx: &mut ViewContext) { + 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, - // ); - // } - // } + 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, @@ -3154,31 +3155,31 @@ impl Workspace { // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) // } - // pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { - // if active { - // self.update_active_view_for_followers(cx); - // cx.background() - // .spawn(persistence::DB.update_timestamp(self.database_id())) - // .detach(); - // } else { - // for pane in &self.panes { - // pane.update(cx, |pane, cx| { - // if let Some(item) = pane.active_item() { - // item.workspace_deactivated(cx); - // } - // if matches!( - // settings::get::(cx).autosave, - // AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange - // ) { - // for item in pane.items() { - // Pane::autosave_item(item.as_ref(), self.project.clone(), cx) - // .detach_and_log_err(cx); - // } - // } - // }); - // } - // } - // } + pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext) { + if cx.is_window_active() { + self.update_active_view_for_followers(cx); + cx.background_executor() + .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!( + WorkspaceSettings::get_global(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<&Model> { self.active_call.as_ref().map(|(call, _)| call) diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 2dff4f2eff..e91fb4bed4 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -188,8 +188,7 @@ fn main() { // audio::init(Assets, cx); // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); - // todo!("workspace") - // workspace::init(app_state.clone(), cx); + workspace2::init(app_state.clone(), cx); // recent_projects::init(cx); // journal2::init(app_state.clone(), cx); From bd54bfa4e1b5b972513b1b3dc0752a22c24fc4d4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 10:31:18 -0600 Subject: [PATCH 51/66] Render titlebar Co-Authored-By: Mikayla --- crates/workspace2/src/workspace2.rs | 207 ++++++++++++++++++++++------ 1 file changed, 165 insertions(+), 42 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 32dd09572c..3db4900463 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -29,9 +29,9 @@ use futures::{ }; use gpui2::{ div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, - Div, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, Point, Render, Size, - Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, - WindowHandle, WindowOptions, + Component, Div, Element, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, + ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription, Task, View, + ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use language2::LanguageRegistry; @@ -57,6 +57,7 @@ use std::{ sync::{atomic::AtomicUsize, Arc}, time::Duration, }; +use theme2::ActiveTheme; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; use uuid::Uuid; @@ -2687,45 +2688,27 @@ impl Workspace { // .any(|state| state.leader_id == peer_id) // } - // fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { - // // TODO: There should be a better system in place for this - // // (https://github.com/zed-industries/zed/issues/1290) - // let is_fullscreen = cx.window_is_fullscreen(); - // let container_theme = if is_fullscreen { - // let mut container_theme = theme.titlebar.container; - // container_theme.padding.left = container_theme.padding.right; - // container_theme - // } else { - // theme.titlebar.container - // }; + fn render_titlebar(&self, cx: &mut ViewContext) -> impl Component { + div() + .when( + matches!(cx.window_bounds(), WindowBounds::Fullscreen), + |s| s.pl_20(), + ) + .id(0) + .on_click(|workspace, event, cx| { + if event.up.click_count == 2 { + println!("ZOOOOOM") + } + }) + .child("Collab title bar Item") // self.titlebar_item + } - // enum TitleBar {} - // MouseEventHandler::new::(0, cx, |_, cx| { - // Stack::new() - // .with_children( - // self.titlebar_item - // .as_ref() - // .map(|item| ChildView::new(item, cx)), - // ) - // .contained() - // .with_style(container_theme) - // }) - // .on_click(MouseButton::Left, |event, _, cx| { - // if event.click_count == 2 { - // cx.zoom_window(); - // } - // }) - // .constrained() - // .with_height(theme.titlebar.height) - // .into_any_named("titlebar") - // } - - // fn active_item_path_changed(&mut self, cx: &mut ViewContext) { - // let active_entry = self.active_project_path(cx); - // self.project - // .update(cx, |project, cx| project.set_active_path(active_entry, cx)); - // self.update_window_title(cx); - // } + // fn active_item_path_changed(&mut self, cx: &mut ViewContext) { + // let active_entry = self.active_project_path(cx); + // self.project + // .update(cx, |project, cx| project.set_active_path(active_entry, cx)); + // self.update_window_title(cx); + // } fn update_window_title(&mut self, cx: &mut ViewContext) { let project = self.project().read(cx); @@ -3776,8 +3759,148 @@ impl EventEmitter for Workspace { impl Render for Workspace { type Element = Div; - fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { div() + .relative() + .size_full() + .flex() + .flex_col() + .font("Zed Sans") + .gap_0() + .justify_start() + .items_start() + .text_color(cx.theme().colors().text) + .bg(cx.theme().colors().background) + .child(self.render_titlebar(cx)) + .child( + div() + .flex_1() + .w_full() + .flex() + .flex_row() + .overflow_hidden() + .border_t() + .border_b() + .border_color(cx.theme().colors().border) + // .children( + // Some( + // Panel::new("project-panel-outer", cx) + // .side(PanelSide::Left) + // .child(ProjectPanel::new("project-panel-inner")), + // ) + // .filter(|_| self.is_project_panel_open()), + // ) + // .children( + // Some( + // Panel::new("collab-panel-outer", cx) + // .child(CollabPanel::new("collab-panel-inner")) + // .side(PanelSide::Left), + // ) + // .filter(|_| self.is_collab_panel_open()), + // ) + // .child(NotificationToast::new( + // "maxbrunsfeld has requested to add you as a contact.".into(), + // )) + .child( + div() + .flex() + .flex_col() + .flex_1() + .h_full() + .child(div().flex().flex_1()), // .children( + // Some( + // Panel::new("terminal-panel", cx) + // .child(Terminal::new()) + // .allowed_sides(PanelAllowedSides::BottomOnly) + // .side(PanelSide::Bottom), + // ) + // .filter(|_| self.is_terminal_open()), + // ), + ), // .children( + // Some( + // Panel::new("chat-panel-outer", cx) + // .side(PanelSide::Right) + // .child(ChatPanel::new("chat-panel-inner").messages(vec![ + // ChatMessage::new( + // "osiewicz".to_string(), + // "is this thing on?".to_string(), + // DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z") + // .unwrap() + // .naive_local(), + // ), + // ChatMessage::new( + // "maxdeviant".to_string(), + // "Reading you loud and clear!".to_string(), + // DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z") + // .unwrap() + // .naive_local(), + // ), + // ])), + // ) + // .filter(|_| self.is_chat_panel_open()), + // ) + // .children( + // Some( + // Panel::new("notifications-panel-outer", cx) + // .side(PanelSide::Right) + // .child(NotificationsPanel::new("notifications-panel-inner")), + // ) + // .filter(|_| self.is_notifications_panel_open()), + // ) + // .children( + // Some( + // Panel::new("assistant-panel-outer", cx) + // .child(AssistantPanel::new("assistant-panel-inner")), + // ) + // .filter(|_| self.is_assistant_panel_open()), + // ), + ) + // .child(StatusBar::new()) + // .when(self.debug.show_toast, |this| { + // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast"))) + // }) + // .children( + // Some( + // div() + // .absolute() + // .top(px(50.)) + // .left(px(640.)) + // .z_index(8) + // .child(LanguageSelector::new("language-selector")), + // ) + // .filter(|_| self.is_language_selector_open()), + // ) + .z_index(8) + // Debug + .child( + div() + .flex() + .flex_col() + .z_index(9) + .absolute() + .top_20() + .left_1_4() + .w_40() + .gap_2(), // .when(self.show_debug, |this| { + // this.child(Button::::new("Toggle User Settings").on_click( + // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)), + // )) + // .child( + // Button::::new("Toggle Toasts").on_click(Arc::new( + // |workspace, cx| workspace.debug_toggle_toast(cx), + // )), + // ) + // .child( + // Button::::new("Toggle Livestream").on_click(Arc::new( + // |workspace, cx| workspace.debug_toggle_livestream(cx), + // )), + // ) + // }) + // .child( + // Button::::new("Toggle Debug") + // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))), + // ), + ) } } From 66e4d75e6f1d623157c282293e0664358d124997 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 12:34:40 -0400 Subject: [PATCH 52/66] Checkpoint: Reproduction with `Avatar` story --- crates/gpui2/src/app.rs | 46 ++++++++++++++++----- crates/gpui2/src/app/async_context.rs | 35 +++++++++------- crates/gpui2/src/app/test_context.rs | 20 ++++----- crates/gpui2/src/elements/img.rs | 6 ++- crates/gpui2/src/platform/mac/dispatcher.rs | 2 + crates/ui2/src/elements/avatar.rs | 20 +++++++-- crates/ui2/src/elements/player.rs | 4 +- 7 files changed, 91 insertions(+), 42 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 5a6e360802..b20470fb18 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -5,6 +5,7 @@ mod model_context; mod test_context; pub use async_context::*; +use derive_more::{Deref, DerefMut}; pub use entity_map::*; pub use model_context::*; use refineable::Refineable; @@ -27,7 +28,7 @@ use parking_lot::Mutex; use slotmap::SlotMap; use std::{ any::{type_name, Any, TypeId}, - cell::RefCell, + cell::{Ref, RefCell, RefMut}, marker::PhantomData, mem, ops::{Deref, DerefMut}, @@ -38,7 +39,29 @@ use std::{ }; use util::http::{self, HttpClient}; -pub struct App(Rc>); +pub struct AppCell { + app: RefCell, +} +impl AppCell { + pub fn borrow(&self) -> AppRef { + AppRef(self.app.borrow()) + } + + pub fn borrow_mut(&self, label: &str) -> AppRefMut { + let thread_id = std::thread::current().id(); + + eprintln!(">>> borrowing {thread_id:?}: {label}"); + AppRefMut(self.app.borrow_mut()) + } +} + +#[derive(Deref, DerefMut)] +pub struct AppRef<'a>(Ref<'a, AppContext>); + +#[derive(Deref, DerefMut)] +pub struct AppRefMut<'a>(RefMut<'a, AppContext>); + +pub struct App(Rc); /// Represents an application before it is fully launched. Once your app is /// configured, you'll start the app with `App::run`. @@ -61,7 +84,8 @@ impl App { let this = self.0.clone(); let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { - let cx = &mut *this.borrow_mut(); + dbg!("run callback"); + let cx = &mut *this.borrow_mut("app::borrow_mut"); on_finish_launching(cx); })); } @@ -75,7 +99,7 @@ impl App { let this = Rc::downgrade(&self.0); self.0.borrow().platform.on_open_urls(Box::new(move |urls| { if let Some(app) = this.upgrade() { - callback(urls, &mut *app.borrow_mut()); + callback(urls, &mut *app.borrow_mut("app.rs::on_open_urls")); } })); self @@ -86,9 +110,9 @@ impl App { F: 'static + FnMut(&mut AppContext), { let this = Rc::downgrade(&self.0); - self.0.borrow_mut().platform.on_reopen(Box::new(move || { + self.0.borrow_mut("app.rs::on_reopen").platform.on_reopen(Box::new(move || { if let Some(app) = this.upgrade() { - callback(&mut app.borrow_mut()); + callback(&mut app.borrow_mut("app.rs::on_reopen(callback)")); } })); self @@ -119,7 +143,7 @@ type QuitHandler = Box LocalBoxFuture<'static, () type ReleaseListener = Box; pub struct AppContext { - this: Weak>, + this: Weak, pub(crate) platform: Rc, app_metadata: AppMetadata, text_system: Arc, @@ -157,7 +181,7 @@ impl AppContext { platform: Rc, asset_source: Arc, http_client: Arc, - ) -> Rc> { + ) -> Rc { let executor = platform.background_executor(); let foreground_executor = platform.foreground_executor(); assert!( @@ -174,8 +198,8 @@ impl AppContext { app_version: platform.app_version().ok(), }; - Rc::new_cyclic(|this| { - RefCell::new(AppContext { + Rc::new_cyclic(|this| AppCell { + app: RefCell::new(AppContext { this: this.clone(), text_system, platform, @@ -206,7 +230,7 @@ impl AppContext { layout_id_buffer: Default::default(), propagate_event: true, active_drag: None, - }) + }), }) } diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 4bbab43446..4fffdc693f 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,15 +1,15 @@ use crate::{ - AnyView, AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, - ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, + AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, ForegroundExecutor, + Model, ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle, }; use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; -use std::{cell::RefCell, future::Future, rc::Weak}; +use std::{future::Future, rc::Weak}; #[derive(Clone)] pub struct AsyncAppContext { - pub(crate) app: Weak>, + pub(crate) app: Weak, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, } @@ -28,7 +28,8 @@ impl Context for AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app = app.borrow_mut(); + dbg!("BUILD MODEL A"); + let mut app = app.borrow_mut("gpui2/async_context.rs::build_model"); Ok(app.build_model(build_model)) } @@ -41,7 +42,8 @@ impl Context for AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app = app.borrow_mut(); + dbg!("UPDATE MODEL B"); + let mut app = app.borrow_mut("gpui2/async_context.rs::update_model"); Ok(app.update_model(handle, update)) } @@ -50,7 +52,8 @@ impl Context for AsyncAppContext { F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { let app = self.app.upgrade().context("app was released")?; - let mut lock = app.borrow_mut(); + dbg!("UPDATE WINDOW C"); + let mut lock = app.borrow_mut("gpui2/async_context::update_window"); lock.update_window(window, f) } } @@ -61,7 +64,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.borrow_mut(); + dbg!("REFRESH"); + let mut lock = app.borrow_mut("async_context.rs::refresh"); lock.refresh(); Ok(()) } @@ -79,7 +83,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.borrow_mut(); + let mut lock = app.borrow_mut("async_context.rs::update"); Ok(f(&mut *lock)) } @@ -95,7 +99,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.borrow_mut(); + let mut lock = app.borrow_mut("open_window"); Ok(lock.open_window(options, build_root_view)) } @@ -112,7 +116,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let app = app.borrow_mut(); + let app = app.borrow_mut("has_global"); Ok(app.has_global::()) } @@ -121,7 +125,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let app = app.borrow_mut(); // Need this to compile + dbg!("read global"); + let app = app.borrow_mut("async_context.rs::read_global"); Ok(read(app.global(), &app)) } @@ -130,7 +135,8 @@ impl AsyncAppContext { read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { let app = self.app.upgrade()?; - let app = app.borrow_mut(); + dbg!("try read global"); + let app = app.borrow_mut("async_context.rs::try_read_global"); Some(read(app.try_global()?, &app)) } @@ -142,7 +148,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app = app.borrow_mut(); + dbg!("update global"); + let mut app = app.borrow_mut("async_context.rs::update_global"); Ok(app.update_global(update)) } } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index aaf42dd4a2..f0ee988ebb 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,15 +1,15 @@ use crate::{ AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, - TestPlatform, WindowContext, + TestPlatform, WindowContext, AppCell, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; -use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; +use std::{future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] pub struct TestAppContext { - pub app: Rc>, + pub app: Rc, pub background_executor: BackgroundExecutor, pub foreground_executor: ForegroundExecutor, } @@ -24,7 +24,7 @@ impl Context for TestAppContext { where T: 'static, { - let mut app = self.app.borrow_mut(); + let mut app = self.app.borrow_mut("test_context.rs::build_model"); app.build_model(build_model) } @@ -33,7 +33,7 @@ impl Context for TestAppContext { handle: &Model, update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { - let mut app = self.app.borrow_mut(); + let mut app = self.app.borrow_mut("test_context::update_model"); app.update_model(handle, update) } @@ -41,7 +41,7 @@ impl Context for TestAppContext { where F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { - let mut lock = self.app.borrow_mut(); + let mut lock = self.app.borrow_mut("test_context::update_window"); lock.update_window(window, f) } } @@ -65,11 +65,11 @@ impl TestAppContext { } pub fn quit(&self) { - self.app.borrow_mut().quit(); + self.app.borrow_mut("test_context.rs::quit").quit(); } pub fn refresh(&mut self) -> Result<()> { - let mut app = self.app.borrow_mut(); + let mut app = self.app.borrow_mut("test_context.rs::refresh"); app.refresh(); Ok(()) } @@ -83,7 +83,7 @@ impl TestAppContext { } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { - let mut cx = self.app.borrow_mut(); + let mut cx = self.app.borrow_mut("test_context::update"); cx.update(f) } @@ -117,7 +117,7 @@ impl TestAppContext { &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, ) -> R { - let mut lock = self.app.borrow_mut(); + let mut lock = self.app.borrow_mut("test_context.rs::update_global"); lock.update_global(update) } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 747e573ea5..a9950bfc0a 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -109,7 +109,9 @@ where let corner_radii = style.corner_radii; if let Some(uri) = self.uri.clone() { - let image_future = cx.image_cache.get(uri); + // eprintln!(">>> image_cache.get({uri}"); + let image_future = cx.image_cache.get(uri.clone()); + // eprintln!("<<< image_cache.get({uri}"); if let Some(data) = image_future .clone() .now_or_never() @@ -123,7 +125,9 @@ where } else { cx.spawn(|_, mut cx| async move { if image_future.await.log_err().is_some() { + eprintln!(">>> on_next_frame"); cx.on_next_frame(|cx| cx.notify()); + eprintln!("<<< on_next_frame") } }) .detach() diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index f5334912c6..a39688bafd 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -42,6 +42,7 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch(&self, runnable: Runnable) { + println!("DISPATCH"); unsafe { dispatch_async_f( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0), @@ -52,6 +53,7 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch_on_main_thread(&self, runnable: Runnable) { + println!("DISPATCH ON MAIN THREAD"); unsafe { dispatch_async_f( dispatch_get_main_queue(), diff --git a/crates/ui2/src/elements/avatar.rs b/crates/ui2/src/elements/avatar.rs index ff92021b11..ca773397ca 100644 --- a/crates/ui2/src/elements/avatar.rs +++ b/crates/ui2/src/elements/avatar.rs @@ -58,11 +58,23 @@ mod stories { .child(Avatar::new( "https://avatars.githubusercontent.com/u/1714999?v=4", )) + .child(Avatar::new( + "https://avatars.githubusercontent.com/u/326587?v=4", + )) + // .child(Avatar::new( + // "https://avatars.githubusercontent.com/u/482957?v=4", + // )) + // .child(Avatar::new( + // "https://avatars.githubusercontent.com/u/1714999?v=4", + // )) + // .child(Avatar::new( + // "https://avatars.githubusercontent.com/u/1486634?v=4", + // )) .child(Story::label(cx, "Rounded rectangle")) - .child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ) + // .child( + // Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") + // .shape(Shape::RoundedRectangle), + // ) } } } diff --git a/crates/ui2/src/elements/player.rs b/crates/ui2/src/elements/player.rs index 8e3ad5c3a8..c7b7ade1c1 100644 --- a/crates/ui2/src/elements/player.rs +++ b/crates/ui2/src/elements/player.rs @@ -139,11 +139,11 @@ impl Player { } pub fn cursor_color(&self, cx: &mut ViewContext) -> Hsla { - cx.theme().styles.player.0[self.index].cursor + cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].cursor } pub fn selection_color(&self, cx: &mut ViewContext) -> Hsla { - cx.theme().styles.player.0[self.index].selection + cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].selection } pub fn avatar_src(&self) -> &str { From 0e1d2fdf21627e0f9eb18ed55d2be7913f0e5eb0 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 12:47:06 -0400 Subject: [PATCH 53/66] Checkpoint: Narrow down error --- crates/gpui2/src/app.rs | 21 +++++++++++-------- crates/gpui2/src/app/async_context.rs | 20 +++++++++--------- crates/gpui2/src/app/test_context.rs | 14 ++++++------- crates/gpui2/src/platform.rs | 2 +- .../gpui2/src/platform/mac/display_linker.rs | 4 ++-- crates/gpui2/src/platform/mac/platform.rs | 2 +- crates/gpui2/src/platform/test/platform.rs | 2 +- crates/ui2/src/elements/avatar.rs | 3 +++ 8 files changed, 37 insertions(+), 31 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index b20470fb18..3bc0c14f9a 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -47,10 +47,10 @@ impl AppCell { AppRef(self.app.borrow()) } - pub fn borrow_mut(&self, label: &str) -> AppRefMut { + pub fn borrow_mut(&self) -> AppRefMut { let thread_id = std::thread::current().id(); - eprintln!(">>> borrowing {thread_id:?}: {label}"); + eprintln!(">>> borrowing {thread_id:?}"); AppRefMut(self.app.borrow_mut()) } } @@ -85,7 +85,7 @@ impl App { let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { dbg!("run callback"); - let cx = &mut *this.borrow_mut("app::borrow_mut"); + let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); } @@ -99,7 +99,7 @@ impl App { let this = Rc::downgrade(&self.0); self.0.borrow().platform.on_open_urls(Box::new(move |urls| { if let Some(app) = this.upgrade() { - callback(urls, &mut *app.borrow_mut("app.rs::on_open_urls")); + callback(urls, &mut *app.borrow_mut()); } })); self @@ -110,11 +110,14 @@ impl App { F: 'static + FnMut(&mut AppContext), { let this = Rc::downgrade(&self.0); - self.0.borrow_mut("app.rs::on_reopen").platform.on_reopen(Box::new(move || { - if let Some(app) = this.upgrade() { - callback(&mut app.borrow_mut("app.rs::on_reopen(callback)")); - } - })); + self.0 + .borrow_mut() + .platform + .on_reopen(Box::new(move || { + if let Some(app) = this.upgrade() { + callback(&mut app.borrow_mut()); + } + })); self } diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 4fffdc693f..f45457936c 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -29,7 +29,7 @@ impl Context for AsyncAppContext { .upgrade() .ok_or_else(|| anyhow!("app was released"))?; dbg!("BUILD MODEL A"); - let mut app = app.borrow_mut("gpui2/async_context.rs::build_model"); + let mut app = app.borrow_mut(); Ok(app.build_model(build_model)) } @@ -43,7 +43,7 @@ impl Context for AsyncAppContext { .upgrade() .ok_or_else(|| anyhow!("app was released"))?; dbg!("UPDATE MODEL B"); - let mut app = app.borrow_mut("gpui2/async_context.rs::update_model"); + let mut app = app.borrow_mut(); Ok(app.update_model(handle, update)) } @@ -53,7 +53,7 @@ impl Context for AsyncAppContext { { let app = self.app.upgrade().context("app was released")?; dbg!("UPDATE WINDOW C"); - let mut lock = app.borrow_mut("gpui2/async_context::update_window"); + let mut lock = app.borrow_mut(); lock.update_window(window, f) } } @@ -65,7 +65,7 @@ impl AsyncAppContext { .upgrade() .ok_or_else(|| anyhow!("app was released"))?; dbg!("REFRESH"); - let mut lock = app.borrow_mut("async_context.rs::refresh"); + let mut lock = app.borrow_mut(); lock.refresh(); Ok(()) } @@ -83,7 +83,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.borrow_mut("async_context.rs::update"); + let mut lock = app.borrow_mut(); Ok(f(&mut *lock)) } @@ -99,7 +99,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.borrow_mut("open_window"); + let mut lock = app.borrow_mut(); Ok(lock.open_window(options, build_root_view)) } @@ -116,7 +116,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let app = app.borrow_mut("has_global"); + let app = app.borrow_mut(); Ok(app.has_global::()) } @@ -126,7 +126,7 @@ impl AsyncAppContext { .upgrade() .ok_or_else(|| anyhow!("app was released"))?; dbg!("read global"); - let app = app.borrow_mut("async_context.rs::read_global"); + let app = app.borrow_mut(); Ok(read(app.global(), &app)) } @@ -136,7 +136,7 @@ impl AsyncAppContext { ) -> Option { let app = self.app.upgrade()?; dbg!("try read global"); - let app = app.borrow_mut("async_context.rs::try_read_global"); + let app = app.borrow_mut(); Some(read(app.try_global()?, &app)) } @@ -149,7 +149,7 @@ impl AsyncAppContext { .upgrade() .ok_or_else(|| anyhow!("app was released"))?; dbg!("update global"); - let mut app = app.borrow_mut("async_context.rs::update_global"); + let mut app = app.borrow_mut(); Ok(app.update_global(update)) } } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index f0ee988ebb..7ef53d3e12 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -24,7 +24,7 @@ impl Context for TestAppContext { where T: 'static, { - let mut app = self.app.borrow_mut("test_context.rs::build_model"); + let mut app = self.app.borrow_mut(); app.build_model(build_model) } @@ -33,7 +33,7 @@ impl Context for TestAppContext { handle: &Model, update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { - let mut app = self.app.borrow_mut("test_context::update_model"); + let mut app = self.app.borrow_mut(); app.update_model(handle, update) } @@ -41,7 +41,7 @@ impl Context for TestAppContext { where F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { - let mut lock = self.app.borrow_mut("test_context::update_window"); + let mut lock = self.app.borrow_mut(); lock.update_window(window, f) } } @@ -65,11 +65,11 @@ impl TestAppContext { } pub fn quit(&self) { - self.app.borrow_mut("test_context.rs::quit").quit(); + self.app.borrow_mut().quit(); } pub fn refresh(&mut self) -> Result<()> { - let mut app = self.app.borrow_mut("test_context.rs::refresh"); + let mut app = self.app.borrow_mut(); app.refresh(); Ok(()) } @@ -83,7 +83,7 @@ impl TestAppContext { } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { - let mut cx = self.app.borrow_mut("test_context::update"); + let mut cx = self.app.borrow_mut(); cx.update(f) } @@ -117,7 +117,7 @@ impl TestAppContext { &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, ) -> R { - let mut lock = self.app.borrow_mut("test_context.rs::update_global"); + let mut lock = self.app.borrow_mut(); lock.update_global(update) } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index cdce67d8c1..a4be21ddf3 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -69,7 +69,7 @@ pub(crate) trait Platform: 'static { fn set_display_link_output_callback( &self, display_id: DisplayId, - callback: Box, + callback: Box, ); fn start_display_link(&self, display_id: DisplayId); fn stop_display_link(&self, display_id: DisplayId); diff --git a/crates/gpui2/src/platform/mac/display_linker.rs b/crates/gpui2/src/platform/mac/display_linker.rs index 6d8235a381..b63cf24e26 100644 --- a/crates/gpui2/src/platform/mac/display_linker.rs +++ b/crates/gpui2/src/platform/mac/display_linker.rs @@ -26,13 +26,13 @@ impl MacDisplayLinker { } } -type OutputCallback = Mutex>; +type OutputCallback = Mutex>; impl MacDisplayLinker { pub fn set_output_callback( &mut self, display_id: DisplayId, - output_callback: Box, + output_callback: Box, ) { if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } { let callback = Arc::new(Mutex::new(output_callback)); diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index fdc7fd6ae5..7065c02e87 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -494,7 +494,7 @@ impl Platform for MacPlatform { fn set_display_link_output_callback( &self, display_id: DisplayId, - callback: Box, + callback: Box, ) { self.0 .lock() diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 524611b620..b4f3c739e6 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -81,7 +81,7 @@ impl Platform for TestPlatform { fn set_display_link_output_callback( &self, _display_id: DisplayId, - _callback: Box, + _callback: Box, ) { unimplemented!() } diff --git a/crates/ui2/src/elements/avatar.rs b/crates/ui2/src/elements/avatar.rs index ca773397ca..357e573f7c 100644 --- a/crates/ui2/src/elements/avatar.rs +++ b/crates/ui2/src/elements/avatar.rs @@ -62,6 +62,9 @@ mod stories { "https://avatars.githubusercontent.com/u/326587?v=4", )) // .child(Avatar::new( + // "https://avatars.githubusercontent.com/u/326587?v=4", + // )) + // .child(Avatar::new( // "https://avatars.githubusercontent.com/u/482957?v=4", // )) // .child(Avatar::new( From 803d2b671062ee0e6563fe730253422046fc23b7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 10:58:24 -0600 Subject: [PATCH 54/66] Add double click to zoom the window Co-Authored-By: Antonio --- crates/gpui2/src/platform/mac/window.rs | 33 +++++++++++++++++-------- crates/gpui2/src/window.rs | 4 +++ crates/workspace2/src/workspace2.rs | 7 +++--- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index 52dcf31603..affeab57c7 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -678,10 +678,15 @@ impl MacWindow { impl Drop for MacWindow { fn drop(&mut self) { - let native_window = self.0.lock().native_window; - unsafe { - native_window.close(); - } + let this = self.0.lock(); + let window = this.native_window; + this.executor + .spawn(async move { + unsafe { + window.close(); + } + }) + .detach(); } } @@ -868,17 +873,25 @@ impl PlatformWindow for MacWindow { fn zoom(&self) { let this = self.0.lock(); let window = this.native_window; - unsafe { - window.zoom_(nil); - } + this.executor + .spawn(async move { + unsafe { + window.zoom_(nil); + } + }) + .detach(); } fn toggle_full_screen(&self) { let this = self.0.lock(); let window = this.native_window; - unsafe { - window.toggleFullScreen_(nil); - } + this.executor + .spawn(async move { + unsafe { + window.toggleFullScreen_(nil); + } + }) + .detach(); } fn on_input(&self, callback: Box bool>) { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 8e1022c890..880f4fc6e6 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -575,6 +575,10 @@ impl<'a> WindowContext<'a> { self.window.active } + pub fn zoom_window(&self) { + self.window.platform_window.zoom(); + } + pub fn display(&self) -> Option> { self.platform .displays() diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3db4900463..38e01e10b0 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2690,14 +2690,15 @@ impl Workspace { fn render_titlebar(&self, cx: &mut ViewContext) -> impl Component { div() + .bg(cx.theme().colors().title_bar) .when( - matches!(cx.window_bounds(), WindowBounds::Fullscreen), + !matches!(cx.window_bounds(), WindowBounds::Fullscreen), |s| s.pl_20(), ) - .id(0) + .id("titlebar") .on_click(|workspace, event, cx| { if event.up.click_count == 2 { - println!("ZOOOOOM") + cx.zoom_window(); } }) .child("Collab title bar Item") // self.titlebar_item From 8f0f5a9ba1393eab1d506daa5440078866bc6131 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 11:18:11 -0600 Subject: [PATCH 55/66] Render status bar Co-Authored-By: Antonio --- crates/workspace2/src/dock.rs | 13 ++++++--- crates/workspace2/src/status_bar.rs | 41 ++++++++++++++++++++++++++++- crates/workspace2/src/workspace2.rs | 8 +++--- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index b95c534257..727d777c46 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,7 +1,7 @@ use crate::{status_bar::StatusItemView, Axis, Workspace}; use gpui2::{ - Action, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, Render, Subscription, View, - ViewContext, WeakView, WindowContext, + div, Action, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, ParentElement, Render, + Subscription, View, ViewContext, WeakView, WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -605,8 +605,13 @@ impl EventEmitter for PanelButtons { impl Render for PanelButtons { type Element = Div; - fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { - todo!() + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let dock = self.dock.read(cx); + div().children( + dock.panel_entries + .iter() + .map(|panel| panel.panel.persistent_name(cx)), + ) } } diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index 21f20e9761..52134683d8 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -1,7 +1,11 @@ use std::any::TypeId; use crate::{ItemHandle, Pane}; -use gpui2::{AnyView, Render, Subscription, View, ViewContext, WindowContext}; +use gpui2::{ + div, AnyView, Component, Div, Element, ParentElement, Render, Styled, Subscription, View, + ViewContext, WindowContext, +}; +use theme2::ActiveTheme; use util::ResultExt; pub trait StatusItemView: Render { @@ -29,6 +33,41 @@ pub struct StatusBar { _observe_active_pane: Subscription, } +impl Render for StatusBar { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + div() + .py_0p5() + .px_1() + .flex() + .items_center() + .justify_between() + .w_full() + .bg(cx.theme().colors().status_bar) + .child(self.render_left_tools(cx)) + .child(self.render_right_tools(cx)) + } +} + +impl StatusBar { + fn render_left_tools(&self, cx: &mut ViewContext) -> impl Component { + div() + .flex() + .items_center() + .gap_1() + .children(self.left_items.iter().map(|item| item.to_any())) + } + + fn render_right_tools(&self, cx: &mut ViewContext) -> impl Component { + div() + .flex() + .items_center() + .gap_2() + .children(self.right_items.iter().map(|item| item.to_any())) + } +} + // todo!() // impl View for StatusBar { // fn ui_name() -> &'static str { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 38e01e10b0..61b9243894 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -555,7 +555,7 @@ pub struct Workspace { active_pane: View, last_active_center_pane: Option>, last_active_view_id: Option, - // status_bar: View, + status_bar: View, // titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: Model, @@ -704,7 +704,7 @@ impl Workspace { cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); - let _status_bar = cx.build_view(|cx| { + let status_bar = cx.build_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); @@ -771,7 +771,7 @@ impl Workspace { active_pane: center_pane.clone(), last_active_center_pane: Some(center_pane.downgrade()), last_active_view_id: None, - // status_bar, + status_bar, // titlebar_item: None, notifications: Default::default(), left_dock, @@ -3856,7 +3856,7 @@ impl Render for Workspace { // .filter(|_| self.is_assistant_panel_open()), // ), ) - // .child(StatusBar::new()) + .child(self.status_bar.clone()) // .when(self.debug.show_toast, |this| { // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast"))) // }) From 1e7a216d554c4bf1cd4d03411bb20af199ff2953 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 13:21:28 -0400 Subject: [PATCH 56/66] WIP --- crates/gpui2/src/app.rs | 16 ++++++++++++---- crates/gpui2/src/window.rs | 30 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 3bc0c14f9a..8de3734c4c 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -139,12 +139,18 @@ impl App { } type ActionBuilder = fn(json: Option) -> anyhow::Result>; -type FrameCallback = Box; +type FrameCallback = Box; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; type ReleaseListener = Box; +// struct FrameConsumer { +// next_frame_callbacks: Vec, +// task: Task<()>, +// display_linker +// } + pub struct AppContext { this: Weak, pub(crate) platform: Rc, @@ -154,6 +160,7 @@ pub struct AppContext { pending_updates: usize, pub(crate) active_drag: Option, pub(crate) next_frame_callbacks: HashMap>, + pub(crate) frame_consumers: HashMap>, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, pub(crate) svg_renderer: SvgRenderer, @@ -204,12 +211,14 @@ impl AppContext { Rc::new_cyclic(|this| AppCell { app: RefCell::new(AppContext { this: this.clone(), - text_system, platform, app_metadata, + text_system, flushing_effects: false, pending_updates: 0, - next_frame_callbacks: Default::default(), + active_drag: None, + next_frame_callbacks: HashMap::default(), + frame_consumers: HashMap::default(), background_executor: executor, foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), @@ -232,7 +241,6 @@ impl AppContext { quit_observers: SubscriberSet::new(), layout_id_buffer: Default::default(), propagate_event: true, - active_drag: None, }), }) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0202b7521e..6034727d82 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -12,7 +12,10 @@ use crate::{ use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; -use futures::channel::oneshot; +use futures::{ + channel::{mpsc, oneshot}, + StreamExt, +}; use parking_lot::RwLock; use slotmap::SlotMap; use smallvec::SmallVec; @@ -411,6 +414,31 @@ impl<'a> WindowContext<'a> { let f = Box::new(f); let display_id = self.window.display_id; + self.next_frame_callbacks + .entry(display_id) + .or_default() + .push(f); + + self.frame_consumers.entry(display_id).or_insert_with(|| { + let (tx, rx) = mpsc::unbounded::<()>(); + + self.spawn(|cx| async move { + while rx.next().await.is_some() { + let _ = cx.update(|_, cx| { + for callback in cx + .app + .next_frame_callbacks + .get_mut(&display_id) + .unwrap() + .drain(..) + { + callback(cx); + } + }); + } + }) + }); + if let Some(callbacks) = self.next_frame_callbacks.get_mut(&display_id) { callbacks.push(f); // If there was already a callback, it means that we already scheduled a frame. From 5a41eed120fffd695d0b61f0b6e8d38da51d049b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 11:34:31 -0600 Subject: [PATCH 57/66] WIP --- Cargo.lock | 1 + crates/workspace2/Cargo.toml | 1 + crates/workspace2/src/dock.rs | 1 + crates/workspace2/src/pane_group.rs | 39 ++++++++++++++++++++++++++--- crates/workspace2/src/workspace2.rs | 33 +++++++++++++----------- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f91f574b9c..134972e3a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10831,6 +10831,7 @@ dependencies = [ "smallvec", "terminal2", "theme2", + "ui2", "util", "uuid 1.4.1", ] diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index 7c55d8bedb..5072f2b8f9 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -35,6 +35,7 @@ settings2 = { path = "../settings2" } terminal2 = { path = "../terminal2" } theme2 = { path = "../theme2" } util = { path = "../util" } +ui = { package = "ui2", path = "../ui2" } async-recursion = "1.0.0" itertools = "0.10" diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 727d777c46..20a06d1658 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -606,6 +606,7 @@ impl Render for PanelButtons { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + // todo!() let dock = self.dock.read(cx); div().children( dock.panel_entries diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index f4fdb6ba16..22bbb946a7 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -12,6 +12,7 @@ use project2::Project; use serde::Deserialize; use std::sync::Arc; use theme2::ThemeVariant; +use ui::prelude::*; const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; @@ -124,7 +125,6 @@ impl PaneGroup { pub(crate) fn render( &self, project: &Model, - theme: &ThemeVariant, follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, @@ -135,7 +135,6 @@ impl PaneGroup { self.root.render( project, 0, - theme, follower_states, active_call, active_pane, @@ -187,7 +186,6 @@ impl Member { &self, project: &Model, basis: usize, - theme: &ThemeVariant, follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, @@ -195,7 +193,40 @@ impl Member { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - todo!() + match self { + Member::Pane(pane) => { + let pane_element = if Some(&**pane) == zoomed { + None + } else { + Some(pane) + }; + + // Stack::new() + // .with_child(pane_element.contained().with_border(leader_border)) + // .with_children(leader_status_box) + // .into_any() + + let el = div() + .flex() + .flex_1() + .gap_px() + .w_full() + .h_full() + .bg(cx.theme().colors().editor) + .children(); + } + Member::Axis(axis) => axis.render( + project, + basis + 1, + theme, + follower_states, + active_call, + active_pane, + zoomed, + app_state, + cx, + ), + } // enum FollowIntoExternalProject {} diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 61b9243894..f3516c0fa4 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3803,20 +3803,25 @@ impl Render for Workspace { // "maxbrunsfeld has requested to add you as a contact.".into(), // )) .child( - div() - .flex() - .flex_col() - .flex_1() - .h_full() - .child(div().flex().flex_1()), // .children( - // Some( - // Panel::new("terminal-panel", cx) - // .child(Terminal::new()) - // .allowed_sides(PanelAllowedSides::BottomOnly) - // .side(PanelSide::Bottom), - // ) - // .filter(|_| self.is_terminal_open()), - // ), + div().flex().flex_col().flex_1().h_full().child( + div().flex().flex_1().child(self.center.render( + project, + follower_states, + active_call, + active_pane, + zoomed, + app_state, + cx, + )), + ), // .children( + // Some( + // Panel::new("terminal-panel", cx) + // .child(Terminal::new()) + // .allowed_sides(PanelAllowedSides::BottomOnly) + // .side(PanelSide::Bottom), + // ) + // .filter(|_| self.is_terminal_open()), + // ), ), // .children( // Some( // Panel::new("chat-panel-outer", cx) From d11ff14b57d966c485a496c338711b117836e38b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 2 Nov 2023 10:55:02 -0700 Subject: [PATCH 58/66] Remove the 2s from source code --- Cargo.lock | 2 - crates/ai2/Cargo.toml | 6 +- crates/ai2/src/auth.rs | 2 +- crates/ai2/src/embedding.rs | 2 +- crates/ai2/src/prompts/base.rs | 2 +- crates/ai2/src/prompts/file_context.rs | 4 +- crates/ai2/src/prompts/repository_context.rs | 4 +- .../ai2/src/providers/open_ai/completion.rs | 2 +- crates/ai2/src/providers/open_ai/embedding.rs | 4 +- crates/ai2/src/test.rs | 2 +- crates/audio2/Cargo.toml | 2 +- crates/audio2/src/assets.rs | 2 +- crates/audio2/src/audio2.rs | 2 +- crates/call2/Cargo.toml | 36 +- crates/call2/src/call2.rs | 12 +- crates/call2/src/call_settings.rs | 4 +- crates/call2/src/participant.rs | 16 +- crates/call2/src/room.rs | 32 +- crates/client2/Cargo.toml | 18 +- crates/client2/src/client2.rs | 28 +- crates/client2/src/telemetry.rs | 4 +- crates/client2/src/test.rs | 4 +- crates/client2/src/user.rs | 6 +- crates/copilot2/Cargo.toml | 24 +- crates/copilot2/src/copilot2.rs | 70 +- crates/copilot2/src/request.rs | 30 +- crates/db2/Cargo.toml | 4 +- crates/db2/src/db2.rs | 12 +- crates/db2/src/kvp.rs | 2 +- crates/feature_flags2/Cargo.toml | 2 +- crates/feature_flags2/src/feature_flags2.rs | 2 +- crates/fs2/Cargo.toml | 6 +- crates/fs2/src/fs2.rs | 8 +- crates/fuzzy2/Cargo.toml | 2 +- crates/fuzzy2/src/paths.rs | 2 +- crates/fuzzy2/src/strings.rs | 2 +- crates/gpui2_macros/src/test.rs | 14 +- crates/install_cli2/Cargo.toml | 2 +- crates/install_cli2/src/install_cli2.rs | 2 +- crates/journal2/Cargo.toml | 2 +- crates/journal2/src/journal2.rs | 2 +- crates/language2/Cargo.toml | 26 +- crates/language2/src/buffer.rs | 20 +- crates/language2/src/buffer_tests.rs | 94 +-- crates/language2/src/diagnostic_set.rs | 8 +- crates/language2/src/highlight_map.rs | 6 +- crates/language2/src/language2.rs | 70 +- crates/language2/src/language_settings.rs | 8 +- crates/language2/src/outline.rs | 6 +- crates/language2/src/proto.rs | 4 +- .../src/syntax_map/syntax_map_tests.rs | 42 +- crates/live_kit_client2/Cargo.toml | 4 +- crates/lsp2/Cargo.toml | 4 +- crates/lsp2/src/lsp2.rs | 10 +- crates/menu2/Cargo.toml | 2 +- crates/multi_buffer2/Cargo.toml | 30 +- crates/multi_buffer2/src/anchor.rs | 2 +- crates/multi_buffer2/src/multi_buffer2.rs | 84 +- crates/prettier2/Cargo.toml | 16 +- crates/prettier2/src/prettier2.rs | 14 +- crates/project2/Cargo.toml | 54 +- crates/project2/src/lsp_command.rs | 374 +++++---- crates/project2/src/project2.rs | 338 ++++---- crates/project2/src/project_settings.rs | 4 +- crates/project2/src/project_tests.rs | 722 ++++++++---------- crates/project2/src/search.rs | 4 +- crates/project2/src/terminals.rs | 10 +- crates/project2/src/worktree.rs | 32 +- crates/rpc2/Cargo.toml | 6 +- crates/rpc2/src/conn.rs | 4 +- crates/rpc2/src/peer.rs | 14 +- crates/rpc2/src/proto.rs | 4 +- crates/settings2/Cargo.toml | 12 +- crates/settings2/src/keymap_file.rs | 4 +- crates/settings2/src/settings_file.rs | 4 +- crates/settings2/src/settings_store.rs | 8 +- crates/terminal2/Cargo.toml | 8 +- crates/terminal2/src/mappings/colors.rs | 2 +- crates/terminal2/src/mappings/keys.rs | 4 +- crates/terminal2/src/mappings/mouse.rs | 24 +- crates/terminal2/src/terminal2.rs | 6 +- crates/terminal2/src/terminal_settings.rs | 4 +- crates/text2/Cargo.toml | 2 +- crates/text2/src/locator.rs | 2 +- crates/text2/src/patch.rs | 14 +- crates/text2/src/tests.rs | 4 +- crates/theme2/Cargo.toml | 12 +- crates/theme2/src/colors.rs | 8 +- crates/theme2/src/default_colors.rs | 2 +- crates/theme2/src/registry.rs | 2 +- crates/theme2/src/scale.rs | 2 +- crates/theme2/src/settings.rs | 6 +- crates/theme2/src/syntax.rs | 2 +- crates/theme2/src/theme2.rs | 4 +- crates/zed2/Cargo.toml | 42 +- crates/zed2/src/assets.rs | 3 +- crates/zed2/src/languages.rs | 6 +- crates/zed2/src/languages/c.rs | 52 +- crates/zed2/src/languages/css.rs | 4 +- crates/zed2/src/languages/elixir.rs | 18 +- crates/zed2/src/languages/go.rs | 57 +- crates/zed2/src/languages/html.rs | 4 +- crates/zed2/src/languages/json.rs | 10 +- crates/zed2/src/languages/lua.rs | 4 +- crates/zed2/src/languages/php.rs | 10 +- crates/zed2/src/languages/python.rs | 46 +- crates/zed2/src/languages/ruby.rs | 40 +- crates/zed2/src/languages/rust.rs | 92 +-- crates/zed2/src/languages/svelte.rs | 4 +- crates/zed2/src/languages/tailwind.rs | 6 +- crates/zed2/src/languages/typescript.rs | 28 +- crates/zed2/src/languages/vue.rs | 14 +- crates/zed2/src/languages/yaml.rs | 6 +- crates/zed2/src/main.rs | 36 +- crates/zed2/src/zed2.rs | 4 +- 115 files changed, 1473 insertions(+), 1549 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 903dd125c5..7545448720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1541,7 +1541,6 @@ dependencies = [ "schemars", "serde", "serde_derive", - "settings", "settings2", "smol", "sum_tree", @@ -7796,7 +7795,6 @@ dependencies = [ "anyhow", "collections", "feature_flags2", - "fs", "fs2", "futures 0.3.28", "gpui2", diff --git a/crates/ai2/Cargo.toml b/crates/ai2/Cargo.toml index 4f06840e8e..aee265db6e 100644 --- a/crates/ai2/Cargo.toml +++ b/crates/ai2/Cargo.toml @@ -12,9 +12,9 @@ doctest = false test-support = [] [dependencies] -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } -language2 = { path = "../language2" } +language = { package = "language2", path = "../language2" } async-trait.workspace = true anyhow.workspace = true futures.workspace = true @@ -35,4 +35,4 @@ rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } bincode = "1.3.3" [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/ai2/src/auth.rs b/crates/ai2/src/auth.rs index 995f20d39c..baa1fe7b83 100644 --- a/crates/ai2/src/auth.rs +++ b/crates/ai2/src/auth.rs @@ -1,4 +1,4 @@ -use gpui2::AppContext; +use gpui::AppContext; #[derive(Clone, Debug)] pub enum ProviderCredential { diff --git a/crates/ai2/src/embedding.rs b/crates/ai2/src/embedding.rs index 7ea4786178..6768b7ce7b 100644 --- a/crates/ai2/src/embedding.rs +++ b/crates/ai2/src/embedding.rs @@ -81,7 +81,7 @@ mod tests { use super::*; use rand::prelude::*; - #[gpui2::test] + #[gpui::test] fn test_similarity(mut rng: StdRng) { assert_eq!( Embedding::from(vec![1., 0., 0., 0., 0.]) diff --git a/crates/ai2/src/prompts/base.rs b/crates/ai2/src/prompts/base.rs index 29091d0f5b..75bad00154 100644 --- a/crates/ai2/src/prompts/base.rs +++ b/crates/ai2/src/prompts/base.rs @@ -2,7 +2,7 @@ use std::cmp::Reverse; use std::ops::Range; use std::sync::Arc; -use language2::BufferSnapshot; +use language::BufferSnapshot; use util::ResultExt; use crate::models::LanguageModel; diff --git a/crates/ai2/src/prompts/file_context.rs b/crates/ai2/src/prompts/file_context.rs index 4a741beb24..f108a62f6f 100644 --- a/crates/ai2/src/prompts/file_context.rs +++ b/crates/ai2/src/prompts/file_context.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; -use language2::BufferSnapshot; -use language2::ToOffset; +use language::BufferSnapshot; +use language::ToOffset; use crate::models::LanguageModel; use crate::models::TruncationDirection; diff --git a/crates/ai2/src/prompts/repository_context.rs b/crates/ai2/src/prompts/repository_context.rs index 1bb75de7d2..0d831c2cb2 100644 --- a/crates/ai2/src/prompts/repository_context.rs +++ b/crates/ai2/src/prompts/repository_context.rs @@ -2,8 +2,8 @@ use crate::prompts::base::{PromptArguments, PromptTemplate}; use std::fmt::Write; use std::{ops::Range, path::PathBuf}; -use gpui2::{AsyncAppContext, Model}; -use language2::{Anchor, Buffer}; +use gpui::{AsyncAppContext, Model}; +use language::{Anchor, Buffer}; #[derive(Clone)] pub struct PromptCodeSnippet { diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index bf9dc704a2..3e49fc5290 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -3,7 +3,7 @@ use futures::{ future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt, Stream, StreamExt, }; -use gpui2::{AppContext, BackgroundExecutor}; +use gpui::{AppContext, BackgroundExecutor}; use isahc::{http::StatusCode, Request, RequestExt}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; diff --git a/crates/ai2/src/providers/open_ai/embedding.rs b/crates/ai2/src/providers/open_ai/embedding.rs index 27a01328f3..8f62c8dc0d 100644 --- a/crates/ai2/src/providers/open_ai/embedding.rs +++ b/crates/ai2/src/providers/open_ai/embedding.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::AsyncReadExt; -use gpui2::BackgroundExecutor; -use gpui2::{serde_json, AppContext}; +use gpui::BackgroundExecutor; +use gpui::{serde_json, AppContext}; use isahc::http::StatusCode; use isahc::prelude::Configurable; use isahc::{AsyncBody, Response}; diff --git a/crates/ai2/src/test.rs b/crates/ai2/src/test.rs index b061a47139..3d59febbe9 100644 --- a/crates/ai2/src/test.rs +++ b/crates/ai2/src/test.rs @@ -5,7 +5,7 @@ use std::{ use async_trait::async_trait; use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; -use gpui2::AppContext; +use gpui::AppContext; use parking_lot::Mutex; use crate::{ diff --git a/crates/audio2/Cargo.toml b/crates/audio2/Cargo.toml index 298142dbef..3688f108f4 100644 --- a/crates/audio2/Cargo.toml +++ b/crates/audio2/Cargo.toml @@ -9,7 +9,7 @@ path = "src/audio2.rs" doctest = false [dependencies] -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } collections = { path = "../collections" } util = { path = "../util" } diff --git a/crates/audio2/src/assets.rs b/crates/audio2/src/assets.rs index 66e0bf5aa5..b58e1f6aee 100644 --- a/crates/audio2/src/assets.rs +++ b/crates/audio2/src/assets.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc}; use anyhow::Result; use collections::HashMap; -use gpui2::{AppContext, AssetSource}; +use gpui::{AppContext, AssetSource}; use rodio::{ source::{Buffered, SamplesConverter}, Decoder, Source, diff --git a/crates/audio2/src/audio2.rs b/crates/audio2/src/audio2.rs index 286b07aba1..9264ed25d6 100644 --- a/crates/audio2/src/audio2.rs +++ b/crates/audio2/src/audio2.rs @@ -1,5 +1,5 @@ use assets::SoundRegistry; -use gpui2::{AppContext, AssetSource}; +use gpui::{AppContext, AssetSource}; use rodio::{OutputStream, OutputStreamHandle}; use util::ResultExt; diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index e918ada3e8..9e13463680 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -10,26 +10,26 @@ doctest = false [features] test-support = [ - "client2/test-support", + "client/test-support", "collections/test-support", - "gpui2/test-support", - "live_kit_client2/test-support", - "project2/test-support", + "gpui/test-support", + "live_kit_client/test-support", + "project/test-support", "util/test-support" ] [dependencies] -audio2 = { path = "../audio2" } -client2 = { path = "../client2" } +audio = { package = "audio2", path = "../audio2" } +client = { package = "client2", path = "../client2" } collections = { path = "../collections" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } log.workspace = true -live_kit_client2 = { path = "../live_kit_client2" } -fs2 = { path = "../fs2" } -language2 = { path = "../language2" } +live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" } +fs = { package = "fs2", path = "../fs2" } +language = { package = "language2", path = "../language2" } media = { path = "../media" } -project2 = { path = "../project2" } -settings2 = { path = "../settings2" } +project = { package = "project2", path = "../project2" } +settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } anyhow.workspace = true @@ -42,11 +42,11 @@ serde_json.workspace = true serde_derive.workspace = true [dev-dependencies] -client2 = { path = "../client2", features = ["test-support"] } -fs2 = { path = "../fs2", features = ["test-support"] } -language2 = { path = "../language2", features = ["test-support"] } +client = { package = "client2", path = "../client2", features = ["test-support"] } +fs = { package = "fs2", path = "../fs2", features = ["test-support"] } +language = { package = "language2", path = "../language2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -live_kit_client2 = { path = "../live_kit_client2", features = ["test-support"] } -project2 = { path = "../project2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] } +project = { package = "project2", path = "../project2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 9383f9845f..477931919d 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -3,21 +3,21 @@ pub mod participant; pub mod room; use anyhow::{anyhow, Result}; -use audio2::Audio; +use audio::Audio; use call_settings::CallSettings; -use client2::{ +use client::{ proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE, }; use collections::HashSet; use futures::{future::Shared, FutureExt}; -use gpui2::{ +use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, WeakModel, }; use postage::watch; -use project2::Project; -use settings2::Settings; +use project::Project; +use settings::Settings; use std::sync::Arc; pub use participant::ParticipantLocation; @@ -50,7 +50,7 @@ pub struct ActiveCall { ), client: Arc, user_store: Model, - _subscriptions: Vec, + _subscriptions: Vec, } impl EventEmitter for ActiveCall { diff --git a/crates/call2/src/call_settings.rs b/crates/call2/src/call_settings.rs index c83ed73980..9375feedf0 100644 --- a/crates/call2/src/call_settings.rs +++ b/crates/call2/src/call_settings.rs @@ -1,8 +1,8 @@ use anyhow::Result; -use gpui2::AppContext; +use gpui::AppContext; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; #[derive(Deserialize, Debug)] pub struct CallSettings { diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index 9fe212e776..f62d103f17 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -1,11 +1,11 @@ use anyhow::{anyhow, Result}; -use client2::ParticipantIndex; -use client2::{proto, User}; +use client::ParticipantIndex; +use client::{proto, User}; use collections::HashMap; -use gpui2::WeakModel; -pub use live_kit_client2::Frame; -use live_kit_client2::{RemoteAudioTrack, RemoteVideoTrack}; -use project2::Project; +use gpui::WeakModel; +pub use live_kit_client::Frame; +use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; +use project::Project; use std::sync::Arc; #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -47,6 +47,6 @@ pub struct RemoteParticipant { pub participant_index: ParticipantIndex, pub muted: bool, pub speaking: bool, - pub video_tracks: HashMap>, - pub audio_tracks: HashMap>, + pub video_tracks: HashMap>, + pub audio_tracks: HashMap>, } diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index deeec1df24..a46269a508 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -4,25 +4,25 @@ use crate::{ IncomingCall, }; use anyhow::{anyhow, Result}; -use audio2::{Audio, Sound}; -use client2::{ +use audio::{Audio, Sound}; +use client::{ proto::{self, PeerId}, Client, ParticipantIndex, TypedEnvelope, User, UserStore, }; use collections::{BTreeMap, HashMap, HashSet}; -use fs2::Fs; +use fs::Fs; use futures::{FutureExt, StreamExt}; -use gpui2::{ +use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; -use language2::LanguageRegistry; -use live_kit_client2::{ +use language::LanguageRegistry; +use live_kit_client::{ LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, }; use postage::{sink::Sink, stream::Stream, watch}; -use project2::Project; -use settings2::Settings; +use project::Project; +use settings::Settings; use std::{future::Future, mem, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -72,8 +72,8 @@ pub struct Room { client: Arc, user_store: Model, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>, - client_subscriptions: Vec, - _subscriptions: Vec, + client_subscriptions: Vec, + _subscriptions: Vec, room_update_completed_tx: watch::Sender>, room_update_completed_rx: watch::Receiver>, pending_room_update: Option>, @@ -98,7 +98,7 @@ impl Room { if let Some(live_kit) = self.live_kit.as_ref() { matches!( *live_kit.room.status().borrow(), - live_kit_client2::ConnectionState::Connected { .. } + live_kit_client::ConnectionState::Connected { .. } ) } else { false @@ -114,7 +114,7 @@ impl Room { cx: &mut ModelContext, ) -> Self { let live_kit_room = if let Some(connection_info) = live_kit_connection_info { - let room = live_kit_client2::Room::new(); + let room = live_kit_client::Room::new(); let mut status = room.status(); // Consume the initial status of the room. let _ = status.try_recv(); @@ -126,7 +126,7 @@ impl Room { break; }; - if status == live_kit_client2::ConnectionState::Disconnected { + if status == live_kit_client::ConnectionState::Disconnected { this.update(&mut cx, |this, cx| this.leave(cx).log_err()) .ok(); break; @@ -341,7 +341,7 @@ impl Room { } pub fn mute_on_join(cx: &AppContext) -> bool { - CallSettings::get_global(cx).mute_on_join || client2::IMPERSONATE_LOGIN.is_some() + CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() } fn from_join_response( @@ -1504,7 +1504,7 @@ impl Room { } #[cfg(any(test, feature = "test-support"))] - pub fn set_display_sources(&self, sources: Vec) { + pub fn set_display_sources(&self, sources: Vec) { self.live_kit .as_ref() .unwrap() @@ -1514,7 +1514,7 @@ impl Room { } struct LiveKitRoom { - room: Arc, + room: Arc, screen_track: LocalTrack, microphone_track: LocalTrack, /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user. diff --git a/crates/client2/Cargo.toml b/crates/client2/Cargo.toml index 8a6edbb428..45e1f618d2 100644 --- a/crates/client2/Cargo.toml +++ b/crates/client2/Cargo.toml @@ -9,17 +9,17 @@ path = "src/client2.rs" doctest = false [features] -test-support = ["collections/test-support", "gpui2/test-support", "rpc2/test-support"] +test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] [dependencies] collections = { path = "../collections" } -db2 = { path = "../db2" } -gpui2 = { path = "../gpui2" } +db = { package = "db2", path = "../db2" } +gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } -rpc2 = { path = "../rpc2" } +rpc = { package = "rpc2", path = "../rpc2" } text = { path = "../text" } -settings2 = { path = "../settings2" } -feature_flags2 = { path = "../feature_flags2" } +settings = { package = "settings2", path = "../settings2" } +feature_flags = { package = "feature_flags2", path = "../feature_flags2" } sum_tree = { path = "../sum_tree" } anyhow.workspace = true @@ -46,7 +46,7 @@ url = "2.2" [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -rpc2 = { path = "../rpc2", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index b933b62a6f..6494e0350b 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -14,7 +14,7 @@ use futures::{ future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt, }; -use gpui2::{ +use gpui::{ serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task, WeakModel, }; @@ -22,10 +22,10 @@ use lazy_static::lazy_static; use parking_lot::RwLock; use postage::watch; use rand::prelude::*; -use rpc2::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; +use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; use std::{ any::TypeId, collections::HashMap, @@ -44,7 +44,7 @@ use util::channel::ReleaseChannel; use util::http::HttpClient; use util::{ResultExt, TryFutureExt}; -pub use rpc2::*; +pub use rpc::*; pub use telemetry::ClickhouseEvent; pub use user::*; @@ -367,7 +367,7 @@ pub struct TelemetrySettingsContent { pub metrics: Option, } -impl settings2::Settings for TelemetrySettings { +impl settings::Settings for TelemetrySettings { const KEY: Option<&'static str> = Some("telemetry"); type FileContent = TelemetrySettingsContent; @@ -979,7 +979,7 @@ impl Client { "Authorization", format!("{} {}", credentials.user_id, credentials.access_token), ) - .header("x-zed-protocol-version", rpc2::PROTOCOL_VERSION); + .header("x-zed-protocol-version", rpc::PROTOCOL_VERSION); let http = self.http.clone(); cx.background_executor().spawn(async move { @@ -1029,7 +1029,7 @@ impl Client { // zed server to encrypt the user's access token, so that it can'be intercepted by // any other app running on the user's device. let (public_key, private_key) = - rpc2::auth::keypair().expect("failed to generate keypair for auth"); + rpc::auth::keypair().expect("failed to generate keypair for auth"); let public_key_string = String::try_from(public_key).expect("failed to serialize public key for auth"); @@ -1383,12 +1383,12 @@ mod tests { use super::*; use crate::test::FakeServer; - use gpui2::{BackgroundExecutor, Context, TestAppContext}; + use gpui::{BackgroundExecutor, Context, TestAppContext}; use parking_lot::Mutex; use std::future; use util::http::FakeHttpClient; - #[gpui2::test(iterations = 10)] + #[gpui::test(iterations = 10)] async fn test_reconnection(cx: &mut TestAppContext) { let user_id = 5; let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); @@ -1422,7 +1422,7 @@ mod tests { assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token } - #[gpui2::test(iterations = 10)] + #[gpui::test(iterations = 10)] async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) { let user_id = 5; let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); @@ -1490,7 +1490,7 @@ mod tests { )); } - #[gpui2::test(iterations = 10)] + #[gpui::test(iterations = 10)] async fn test_authenticating_more_than_once( cx: &mut TestAppContext, executor: BackgroundExecutor, @@ -1541,7 +1541,7 @@ mod tests { assert_eq!(decode_worktree_url("not://the-right-format"), None); } - #[gpui2::test] + #[gpui::test] async fn test_subscribing_to_entity(cx: &mut TestAppContext) { let user_id = 5; let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); @@ -1594,7 +1594,7 @@ mod tests { done_rx2.next().await.unwrap(); } - #[gpui2::test] + #[gpui::test] async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) { let user_id = 5; let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); @@ -1622,7 +1622,7 @@ mod tests { done_rx2.next().await.unwrap(); } - #[gpui2::test] + #[gpui::test] async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) { let user_id = 5; let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); diff --git a/crates/client2/src/telemetry.rs b/crates/client2/src/telemetry.rs index 0ef5f0d140..3723f7b906 100644 --- a/crates/client2/src/telemetry.rs +++ b/crates/client2/src/telemetry.rs @@ -1,9 +1,9 @@ use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; -use gpui2::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; +use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; -use settings2::Settings; +use settings::Settings; use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; use sysinfo::{ CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt, diff --git a/crates/client2/src/test.rs b/crates/client2/src/test.rs index 61f94580c3..5462799103 100644 --- a/crates/client2/src/test.rs +++ b/crates/client2/src/test.rs @@ -1,9 +1,9 @@ use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore}; use anyhow::{anyhow, Result}; use futures::{stream::BoxStream, StreamExt}; -use gpui2::{BackgroundExecutor, Context, Model, TestAppContext}; +use gpui::{BackgroundExecutor, Context, Model, TestAppContext}; use parking_lot::Mutex; -use rpc2::{ +use rpc::{ proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse}, ConnectionId, Peer, Receipt, TypedEnvelope, }; diff --git a/crates/client2/src/user.rs b/crates/client2/src/user.rs index 2a8cf34af4..baf3a19dad 100644 --- a/crates/client2/src/user.rs +++ b/crates/client2/src/user.rs @@ -1,11 +1,11 @@ use super::{proto, Client, Status, TypedEnvelope}; use anyhow::{anyhow, Context, Result}; use collections::{hash_map::Entry, HashMap, HashSet}; -use feature_flags2::FeatureFlagAppExt; +use feature_flags::FeatureFlagAppExt; use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; -use gpui2::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task}; +use gpui::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task}; use postage::{sink::Sink, watch}; -use rpc2::proto::{RequestMessage, UsersResponse}; +use rpc::proto::{RequestMessage, UsersResponse}; use std::sync::{Arc, Weak}; use text::ReplicaId; use util::http::HttpClient; diff --git a/crates/copilot2/Cargo.toml b/crates/copilot2/Cargo.toml index f83824d808..2021194607 100644 --- a/crates/copilot2/Cargo.toml +++ b/crates/copilot2/Cargo.toml @@ -11,21 +11,21 @@ doctest = false [features] test-support = [ "collections/test-support", - "gpui2/test-support", - "language2/test-support", - "lsp2/test-support", - "settings2/test-support", + "gpui/test-support", + "language/test-support", + "lsp/test-support", + "settings/test-support", "util/test-support", ] [dependencies] collections = { path = "../collections" } context_menu = { path = "../context_menu" } -gpui2 = { path = "../gpui2" } -language2 = { path = "../language2" } -settings2 = { path = "../settings2" } +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +settings = { package = "settings2", path = "../settings2" } theme = { path = "../theme" } -lsp2 = { path = "../lsp2" } +lsp = { package = "lsp2", path = "../lsp2" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } @@ -42,9 +42,9 @@ parking_lot.workspace = true clock = { path = "../clock" } collections = { path = "../collections", features = ["test-support"] } fs = { path = "../fs", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -language2 = { path = "../language2", features = ["test-support"] } -lsp2 = { path = "../lsp2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +language = { package = "language2", path = "../language2", features = ["test-support"] } +lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } -settings2 = { path = "../settings2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 3b059775cd..6b1190a5bf 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -6,20 +6,20 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use collections::{HashMap, HashSet}; use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt}; -use gpui2::{ +use gpui::{ AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext, Task, WeakModel, }; -use language2::{ +use language::{ language_settings::{all_language_settings, language_settings}, point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, LanguageServerName, PointUtf16, ToPointUtf16, }; -use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use parking_lot::Mutex; use request::StatusNotification; -use settings2::SettingsStore; +use settings::SettingsStore; use smol::{fs, io::BufReader, stream::StreamExt}; use std::{ ffi::OsString, @@ -172,11 +172,11 @@ impl Status { } struct RegisteredBuffer { - uri: lsp2::Url, + uri: lsp::Url, language_id: String, snapshot: BufferSnapshot, snapshot_version: i32, - _subscriptions: [gpui2::Subscription; 2], + _subscriptions: [gpui::Subscription; 2], pending_buffer_change: Task>, } @@ -220,8 +220,8 @@ impl RegisteredBuffer { let new_text = new_snapshot .text_for_range(edit.new.start.1..edit.new.end.1) .collect(); - lsp2::TextDocumentContentChangeEvent { - range: Some(lsp2::Range::new( + lsp::TextDocumentContentChangeEvent { + range: Some(lsp::Range::new( point_to_lsp(edit_start), point_to_lsp(edit_end), )), @@ -243,9 +243,9 @@ impl RegisteredBuffer { buffer.snapshot = new_snapshot; server .lsp - .notify::( - lsp2::DidChangeTextDocumentParams { - text_document: lsp2::VersionedTextDocumentIdentifier::new( + .notify::( + lsp::DidChangeTextDocumentParams { + text_document: lsp::VersionedTextDocumentIdentifier::new( buffer.uri.clone(), buffer.snapshot_version, ), @@ -280,7 +280,7 @@ pub struct Copilot { server: CopilotServer, buffers: HashSet>, server_id: LanguageServerId, - _subscription: gpui2::Subscription, + _subscription: gpui::Subscription, } pub enum Event { @@ -608,13 +608,13 @@ impl Copilot { registered_buffers .entry(buffer.entity_id()) .or_insert_with(|| { - let uri: lsp2::Url = uri_for_buffer(buffer, cx); + let uri: lsp::Url = uri_for_buffer(buffer, cx); let language_id = id_for_language(buffer.read(cx).language()); let snapshot = buffer.read(cx).snapshot(); server - .notify::( - lsp2::DidOpenTextDocumentParams { - text_document: lsp2::TextDocumentItem { + .notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem { uri: uri.clone(), language_id: language_id.clone(), version: 0, @@ -647,29 +647,29 @@ impl Copilot { fn handle_buffer_event( &mut self, buffer: Model, - event: &language2::Event, + event: &language::Event, cx: &mut ModelContext, ) -> Result<()> { if let Ok(server) = self.server.as_running() { if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id()) { match event { - language2::Event::Edited => { + language::Event::Edited => { let _ = registered_buffer.report_changes(&buffer, cx); } - language2::Event::Saved => { + language::Event::Saved => { server .lsp - .notify::( - lsp2::DidSaveTextDocumentParams { - text_document: lsp2::TextDocumentIdentifier::new( + .notify::( + lsp::DidSaveTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new( registered_buffer.uri.clone(), ), text: None, }, )?; } - language2::Event::FileHandleChanged | language2::Event::LanguageChanged => { + language::Event::FileHandleChanged | language::Event::LanguageChanged => { let new_language_id = id_for_language(buffer.read(cx).language()); let new_uri = uri_for_buffer(&buffer, cx); if new_uri != registered_buffer.uri @@ -679,16 +679,16 @@ impl Copilot { registered_buffer.language_id = new_language_id; server .lsp - .notify::( - lsp2::DidCloseTextDocumentParams { - text_document: lsp2::TextDocumentIdentifier::new(old_uri), + .notify::( + lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(old_uri), }, )?; server .lsp - .notify::( - lsp2::DidOpenTextDocumentParams { - text_document: lsp2::TextDocumentItem::new( + .notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( registered_buffer.uri.clone(), registered_buffer.language_id.clone(), registered_buffer.snapshot_version, @@ -711,9 +711,9 @@ impl Copilot { if let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) { server .lsp - .notify::( - lsp2::DidCloseTextDocumentParams { - text_document: lsp2::TextDocumentIdentifier::new(buffer.uri), + .notify::( + lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(buffer.uri), }, ) .log_err(); @@ -798,7 +798,7 @@ impl Copilot { ) -> Task>> where R: 'static - + lsp2::request::Request< + + lsp::request::Request< Params = request::GetCompletionsParams, Result = request::GetCompletionsResult, >, @@ -926,9 +926,9 @@ fn id_for_language(language: Option<&Arc>) -> String { } } -fn uri_for_buffer(buffer: &Model, cx: &AppContext) -> lsp2::Url { +fn uri_for_buffer(buffer: &Model, cx: &AppContext) -> lsp::Url { if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) { - lsp2::Url::from_file_path(file.abs_path(cx)).unwrap() + lsp::Url::from_file_path(file.abs_path(cx)).unwrap() } else { format!("buffer://{}", buffer.entity_id()).parse().unwrap() } diff --git a/crates/copilot2/src/request.rs b/crates/copilot2/src/request.rs index fee92051dc..0f9a478b91 100644 --- a/crates/copilot2/src/request.rs +++ b/crates/copilot2/src/request.rs @@ -8,7 +8,7 @@ pub struct CheckStatusParams { pub local_checks_only: bool, } -impl lsp2::request::Request for CheckStatus { +impl lsp::request::Request for CheckStatus { type Params = CheckStatusParams; type Result = SignInStatus; const METHOD: &'static str = "checkStatus"; @@ -33,7 +33,7 @@ pub struct PromptUserDeviceFlow { pub verification_uri: String, } -impl lsp2::request::Request for SignInInitiate { +impl lsp::request::Request for SignInInitiate { type Params = SignInInitiateParams; type Result = SignInInitiateResult; const METHOD: &'static str = "signInInitiate"; @@ -66,7 +66,7 @@ pub enum SignInStatus { NotSignedIn, } -impl lsp2::request::Request for SignInConfirm { +impl lsp::request::Request for SignInConfirm { type Params = SignInConfirmParams; type Result = SignInStatus; const METHOD: &'static str = "signInConfirm"; @@ -82,7 +82,7 @@ pub struct SignOutParams {} #[serde(rename_all = "camelCase")] pub struct SignOutResult {} -impl lsp2::request::Request for SignOut { +impl lsp::request::Request for SignOut { type Params = SignOutParams; type Result = SignOutResult; const METHOD: &'static str = "signOut"; @@ -102,9 +102,9 @@ pub struct GetCompletionsDocument { pub tab_size: u32, pub indent_size: u32, pub insert_spaces: bool, - pub uri: lsp2::Url, + pub uri: lsp::Url, pub relative_path: String, - pub position: lsp2::Position, + pub position: lsp::Position, pub version: usize, } @@ -118,13 +118,13 @@ pub struct GetCompletionsResult { #[serde(rename_all = "camelCase")] pub struct Completion { pub text: String, - pub position: lsp2::Position, + pub position: lsp::Position, pub uuid: String, - pub range: lsp2::Range, + pub range: lsp::Range, pub display_text: String, } -impl lsp2::request::Request for GetCompletions { +impl lsp::request::Request for GetCompletions { type Params = GetCompletionsParams; type Result = GetCompletionsResult; const METHOD: &'static str = "getCompletions"; @@ -132,7 +132,7 @@ impl lsp2::request::Request for GetCompletions { pub enum GetCompletionsCycling {} -impl lsp2::request::Request for GetCompletionsCycling { +impl lsp::request::Request for GetCompletionsCycling { type Params = GetCompletionsParams; type Result = GetCompletionsResult; const METHOD: &'static str = "getCompletionsCycling"; @@ -149,7 +149,7 @@ pub struct LogMessageParams { pub extra: Vec, } -impl lsp2::notification::Notification for LogMessage { +impl lsp::notification::Notification for LogMessage { type Params = LogMessageParams; const METHOD: &'static str = "LogMessage"; } @@ -162,7 +162,7 @@ pub struct StatusNotificationParams { pub status: String, // One of Normal/InProgress } -impl lsp2::notification::Notification for StatusNotification { +impl lsp::notification::Notification for StatusNotification { type Params = StatusNotificationParams; const METHOD: &'static str = "statusNotification"; } @@ -176,7 +176,7 @@ pub struct SetEditorInfoParams { pub editor_plugin_info: EditorPluginInfo, } -impl lsp2::request::Request for SetEditorInfo { +impl lsp::request::Request for SetEditorInfo { type Params = SetEditorInfoParams; type Result = String; const METHOD: &'static str = "setEditorInfo"; @@ -204,7 +204,7 @@ pub struct NotifyAcceptedParams { pub uuid: String, } -impl lsp2::request::Request for NotifyAccepted { +impl lsp::request::Request for NotifyAccepted { type Params = NotifyAcceptedParams; type Result = String; const METHOD: &'static str = "notifyAccepted"; @@ -218,7 +218,7 @@ pub struct NotifyRejectedParams { pub uuids: Vec, } -impl lsp2::request::Request for NotifyRejected { +impl lsp::request::Request for NotifyRejected { type Params = NotifyRejectedParams; type Result = String; const METHOD: &'static str = "notifyRejected"; diff --git a/crates/db2/Cargo.toml b/crates/db2/Cargo.toml index 6ef8ec0874..c73e6314c5 100644 --- a/crates/db2/Cargo.toml +++ b/crates/db2/Cargo.toml @@ -13,7 +13,7 @@ test-support = [] [dependencies] collections = { path = "../collections" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } sqlez = { path = "../sqlez" } sqlez_macros = { path = "../sqlez_macros" } util = { path = "../util" } @@ -28,6 +28,6 @@ serde_derive.workspace = true smol.workspace = true [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } env_logger.workspace = true tempdir.workspace = true diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs index e052d59d12..573845aa2e 100644 --- a/crates/db2/src/db2.rs +++ b/crates/db2/src/db2.rs @@ -4,7 +4,7 @@ pub mod query; // Re-export pub use anyhow; use anyhow::Context; -use gpui2::AppContext; +use gpui::AppContext; pub use indoc::indoc; pub use lazy_static; pub use smol; @@ -201,7 +201,7 @@ mod tests { use crate::open_db; // Test bad migration panics - #[gpui2::test] + #[gpui::test] #[should_panic] async fn test_bad_migration_panics() { enum BadDB {} @@ -225,8 +225,8 @@ mod tests { } /// Test that DB exists but corrupted (causing recreate) - #[gpui2::test] - async fn test_db_corruption(cx: &mut gpui2::TestAppContext) { + #[gpui::test] + async fn test_db_corruption(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); enum CorruptedDB {} @@ -269,8 +269,8 @@ mod tests { } /// Test that DB exists but corrupted (causing recreate) - #[gpui2::test(iterations = 30)] - async fn test_simultaneous_db_corruption(cx: &mut gpui2::TestAppContext) { + #[gpui::test(iterations = 30)] + async fn test_simultaneous_db_corruption(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); enum CorruptedDB {} diff --git a/crates/db2/src/kvp.rs b/crates/db2/src/kvp.rs index b4445e3586..0b0cdd9aa1 100644 --- a/crates/db2/src/kvp.rs +++ b/crates/db2/src/kvp.rs @@ -35,7 +35,7 @@ impl KeyValueStore { mod tests { use crate::kvp::KeyValueStore; - #[gpui2::test] + #[gpui::test] async fn test_kvp() { let db = KeyValueStore(crate::open_test_db("test_kvp").await); diff --git a/crates/feature_flags2/Cargo.toml b/crates/feature_flags2/Cargo.toml index ad77330ac3..8ae39b31db 100644 --- a/crates/feature_flags2/Cargo.toml +++ b/crates/feature_flags2/Cargo.toml @@ -8,5 +8,5 @@ publish = false path = "src/feature_flags2.rs" [dependencies] -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } anyhow.workspace = true diff --git a/crates/feature_flags2/src/feature_flags2.rs b/crates/feature_flags2/src/feature_flags2.rs index 446a2867e5..23167796ec 100644 --- a/crates/feature_flags2/src/feature_flags2.rs +++ b/crates/feature_flags2/src/feature_flags2.rs @@ -1,4 +1,4 @@ -use gpui2::{AppContext, Subscription, ViewContext}; +use gpui::{AppContext, Subscription, ViewContext}; #[derive(Default)] struct FeatureFlags { diff --git a/crates/fs2/Cargo.toml b/crates/fs2/Cargo.toml index 36f4e9c9c9..636def05ec 100644 --- a/crates/fs2/Cargo.toml +++ b/crates/fs2/Cargo.toml @@ -31,10 +31,10 @@ log.workspace = true libc = "0.2" time.workspace = true -gpui2 = { path = "../gpui2", optional = true} +gpui = { package = "gpui2", path = "../gpui2", optional = true} [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } [features] -test-support = ["gpui2/test-support"] +test-support = ["gpui/test-support"] diff --git a/crates/fs2/src/fs2.rs b/crates/fs2/src/fs2.rs index 82b5aead07..350a33b208 100644 --- a/crates/fs2/src/fs2.rs +++ b/crates/fs2/src/fs2.rs @@ -288,7 +288,7 @@ impl Fs for RealFs { pub struct FakeFs { // Use an unfair lock to ensure tests are deterministic. state: Mutex, - executor: gpui2::BackgroundExecutor, + executor: gpui::BackgroundExecutor, } #[cfg(any(test, feature = "test-support"))] @@ -434,7 +434,7 @@ lazy_static::lazy_static! { #[cfg(any(test, feature = "test-support"))] impl FakeFs { - pub fn new(executor: gpui2::BackgroundExecutor) -> Arc { + pub fn new(executor: gpui::BackgroundExecutor) -> Arc { Arc::new(Self { executor, state: Mutex::new(FakeFsState { @@ -1222,10 +1222,10 @@ pub fn copy_recursive<'a>( #[cfg(test)] mod tests { use super::*; - use gpui2::BackgroundExecutor; + use gpui::BackgroundExecutor; use serde_json::json; - #[gpui2::test] + #[gpui::test] async fn test_fake_fs(executor: BackgroundExecutor) { let fs = FakeFs::new(executor.clone()); fs.insert_tree( diff --git a/crates/fuzzy2/Cargo.toml b/crates/fuzzy2/Cargo.toml index 5b92a27a27..a112554a39 100644 --- a/crates/fuzzy2/Cargo.toml +++ b/crates/fuzzy2/Cargo.toml @@ -9,5 +9,5 @@ path = "src/fuzzy2.rs" doctest = false [dependencies] -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } diff --git a/crates/fuzzy2/src/paths.rs b/crates/fuzzy2/src/paths.rs index 4990b9e5b5..e982195158 100644 --- a/crates/fuzzy2/src/paths.rs +++ b/crates/fuzzy2/src/paths.rs @@ -1,4 +1,4 @@ -use gpui2::BackgroundExecutor; +use gpui::BackgroundExecutor; use std::{ borrow::Cow, cmp::{self, Ordering}, diff --git a/crates/fuzzy2/src/strings.rs b/crates/fuzzy2/src/strings.rs index 7c71496a13..085362dd2c 100644 --- a/crates/fuzzy2/src/strings.rs +++ b/crates/fuzzy2/src/strings.rs @@ -2,7 +2,7 @@ use crate::{ matcher::{Match, MatchCandidate, Matcher}, CharBag, }; -use gpui2::BackgroundExecutor; +use gpui::BackgroundExecutor; use std::{ borrow::Cow, cmp::{self, Ordering}, diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index f7e45a90f9..acaaee597b 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -90,7 +90,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { continue; } Some("BackgroundExecutor") => { - inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new( + inner_fn_args.extend(quote!(gpui::BackgroundExecutor::new( std::sync::Arc::new(dispatcher.clone()), ),)); continue; @@ -105,7 +105,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( - let mut #cx_varname = gpui2::TestAppContext::new( + let mut #cx_varname = gpui::TestAppContext::new( dispatcher.clone() ); )); @@ -130,11 +130,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { fn #outer_fn_name() { #inner_fn - gpui2::run_test( + gpui::run_test( #num_iterations as u64, #max_retries, &mut |dispatcher, _seed| { - let executor = gpui2::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone())); + let executor = gpui::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone())); #cx_vars executor.block_test(#inner_fn_name(#inner_fn_args)); #cx_teardowns @@ -167,7 +167,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); let cx_varname_lock = format_ident!("cx_{}_lock", ix); cx_vars.extend(quote!( - let mut #cx_varname = gpui2::TestAppContext::new( + let mut #cx_varname = gpui::TestAppContext::new( dispatcher.clone() ); let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); @@ -182,7 +182,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { Some("TestAppContext") => { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( - let mut #cx_varname = gpui2::TestAppContext::new( + let mut #cx_varname = gpui::TestAppContext::new( dispatcher.clone() ); )); @@ -209,7 +209,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { fn #outer_fn_name() { #inner_fn - gpui2::run_test( + gpui::run_test( #num_iterations as u64, #max_retries, &mut |dispatcher, _seed| { diff --git a/crates/install_cli2/Cargo.toml b/crates/install_cli2/Cargo.toml index 0dd1b907fd..3310e7fbc8 100644 --- a/crates/install_cli2/Cargo.toml +++ b/crates/install_cli2/Cargo.toml @@ -14,5 +14,5 @@ test-support = [] smol.workspace = true anyhow.workspace = true log.workspace = true -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } diff --git a/crates/install_cli2/src/install_cli2.rs b/crates/install_cli2/src/install_cli2.rs index e24a48ef07..7938d60210 100644 --- a/crates/install_cli2/src/install_cli2.rs +++ b/crates/install_cli2/src/install_cli2.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use gpui2::AsyncAppContext; +use gpui::AsyncAppContext; use std::path::Path; use util::ResultExt; diff --git a/crates/journal2/Cargo.toml b/crates/journal2/Cargo.toml index 8da2f51a62..f43d90fc85 100644 --- a/crates/journal2/Cargo.toml +++ b/crates/journal2/Cargo.toml @@ -10,7 +10,7 @@ doctest = false [dependencies] editor = { path = "../editor" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } workspace = { path = "../workspace" } settings2 = { path = "../settings2" } diff --git a/crates/journal2/src/journal2.rs b/crates/journal2/src/journal2.rs index d875cb3834..fa6e05cca7 100644 --- a/crates/journal2/src/journal2.rs +++ b/crates/journal2/src/journal2.rs @@ -1,6 +1,6 @@ use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; -use gpui2::AppContext; +use gpui::AppContext; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings2::Settings; diff --git a/crates/language2/Cargo.toml b/crates/language2/Cargo.toml index 1e49d0890f..4fca16bcb5 100644 --- a/crates/language2/Cargo.toml +++ b/crates/language2/Cargo.toml @@ -11,28 +11,28 @@ doctest = false [features] test-support = [ "rand", - "client2/test-support", + "client/test-support", "collections/test-support", - "lsp2/test-support", + "lsp/test-support", "text/test-support", "tree-sitter-rust", "tree-sitter-typescript", - "settings2/test-support", + "settings/test-support", "util/test-support", ] [dependencies] clock = { path = "../clock" } collections = { path = "../collections" } -fuzzy2 = { path = "../fuzzy2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } git = { path = "../git" } -gpui2 = { path = "../gpui2" } -lsp2 = { path = "../lsp2" } -rpc2 = { path = "../rpc2" } -settings2 = { path = "../settings2" } +gpui = { package = "gpui2", path = "../gpui2" } +lsp = { package = "lsp2", path = "../lsp2" } +rpc = { package = "rpc2", path = "../rpc2" } +settings = { package = "settings2", path = "../settings2" } sum_tree = { path = "../sum_tree" } text = { path = "../text" } -theme2 = { path = "../theme2" } +theme = { package = "theme2", path = "../theme2" } util = { path = "../util" } anyhow.workspace = true @@ -60,12 +60,12 @@ tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } [dev-dependencies] -client2 = { path = "../client2", features = ["test-support"] } +client = { package = "client2", path = "../client2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -lsp2 = { path = "../lsp2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] } text = { path = "../text", features = ["test-support"] } -settings2 = { path = "../settings2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index 3999f275f2..36c1f39e1c 100644 --- a/crates/language2/src/buffer.rs +++ b/crates/language2/src/buffer.rs @@ -16,8 +16,8 @@ use crate::{ use anyhow::{anyhow, Result}; pub use clock::ReplicaId; use futures::FutureExt as _; -use gpui2::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task}; -use lsp2::LanguageServerId; +use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task}; +use lsp::LanguageServerId; use parking_lot::Mutex; use similar::{ChangeTag, TextDiff}; use smallvec::SmallVec; @@ -40,7 +40,7 @@ use std::{ use sum_tree::TreeMap; use text::operation_queue::OperationQueue; pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, *}; -use theme2::SyntaxTheme; +use theme::SyntaxTheme; #[cfg(any(test, feature = "test-support"))] use util::RandomCharIter; use util::{RangeExt, TryFutureExt as _}; @@ -48,7 +48,7 @@ use util::{RangeExt, TryFutureExt as _}; #[cfg(any(test, feature = "test-support"))] pub use {tree_sitter_rust, tree_sitter_typescript}; -pub use lsp2::DiagnosticSeverity; +pub use lsp::DiagnosticSeverity; pub struct Buffer { text: TextBuffer, @@ -149,14 +149,14 @@ pub struct Completion { pub new_text: String, pub label: CodeLabel, pub server_id: LanguageServerId, - pub lsp_completion: lsp2::CompletionItem, + pub lsp_completion: lsp::CompletionItem, } #[derive(Clone, Debug)] pub struct CodeAction { pub server_id: LanguageServerId, pub range: Range, - pub lsp_action: lsp2::CodeAction, + pub lsp_action: lsp::CodeAction, } #[derive(Clone, Debug, PartialEq)] @@ -226,7 +226,7 @@ pub trait File: Send + Sync { fn as_any(&self) -> &dyn Any; - fn to_proto(&self) -> rpc2::proto::File; + fn to_proto(&self) -> rpc::proto::File; } pub trait LocalFile: File { @@ -375,7 +375,7 @@ impl Buffer { file, ); this.text.set_line_ending(proto::deserialize_line_ending( - rpc2::proto::LineEnding::from_i32(message.line_ending) + rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); this.saved_version = proto::deserialize_version(&message.saved_version); @@ -3003,14 +3003,14 @@ impl IndentSize { impl Completion { pub fn sort_key(&self) -> (usize, &str) { let kind_key = match self.lsp_completion.kind { - Some(lsp2::CompletionItemKind::VARIABLE) => 0, + Some(lsp::CompletionItemKind::VARIABLE) => 0, _ => 1, }; (kind_key, &self.label.text[self.label.filter_range.clone()]) } pub fn is_snippet(&self) -> bool { - self.lsp_completion.insert_text_format == Some(lsp2::InsertTextFormat::SNIPPET) + self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) } } diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index 16306fe2ce..c0bd068973 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/crates/language2/src/buffer_tests.rs @@ -5,13 +5,13 @@ use crate::language_settings::{ use crate::Buffer; use clock::ReplicaId; use collections::BTreeMap; -use gpui2::{AppContext, Model}; -use gpui2::{Context, TestAppContext}; +use gpui::{AppContext, Model}; +use gpui::{Context, TestAppContext}; use indoc::indoc; use proto::deserialize_operation; use rand::prelude::*; use regex::RegexBuilder; -use settings2::SettingsStore; +use settings::SettingsStore; use std::{ env, ops::Range, @@ -38,8 +38,8 @@ fn init_logger() { } } -#[gpui2::test] -fn test_line_endings(cx: &mut gpui2::AppContext) { +#[gpui::test] +fn test_line_endings(cx: &mut gpui::AppContext) { init_settings(cx, |_| {}); cx.build_model(|cx| { @@ -63,7 +63,7 @@ fn test_line_endings(cx: &mut gpui2::AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_select_language() { let registry = Arc::new(LanguageRegistry::test()); registry.add(Arc::new(Language::new( @@ -132,8 +132,8 @@ fn test_select_language() { ); } -#[gpui2::test] -fn test_edit_events(cx: &mut gpui2::AppContext) { +#[gpui::test] +fn test_edit_events(cx: &mut gpui::AppContext) { let mut now = Instant::now(); let buffer_1_events = Arc::new(Mutex::new(Vec::new())); let buffer_2_events = Arc::new(Mutex::new(Vec::new())); @@ -215,7 +215,7 @@ fn test_edit_events(cx: &mut gpui2::AppContext) { ); } -#[gpui2::test] +#[gpui::test] async fn test_apply_diff(cx: &mut TestAppContext) { let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n"; let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); @@ -238,8 +238,8 @@ async fn test_apply_diff(cx: &mut TestAppContext) { }); } -#[gpui2::test(iterations = 10)] -async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) { +#[gpui::test(iterations = 10)] +async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) { let text = [ "zero", // "one ", // 2 trailing spaces @@ -311,8 +311,8 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test] -async fn test_reparse(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_reparse(cx: &mut gpui::TestAppContext) { let text = "fn a() {}"; let buffer = cx.build_model(|cx| { Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx) @@ -440,8 +440,8 @@ async fn test_reparse(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_resetting_language(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_resetting_language(cx: &mut gpui::TestAppContext) { let buffer = cx.build_model(|cx| { let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx); @@ -463,8 +463,8 @@ async fn test_resetting_language(cx: &mut gpui2::TestAppContext) { assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))"); } -#[gpui2::test] -async fn test_outline(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_outline(cx: &mut gpui::TestAppContext) { let text = r#" struct Person { name: String, @@ -556,7 +556,7 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) { async fn search<'a>( outline: &'a Outline, query: &'a str, - cx: &'a gpui2::TestAppContext, + cx: &'a gpui::TestAppContext, ) -> Vec<(&'a str, Vec)> { let matches = cx .update(|cx| outline.search(query, cx.background_executor().clone())) @@ -568,8 +568,8 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) { } } -#[gpui2::test] -async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) { let text = r#" impl A for B< C @@ -595,8 +595,8 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) { let language = javascript_lang() .with_outline_query( r#" @@ -643,8 +643,8 @@ async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { let text = r#" impl Person { fn one() { @@ -731,7 +731,7 @@ async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) { } } -#[gpui2::test] +#[gpui::test] fn test_enclosing_bracket_ranges(cx: &mut AppContext) { let mut assert = |selection_text, range_markers| { assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx) @@ -847,7 +847,7 @@ fn test_enclosing_bracket_ranges(cx: &mut AppContext) { ); } -#[gpui2::test] +#[gpui::test] fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) { let mut assert = |selection_text, bracket_pair_texts| { assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx) @@ -879,7 +879,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: & ); } -#[gpui2::test] +#[gpui::test] fn test_range_for_syntax_ancestor(cx: &mut AppContext) { cx.build_model(|cx| { let text = "fn a() { b(|c| {}) }"; @@ -918,7 +918,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) { } } -#[gpui2::test] +#[gpui::test] fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -959,7 +959,7 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { init_settings(cx, |settings| { settings.defaults.hard_tabs = Some(true); @@ -1002,7 +1002,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1143,7 +1143,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC eprintln!("DONE"); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1205,7 +1205,7 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1262,7 +1262,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1280,7 +1280,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1322,7 +1322,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_block_mode(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1406,7 +1406,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1486,7 +1486,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1530,7 +1530,7 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_with_injected_languages(cx: &mut AppContext) { init_settings(cx, |settings| { settings.languages.extend([ @@ -1604,7 +1604,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { init_settings(cx, |settings| { settings.defaults.tab_size = Some(2.try_into().unwrap()); @@ -1649,7 +1649,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_language_scope_at_with_javascript(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1738,7 +1738,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_language_scope_at_with_rust(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1806,7 +1806,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) { }); } -#[gpui2::test] +#[gpui::test] fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { init_settings(cx, |_| {}); @@ -1854,8 +1854,8 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { }); } -#[gpui2::test] -fn test_serialization(cx: &mut gpui2::AppContext) { +#[gpui::test] +fn test_serialization(cx: &mut gpui::AppContext) { let mut now = Instant::now(); let buffer1 = cx.build_model(|cx| { @@ -1895,7 +1895,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) { assert_eq!(buffer2.read(cx).text(), "abcDF"); } -#[gpui2::test(iterations = 100)] +#[gpui::test(iterations = 100)] fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { let min_peers = env::var("MIN_PEERS") .map(|i| i.parse().expect("invalid `MIN_PEERS` variable")) @@ -2199,7 +2199,7 @@ fn test_contiguous_ranges() { ); } -#[gpui2::test(iterations = 500)] +#[gpui::test(iterations = 500)] fn test_trailing_whitespace_ranges(mut rng: StdRng) { // Generate a random multi-line string containing // some lines with trailing whitespace. @@ -2400,7 +2400,7 @@ fn javascript_lang() -> Language { .unwrap() } -fn get_tree_sexp(buffer: &Model, cx: &mut gpui2::TestAppContext) -> String { +fn get_tree_sexp(buffer: &Model, cx: &mut gpui::TestAppContext) -> String { buffer.update(cx, |buffer, _| { let snapshot = buffer.snapshot(); let layers = snapshot.syntax.layers(buffer.as_text_snapshot()); diff --git a/crates/language2/src/diagnostic_set.rs b/crates/language2/src/diagnostic_set.rs index 5247af285e..f269fce88d 100644 --- a/crates/language2/src/diagnostic_set.rs +++ b/crates/language2/src/diagnostic_set.rs @@ -1,6 +1,6 @@ use crate::Diagnostic; use collections::HashMap; -use lsp2::LanguageServerId; +use lsp::LanguageServerId; use std::{ cmp::{Ordering, Reverse}, iter, @@ -37,14 +37,14 @@ pub struct Summary { impl DiagnosticEntry { // Used to provide diagnostic context to lsp codeAction request - pub fn to_lsp_diagnostic_stub(&self) -> lsp2::Diagnostic { + pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic { let code = self .diagnostic .code .clone() - .map(lsp2::NumberOrString::String); + .map(lsp::NumberOrString::String); - lsp2::Diagnostic { + lsp::Diagnostic { code, severity: Some(self.diagnostic.severity), ..Default::default() diff --git a/crates/language2/src/highlight_map.rs b/crates/language2/src/highlight_map.rs index b394d0446e..aeeda546bf 100644 --- a/crates/language2/src/highlight_map.rs +++ b/crates/language2/src/highlight_map.rs @@ -1,6 +1,6 @@ -use gpui2::HighlightStyle; +use gpui::HighlightStyle; use std::sync::Arc; -use theme2::SyntaxTheme; +use theme::SyntaxTheme; #[derive(Clone, Debug)] pub struct HighlightMap(Arc<[HighlightId]>); @@ -79,7 +79,7 @@ impl Default for HighlightId { #[cfg(test)] mod tests { use super::*; - use gpui2::rgba; + use gpui::rgba; #[test] fn test_highlight_map() { diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index ea13dc185f..381284659b 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -17,10 +17,10 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt as _, }; -use gpui2::{AppContext, AsyncAppContext, BackgroundExecutor, Task}; +use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task}; pub use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp2::{CodeActionKind, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -42,7 +42,7 @@ use std::{ }, }; use syntax_map::SyntaxSnapshot; -use theme2::{SyntaxTheme, ThemeVariant}; +use theme::{SyntaxTheme, ThemeVariant}; use tree_sitter::{self, Query}; use unicase::UniCase; use util::{http::HttpClient, paths::PathExt}; @@ -51,7 +51,7 @@ use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture}; pub use buffer::Operation; pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; -pub use lsp2::LanguageServerId; +pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo}; pub use text::LineEnding; @@ -98,7 +98,7 @@ lazy_static! { } pub trait ToLspPosition { - fn to_lsp_position(self) -> lsp2::Position; + fn to_lsp_position(self) -> lsp::Position; } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -203,17 +203,17 @@ impl CachedLspAdapter { self.adapter.workspace_configuration(cx) } - pub fn process_diagnostics(&self, params: &mut lsp2::PublishDiagnosticsParams) { + pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { self.adapter.process_diagnostics(params) } - pub async fn process_completion(&self, completion_item: &mut lsp2::CompletionItem) { + pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) { self.adapter.process_completion(completion_item).await } pub async fn label_for_completion( &self, - completion_item: &lsp2::CompletionItem, + completion_item: &lsp::CompletionItem, language: &Arc, ) -> Option { self.adapter @@ -224,7 +224,7 @@ impl CachedLspAdapter { pub async fn label_for_symbol( &self, name: &str, - kind: lsp2::SymbolKind, + kind: lsp::SymbolKind, language: &Arc, ) -> Option { self.adapter.label_for_symbol(name, kind, language).await @@ -289,13 +289,13 @@ pub trait LspAdapter: 'static + Send + Sync { container_dir: PathBuf, ) -> Option; - fn process_diagnostics(&self, _: &mut lsp2::PublishDiagnosticsParams) {} + fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - async fn process_completion(&self, _: &mut lsp2::CompletionItem) {} + async fn process_completion(&self, _: &mut lsp::CompletionItem) {} async fn label_for_completion( &self, - _: &lsp2::CompletionItem, + _: &lsp::CompletionItem, _: &Arc, ) -> Option { None @@ -304,7 +304,7 @@ pub trait LspAdapter: 'static + Send + Sync { async fn label_for_symbol( &self, _: &str, - _: lsp2::SymbolKind, + _: lsp::SymbolKind, _: &Arc, ) -> Option { None @@ -476,8 +476,8 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D pub struct FakeLspAdapter { pub name: &'static str, pub initialization_options: Option, - pub capabilities: lsp2::ServerCapabilities, - pub initializer: Option>, + pub capabilities: lsp::ServerCapabilities, + pub initializer: Option>, pub disk_based_diagnostics_progress_token: Option, pub disk_based_diagnostics_sources: Vec, pub prettier_plugins: Vec<&'static str>, @@ -532,7 +532,7 @@ pub struct Language { #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( - mpsc::UnboundedSender, + mpsc::UnboundedSender, Arc, )>, } @@ -649,7 +649,7 @@ struct LanguageRegistryState { pub struct PendingLanguageServer { pub server_id: LanguageServerId, - pub task: Task>, + pub task: Task>, pub container_dir: Option>, } @@ -905,7 +905,7 @@ impl LanguageRegistry { if language.fake_adapter.is_some() { let task = cx.spawn(|cx| async move { let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap(); - let (server, mut fake_server) = lsp2::LanguageServer::fake( + let (server, mut fake_server) = lsp::LanguageServer::fake( fake_adapter.name.to_string(), fake_adapter.capabilities.clone(), cx.clone(), @@ -919,7 +919,7 @@ impl LanguageRegistry { cx.background_executor() .spawn(async move { if fake_server - .try_receive_notification::() + .try_receive_notification::() .await .is_some() { @@ -988,7 +988,7 @@ impl LanguageRegistry { task.await?; } - lsp2::LanguageServer::new( + lsp::LanguageServer::new( stderr_capture, server_id, binary, @@ -1471,7 +1471,7 @@ impl Language { pub async fn set_fake_lsp_adapter( &mut self, fake_lsp_adapter: Arc, - ) -> mpsc::UnboundedReceiver { + ) -> mpsc::UnboundedReceiver { let (servers_tx, servers_rx) = mpsc::unbounded(); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await; @@ -1501,7 +1501,7 @@ impl Language { None } - pub async fn process_completion(self: &Arc, completion: &mut lsp2::CompletionItem) { + pub async fn process_completion(self: &Arc, completion: &mut lsp::CompletionItem) { for adapter in &self.adapters { adapter.process_completion(completion).await; } @@ -1509,7 +1509,7 @@ impl Language { pub async fn label_for_completion( self: &Arc, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, ) -> Option { self.adapters .first() @@ -1521,7 +1521,7 @@ impl Language { pub async fn label_for_symbol( self: &Arc, name: &str, - kind: lsp2::SymbolKind, + kind: lsp::SymbolKind, ) -> Option { self.adapters .first() @@ -1745,7 +1745,7 @@ impl Default for FakeLspAdapter { fn default() -> Self { Self { name: "the-fake-language-server", - capabilities: lsp2::LanguageServer::full_capabilities(), + capabilities: lsp::LanguageServer::full_capabilities(), initializer: None, disk_based_diagnostics_progress_token: None, initialization_options: None, @@ -1794,7 +1794,7 @@ impl LspAdapter for Arc { unreachable!(); } - fn process_diagnostics(&self, _: &mut lsp2::PublishDiagnosticsParams) {} + fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn disk_based_diagnostic_sources(&self) -> Vec { self.disk_based_diagnostics_sources.clone() @@ -1824,22 +1824,22 @@ fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) } } -pub fn point_to_lsp(point: PointUtf16) -> lsp2::Position { - lsp2::Position::new(point.row, point.column) +pub fn point_to_lsp(point: PointUtf16) -> lsp::Position { + lsp::Position::new(point.row, point.column) } -pub fn point_from_lsp(point: lsp2::Position) -> Unclipped { +pub fn point_from_lsp(point: lsp::Position) -> Unclipped { Unclipped(PointUtf16::new(point.line, point.character)) } -pub fn range_to_lsp(range: Range) -> lsp2::Range { - lsp2::Range { +pub fn range_to_lsp(range: Range) -> lsp::Range { + lsp::Range { start: point_to_lsp(range.start), end: point_to_lsp(range.end), } } -pub fn range_from_lsp(range: lsp2::Range) -> Range> { +pub fn range_from_lsp(range: lsp::Range) -> Range> { let mut start = point_from_lsp(range.start); let mut end = point_from_lsp(range.end); if start > end { @@ -1851,9 +1851,9 @@ pub fn range_from_lsp(range: lsp2::Range) -> Range> { #[cfg(test)] mod tests { use super::*; - use gpui2::TestAppContext; + use gpui::TestAppContext; - #[gpui2::test(iterations = 10)] + #[gpui::test(iterations = 10)] async fn test_first_line_pattern(cx: &mut TestAppContext) { let mut languages = LanguageRegistry::test(); @@ -1891,7 +1891,7 @@ mod tests { ); } - #[gpui2::test(iterations = 10)] + #[gpui::test(iterations = 10)] async fn test_language_loading(cx: &mut TestAppContext) { let mut languages = LanguageRegistry::test(); languages.set_executor(cx.executor().clone()); diff --git a/crates/language2/src/language_settings.rs b/crates/language2/src/language_settings.rs index 4816e506db..49977f690c 100644 --- a/crates/language2/src/language_settings.rs +++ b/crates/language2/src/language_settings.rs @@ -2,13 +2,13 @@ use crate::{File, Language}; use anyhow::Result; use collections::{HashMap, HashSet}; use globset::GlobMatcher; -use gpui2::AppContext; +use gpui::AppContext; use schemars::{ schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, JsonSchema, }; use serde::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; use std::{num::NonZeroU32, path::Path, sync::Arc}; pub fn init(cx: &mut AppContext) { @@ -255,7 +255,7 @@ impl InlayHintKind { } } -impl settings2::Settings for AllLanguageSettings { +impl settings::Settings for AllLanguageSettings { const KEY: Option<&'static str> = None; type FileContent = AllLanguageSettingsContent; @@ -332,7 +332,7 @@ impl settings2::Settings for AllLanguageSettings { fn json_schema( generator: &mut schemars::gen::SchemaGenerator, - params: &settings2::SettingsJsonSchemaParams, + params: &settings::SettingsJsonSchemaParams, _: &AppContext, ) -> schemars::schema::RootSchema { let mut root_schema = generator.root_schema_for::(); diff --git a/crates/language2/src/outline.rs b/crates/language2/src/outline.rs index 94dfaa0e11..4bcbdcd27f 100644 --- a/crates/language2/src/outline.rs +++ b/crates/language2/src/outline.rs @@ -1,5 +1,5 @@ -use fuzzy2::{StringMatch, StringMatchCandidate}; -use gpui2::{BackgroundExecutor, HighlightStyle}; +use fuzzy::{StringMatch, StringMatchCandidate}; +use gpui::{BackgroundExecutor, HighlightStyle}; use std::ops::Range; #[derive(Debug)] @@ -61,7 +61,7 @@ impl Outline { let query = query.trim_start(); let is_path_query = query.contains(' '); let smart_case = query.chars().any(|c| c.is_uppercase()); - let mut matches = fuzzy2::match_strings( + let mut matches = fuzzy::match_strings( if is_path_query { &self.path_candidates } else { diff --git a/crates/language2/src/proto.rs b/crates/language2/src/proto.rs index f90bb94742..c4abe39d47 100644 --- a/crates/language2/src/proto.rs +++ b/crates/language2/src/proto.rs @@ -4,8 +4,8 @@ use crate::{ }; use anyhow::{anyhow, Result}; use clock::ReplicaId; -use lsp2::{DiagnosticSeverity, LanguageServerId}; -use rpc2::proto; +use lsp::{DiagnosticSeverity, LanguageServerId}; +use rpc::proto; use std::{ops::Range, sync::Arc}; use text::*; diff --git a/crates/language2/src/syntax_map/syntax_map_tests.rs b/crates/language2/src/syntax_map/syntax_map_tests.rs index 732ed7e936..bd50608122 100644 --- a/crates/language2/src/syntax_map/syntax_map_tests.rs +++ b/crates/language2/src/syntax_map/syntax_map_tests.rs @@ -78,7 +78,7 @@ fn test_splice_included_ranges() { } } -#[gpui2::test] +#[gpui::test] fn test_syntax_map_layers_for_range() { let registry = Arc::new(LanguageRegistry::test()); let language = Arc::new(rust_lang()); @@ -175,7 +175,7 @@ fn test_syntax_map_layers_for_range() { ); } -#[gpui2::test] +#[gpui::test] fn test_dynamic_language_injection() { let registry = Arc::new(LanguageRegistry::test()); let markdown = Arc::new(markdown_lang()); @@ -253,7 +253,7 @@ fn test_dynamic_language_injection() { assert!(!syntax_map.contains_unknown_injections()); } -#[gpui2::test] +#[gpui::test] fn test_typing_multiple_new_injections() { let (buffer, syntax_map) = test_edit_sequence( "Rust", @@ -282,7 +282,7 @@ fn test_typing_multiple_new_injections() { ); } -#[gpui2::test] +#[gpui::test] fn test_pasting_new_injection_line_between_others() { let (buffer, syntax_map) = test_edit_sequence( "Rust", @@ -329,7 +329,7 @@ fn test_pasting_new_injection_line_between_others() { ); } -#[gpui2::test] +#[gpui::test] fn test_joining_injections_with_child_injections() { let (buffer, syntax_map) = test_edit_sequence( "Rust", @@ -373,7 +373,7 @@ fn test_joining_injections_with_child_injections() { ); } -#[gpui2::test] +#[gpui::test] fn test_editing_edges_of_injection() { test_edit_sequence( "Rust", @@ -402,7 +402,7 @@ fn test_editing_edges_of_injection() { ); } -#[gpui2::test] +#[gpui::test] fn test_edits_preceding_and_intersecting_injection() { test_edit_sequence( "Rust", @@ -414,7 +414,7 @@ fn test_edits_preceding_and_intersecting_injection() { ); } -#[gpui2::test] +#[gpui::test] fn test_non_local_changes_create_injections() { test_edit_sequence( "Rust", @@ -433,7 +433,7 @@ fn test_non_local_changes_create_injections() { ); } -#[gpui2::test] +#[gpui::test] fn test_creating_many_injections_in_one_edit() { test_edit_sequence( "Rust", @@ -463,7 +463,7 @@ fn test_creating_many_injections_in_one_edit() { ); } -#[gpui2::test] +#[gpui::test] fn test_editing_across_injection_boundary() { test_edit_sequence( "Rust", @@ -491,7 +491,7 @@ fn test_editing_across_injection_boundary() { ); } -#[gpui2::test] +#[gpui::test] fn test_removing_injection_by_replacing_across_boundary() { test_edit_sequence( "Rust", @@ -517,7 +517,7 @@ fn test_removing_injection_by_replacing_across_boundary() { ); } -#[gpui2::test] +#[gpui::test] fn test_combined_injections_simple() { let (buffer, syntax_map) = test_edit_sequence( "ERB", @@ -564,7 +564,7 @@ fn test_combined_injections_simple() { ); } -#[gpui2::test] +#[gpui::test] fn test_combined_injections_empty_ranges() { test_edit_sequence( "ERB", @@ -582,7 +582,7 @@ fn test_combined_injections_empty_ranges() { ); } -#[gpui2::test] +#[gpui::test] fn test_combined_injections_edit_edges_of_ranges() { let (buffer, syntax_map) = test_edit_sequence( "ERB", @@ -613,7 +613,7 @@ fn test_combined_injections_edit_edges_of_ranges() { ); } -#[gpui2::test] +#[gpui::test] fn test_combined_injections_splitting_some_injections() { let (_buffer, _syntax_map) = test_edit_sequence( "ERB", @@ -638,7 +638,7 @@ fn test_combined_injections_splitting_some_injections() { ); } -#[gpui2::test] +#[gpui::test] fn test_combined_injections_editing_after_last_injection() { test_edit_sequence( "ERB", @@ -658,7 +658,7 @@ fn test_combined_injections_editing_after_last_injection() { ); } -#[gpui2::test] +#[gpui::test] fn test_combined_injections_inside_injections() { let (buffer, syntax_map) = test_edit_sequence( "Markdown", @@ -734,7 +734,7 @@ fn test_combined_injections_inside_injections() { ); } -#[gpui2::test] +#[gpui::test] fn test_empty_combined_injections_inside_injections() { let (buffer, syntax_map) = test_edit_sequence( "Markdown", @@ -762,7 +762,7 @@ fn test_empty_combined_injections_inside_injections() { ); } -#[gpui2::test(iterations = 50)] +#[gpui::test(iterations = 50)] fn test_random_syntax_map_edits_rust_macros(rng: StdRng) { let text = r#" fn test_something() { @@ -788,7 +788,7 @@ fn test_random_syntax_map_edits_rust_macros(rng: StdRng) { test_random_edits(text, registry, language, rng); } -#[gpui2::test(iterations = 50)] +#[gpui::test(iterations = 50)] fn test_random_syntax_map_edits_with_erb(rng: StdRng) { let text = r#"
@@ -817,7 +817,7 @@ fn test_random_syntax_map_edits_with_erb(rng: StdRng) { test_random_edits(text, registry, language, rng); } -#[gpui2::test(iterations = 50)] +#[gpui::test(iterations = 50)] fn test_random_syntax_map_edits_with_heex(rng: StdRng) { let text = r#" defmodule TheModule do diff --git a/crates/live_kit_client2/Cargo.toml b/crates/live_kit_client2/Cargo.toml index b5b45a8d45..5adb711948 100644 --- a/crates/live_kit_client2/Cargo.toml +++ b/crates/live_kit_client2/Cargo.toml @@ -23,7 +23,7 @@ test-support = [ [dependencies] collections = { path = "../collections", optional = true } -gpui2 = { path = "../gpui2", optional = true } +gpui2 = { package = "gpui2", path = "../gpui2", optional = true } live_kit_server = { path = "../live_kit_server", optional = true } media = { path = "../media" } @@ -41,7 +41,7 @@ nanoid = { version ="0.4", optional = true} [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui2 = { package = "gpui2", path = "../gpui2", features = ["test-support"] } live_kit_server = { path = "../live_kit_server" } media = { path = "../media" } nanoid = "0.4" diff --git a/crates/lsp2/Cargo.toml b/crates/lsp2/Cargo.toml index a32dd2b6b2..12993eedb6 100644 --- a/crates/lsp2/Cargo.toml +++ b/crates/lsp2/Cargo.toml @@ -13,7 +13,7 @@ test-support = ["async-pipe"] [dependencies] collections = { path = "../collections" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } anyhow.workspace = true @@ -29,7 +29,7 @@ serde_json.workspace = true smol.workspace = true [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" } diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 8871c8eaef..356d029c58 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -5,7 +5,7 @@ pub use lsp_types::*; use anyhow::{anyhow, Context, Result}; use collections::HashMap; use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt}; -use gpui2::{AsyncAppContext, BackgroundExecutor, Task}; +use gpui::{AsyncAppContext, BackgroundExecutor, Task}; use parking_lot::Mutex; use postage::{barrier, prelude::Stream}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -1038,7 +1038,7 @@ impl FakeLanguageServer { where T: 'static + request::Request, T::Params: 'static + Send, - F: 'static + Send + FnMut(T::Params, gpui2::AsyncAppContext) -> Fut, + F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut, Fut: 'static + Send + Future>, { let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded(); @@ -1066,7 +1066,7 @@ impl FakeLanguageServer { where T: 'static + notification::Notification, T::Params: 'static + Send, - F: 'static + Send + FnMut(T::Params, gpui2::AsyncAppContext), + F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext), { let (handled_tx, handled_rx) = futures::channel::mpsc::unbounded(); self.server.remove_notification_handler::(); @@ -1110,7 +1110,7 @@ impl FakeLanguageServer { #[cfg(test)] mod tests { use super::*; - use gpui2::TestAppContext; + use gpui::TestAppContext; #[ctor::ctor] fn init_logger() { @@ -1119,7 +1119,7 @@ mod tests { } } - #[gpui2::test] + #[gpui::test] async fn test_fake(cx: &mut TestAppContext) { let (server, mut fake) = LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async()); diff --git a/crates/menu2/Cargo.toml b/crates/menu2/Cargo.toml index c366de6866..9bf61db82c 100644 --- a/crates/menu2/Cargo.toml +++ b/crates/menu2/Cargo.toml @@ -9,4 +9,4 @@ path = "src/menu2.rs" doctest = false [dependencies] -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } diff --git a/crates/multi_buffer2/Cargo.toml b/crates/multi_buffer2/Cargo.toml index 4c56bab9dc..a57ef29531 100644 --- a/crates/multi_buffer2/Cargo.toml +++ b/crates/multi_buffer2/Cargo.toml @@ -10,29 +10,29 @@ doctest = false [features] test-support = [ - "copilot2/test-support", + "copilot/test-support", "text/test-support", - "language2/test-support", - "gpui2/test-support", + "language/test-support", + "gpui/test-support", "util/test-support", "tree-sitter-rust", "tree-sitter-typescript" ] [dependencies] -client2 = { path = "../client2" } +client = { package = "client2", path = "../client2" } clock = { path = "../clock" } collections = { path = "../collections" } git = { path = "../git" } -gpui2 = { path = "../gpui2" } -language2 = { path = "../language2" } -lsp2 = { path = "../lsp2" } +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +lsp = { package = "lsp2", path = "../lsp2" } rich_text = { path = "../rich_text" } -settings2 = { path = "../settings2" } +settings = { package = "settings2", path = "../settings2" } snippet = { path = "../snippet" } sum_tree = { path = "../sum_tree" } text = { path = "../text" } -theme2 = { path = "../theme2" } +theme = { package = "theme2", path = "../theme2" } util = { path = "../util" } aho-corasick = "1.1" @@ -59,14 +59,14 @@ tree-sitter-html = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } [dev-dependencies] -copilot2 = { path = "../copilot2", features = ["test-support"] } +copilot = { package = "copilot2", path = "../copilot2", features = ["test-support"] } text = { path = "../text", features = ["test-support"] } -language2 = { path = "../language2", features = ["test-support"] } -lsp2 = { path = "../lsp2", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } +language = { package = "language2", path = "../language2", features = ["test-support"] } +lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } -project2 = { path = "../project2", features = ["test-support"] } -settings2 = { path = "../settings2", features = ["test-support"] } +project = { package = "project2", path = "../project2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true diff --git a/crates/multi_buffer2/src/anchor.rs b/crates/multi_buffer2/src/anchor.rs index fa65bfc800..39a8182da1 100644 --- a/crates/multi_buffer2/src/anchor.rs +++ b/crates/multi_buffer2/src/anchor.rs @@ -1,5 +1,5 @@ use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint}; -use language2::{OffsetUtf16, Point, TextDimension}; +use language::{OffsetUtf16, Point, TextDimension}; use std::{ cmp::Ordering, ops::{Range, Sub}, diff --git a/crates/multi_buffer2/src/multi_buffer2.rs b/crates/multi_buffer2/src/multi_buffer2.rs index b5a7ced517..df33f98b4b 100644 --- a/crates/multi_buffer2/src/multi_buffer2.rs +++ b/crates/multi_buffer2/src/multi_buffer2.rs @@ -6,9 +6,9 @@ use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; use futures::{channel::mpsc, SinkExt}; use git::diff::DiffHunk; -use gpui2::{AppContext, EventEmitter, Model, ModelContext}; -pub use language2::Completion; -use language2::{ +use gpui::{AppContext, EventEmitter, Model, ModelContext}; +pub use language::Completion; +use language::{ char_kind, language_settings::{language_settings, LanguageSettings}, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, @@ -35,11 +35,11 @@ use text::{ subscription::{Subscription, Topic}, Edit, TextSummary, }; -use theme2::SyntaxTheme; +use theme::SyntaxTheme; use util::post_inc; #[cfg(any(test, feature = "test-support"))] -use gpui2::Context; +use gpui::Context; const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; @@ -62,7 +62,7 @@ pub enum Event { ExcerptsAdded { buffer: Model, predecessor: ExcerptId, - excerpts: Vec<(ExcerptId, ExcerptRange)>, + excerpts: Vec<(ExcerptId, ExcerptRange)>, }, ExcerptsRemoved { ids: Vec, @@ -130,7 +130,7 @@ struct BufferState { last_file_update_count: usize, last_git_diff_update_count: usize, excerpts: Vec, - _subscriptions: [gpui2::Subscription; 2], + _subscriptions: [gpui::Subscription; 2], } #[derive(Clone, Default)] @@ -684,7 +684,7 @@ impl MultiBuffer { pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &mut ModelContext) where - T: IntoIterator, &'a language2::Transaction)>, + T: IntoIterator, &'a language::Transaction)>, { self.history .push_transaction(buffer_transactions, Instant::now(), cx); @@ -1383,7 +1383,7 @@ impl MultiBuffer { &self, position: T, cx: &AppContext, - ) -> Option<(Model, language2::Anchor)> { + ) -> Option<(Model, language::Anchor)> { let snapshot = self.read(cx); let anchor = snapshot.anchor_before(position); let buffer = self @@ -1398,25 +1398,25 @@ impl MultiBuffer { fn on_buffer_event( &mut self, _: Model, - event: &language2::Event, + event: &language::Event, cx: &mut ModelContext, ) { cx.emit(match event { - language2::Event::Edited => Event::Edited { + language::Event::Edited => Event::Edited { sigleton_buffer_edited: true, }, - language2::Event::DirtyChanged => Event::DirtyChanged, - language2::Event::Saved => Event::Saved, - language2::Event::FileHandleChanged => Event::FileHandleChanged, - language2::Event::Reloaded => Event::Reloaded, - language2::Event::DiffBaseChanged => Event::DiffBaseChanged, - language2::Event::LanguageChanged => Event::LanguageChanged, - language2::Event::Reparsed => Event::Reparsed, - language2::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, - language2::Event::Closed => Event::Closed, + language::Event::DirtyChanged => Event::DirtyChanged, + language::Event::Saved => Event::Saved, + language::Event::FileHandleChanged => Event::FileHandleChanged, + language::Event::Reloaded => Event::Reloaded, + language::Event::DiffBaseChanged => Event::DiffBaseChanged, + language::Event::LanguageChanged => Event::LanguageChanged, + language::Event::Reparsed => Event::Reparsed, + language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, + language::Event::Closed => Event::Closed, // - language2::Event::Operation(_) => return, + language::Event::Operation(_) => return, }); } @@ -1648,14 +1648,14 @@ impl MultiBuffer { #[cfg(any(test, feature = "test-support"))] impl MultiBuffer { - pub fn build_simple(text: &str, cx: &mut gpui2::AppContext) -> Model { + pub fn build_simple(text: &str, cx: &mut gpui::AppContext) -> Model { let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); cx.build_model(|cx| Self::singleton(buffer, cx)) } pub fn build_multi( excerpts: [(&str, Vec>); COUNT], - cx: &mut gpui2::AppContext, + cx: &mut gpui::AppContext, ) -> Model { let multi = cx.build_model(|_| Self::new(0)); for (text, ranges) in excerpts { @@ -1672,11 +1672,11 @@ impl MultiBuffer { multi } - pub fn build_from_buffer(buffer: Model, cx: &mut gpui2::AppContext) -> Model { + pub fn build_from_buffer(buffer: Model, cx: &mut gpui::AppContext) -> Model { cx.build_model(|cx| Self::singleton(buffer, cx)) } - pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui2::AppContext) -> Model { + pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> Model { cx.build_model(|cx| { let mut multibuffer = MultiBuffer::new(0); let mutation_count = rng.gen_range(1..=5); @@ -3409,7 +3409,7 @@ impl History { now: Instant, cx: &mut ModelContext, ) where - T: IntoIterator, &'a language2::Transaction)>, + T: IntoIterator, &'a language::Transaction)>, { assert_eq!(self.transaction_depth, 0); let transaction = Transaction { @@ -4135,15 +4135,15 @@ where mod tests { use super::*; use futures::StreamExt; - use gpui2::{AppContext, Context, TestAppContext}; - use language2::{Buffer, Rope}; + use gpui::{AppContext, Context, TestAppContext}; + use language::{Buffer, Rope}; use parking_lot::RwLock; use rand::prelude::*; - use settings2::SettingsStore; + use settings::SettingsStore; use std::env; use util::test::sample_text; - #[gpui2::test] + #[gpui::test] fn test_singleton(cx: &mut AppContext) { let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a'))); @@ -4171,7 +4171,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_remote(cx: &mut AppContext) { let host_buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a")); let guest_buffer = cx.build_model(|cx| { @@ -4183,7 +4183,7 @@ mod tests { buffer .apply_ops( ops.into_iter() - .map(|op| language2::proto::deserialize_operation(op).unwrap()), + .map(|op| language::proto::deserialize_operation(op).unwrap()), cx, ) .unwrap(); @@ -4202,7 +4202,7 @@ mod tests { assert_eq!(snapshot.text(), "abc"); } - #[gpui2::test] + #[gpui::test] fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) { let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a'))); @@ -4438,7 +4438,7 @@ mod tests { } } - #[gpui2::test] + #[gpui::test] fn test_excerpt_events(cx: &mut AppContext) { let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'a'))); @@ -4546,7 +4546,7 @@ mod tests { assert_eq!(*follower_edit_event_count.read(), 4); } - #[gpui2::test] + #[gpui::test] fn test_push_excerpts_with_context_lines(cx: &mut AppContext) { let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a'))); @@ -4583,7 +4583,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a'))); @@ -4620,7 +4620,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_empty_multibuffer(cx: &mut AppContext) { let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); @@ -4630,7 +4630,7 @@ mod tests { assert_eq!(snapshot.buffer_rows(1).collect::>(), &[]); } - #[gpui2::test] + #[gpui::test] fn test_singleton_multibuffer_anchors(cx: &mut AppContext) { let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); let multibuffer = cx.build_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); @@ -4650,7 +4650,7 @@ mod tests { assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6); } - #[gpui2::test] + #[gpui::test] fn test_multibuffer_anchors(cx: &mut AppContext) { let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi")); @@ -4708,7 +4708,7 @@ mod tests { assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14); } - #[gpui2::test] + #[gpui::test] fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) { let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); let buffer_2 = @@ -4840,7 +4840,7 @@ mod tests { ); } - #[gpui2::test(iterations = 100)] + #[gpui::test(iterations = 100)] fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) @@ -5262,7 +5262,7 @@ mod tests { } } - #[gpui2::test] + #[gpui::test] fn test_history(cx: &mut AppContext) { let test_settings = SettingsStore::test(cx); cx.set_global(test_settings); diff --git a/crates/prettier2/Cargo.toml b/crates/prettier2/Cargo.toml index b98124f72c..de229dcc70 100644 --- a/crates/prettier2/Cargo.toml +++ b/crates/prettier2/Cargo.toml @@ -12,12 +12,12 @@ doctest = false test-support = [] [dependencies] -client2 = { path = "../client2" } +client = { package = "client2", path = "../client2" } collections = { path = "../collections"} -language2 = { path = "../language2" } -gpui2 = { path = "../gpui2" } -fs2 = { path = "../fs2" } -lsp2 = { path = "../lsp2" } +language = { package = "language2", path = "../language2" } +gpui = { package = "gpui2", path = "../gpui2" } +fs = { package = "fs2", path = "../fs2" } +lsp = { package = "lsp2", path = "../lsp2" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } @@ -30,6 +30,6 @@ futures.workspace = true parking_lot.workspace = true [dev-dependencies] -language2 = { path = "../language2", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -fs2 = { path = "../fs2", features = ["test-support"] } +language = { package = "language2", path = "../language2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +fs = { package = "fs2", path = "../fs2", features = ["test-support"] } diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index 6d9664b234..d9b6f9eab7 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/crates/prettier2/src/prettier2.rs @@ -1,9 +1,9 @@ use anyhow::Context; use collections::HashMap; -use fs2::Fs; -use gpui2::{AsyncAppContext, Model}; -use language2::{language_settings::language_settings, Buffer, Diff}; -use lsp2::{LanguageServer, LanguageServerId}; +use fs::Fs; +use gpui::{AsyncAppContext, Model}; +use language::{language_settings::language_settings, Buffer, Diff}; +use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use std::{ @@ -141,7 +141,7 @@ impl Prettier { node: Arc, cx: AsyncAppContext, ) -> anyhow::Result { - use lsp2::LanguageServerBinary; + use lsp::LanguageServerBinary; let executor = cx.background_executor().clone(); anyhow::ensure!( @@ -453,7 +453,7 @@ struct FormatResult { text: String, } -impl lsp2::request::Request for Format { +impl lsp::request::Request for Format { type Params = FormatParams; type Result = FormatResult; const METHOD: &'static str = "prettier/format"; @@ -461,7 +461,7 @@ impl lsp2::request::Request for Format { enum ClearCache {} -impl lsp2::request::Request for ClearCache { +impl lsp::request::Request for ClearCache { type Params = (); type Result = (); const METHOD: &'static str = "prettier/clear_cache"; diff --git a/crates/project2/Cargo.toml b/crates/project2/Cargo.toml index f1c0716d58..7aae9fb007 100644 --- a/crates/project2/Cargo.toml +++ b/crates/project2/Cargo.toml @@ -10,35 +10,35 @@ doctest = false [features] test-support = [ - "client2/test-support", - "db2/test-support", - "language2/test-support", - "settings2/test-support", + "client/test-support", + "db/test-support", + "language/test-support", + "settings/test-support", "text/test-support", - "prettier2/test-support", - "gpui2/test-support", + "prettier/test-support", + "gpui/test-support", ] [dependencies] text = { path = "../text" } -copilot2 = { path = "../copilot2" } -client2 = { path = "../client2" } +copilot = { package = "copilot2", path = "../copilot2" } +client = { package = "client2", path = "../client2" } clock = { path = "../clock" } collections = { path = "../collections" } -db2 = { path = "../db2" } -fs2 = { path = "../fs2" } +db = { package = "db2", path = "../db2" } +fs = { package = "fs2", path = "../fs2" } fsevent = { path = "../fsevent" } -fuzzy2 = { path = "../fuzzy2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } git = { path = "../git" } -gpui2 = { path = "../gpui2" } -language2 = { path = "../language2" } -lsp2 = { path = "../lsp2" } +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +lsp = { package = "lsp2", path = "../lsp2" } node_runtime = { path = "../node_runtime" } -prettier2 = { path = "../prettier2" } -rpc2 = { path = "../rpc2" } -settings2 = { path = "../settings2" } +prettier = { package = "prettier2", path = "../prettier2" } +rpc = { package = "rpc2", path = "../rpc2" } +settings = { package = "settings2", path = "../settings2" } sum_tree = { path = "../sum_tree" } -terminal2 = { path = "../terminal2" } +terminal = { package = "terminal2", path = "../terminal2" } util = { path = "../util" } aho-corasick = "1.1" @@ -69,17 +69,17 @@ itertools = "0.10" ctor.workspace = true env_logger.workspace = true pretty_assertions.workspace = true -client2 = { path = "../client2", features = ["test-support"] } +client = { package = "client2", path = "../client2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } -db2 = { path = "../db2", features = ["test-support"] } -fs2 = { path = "../fs2", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -language2 = { path = "../language2", features = ["test-support"] } -lsp2 = { path = "../lsp2", features = ["test-support"] } -settings2 = { path = "../settings2", features = ["test-support"] } -prettier2 = { path = "../prettier2", features = ["test-support"] } +db = { package = "db2", path = "../db2", features = ["test-support"] } +fs = { package = "fs2", path = "../fs2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +language = { package = "language2", path = "../language2", features = ["test-support"] } +lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } +prettier = { package = "prettier2", path = "../prettier2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } -rpc2 = { path = "../rpc2", features = ["test-support"] } +rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] } git2.workspace = true tempdir.workspace = true unindent.workspace = true diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index 9e6a96e15e..cc1821d3ff 100644 --- a/crates/project2/src/lsp_command.rs +++ b/crates/project2/src/lsp_command.rs @@ -5,10 +5,10 @@ use crate::{ }; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client2::proto::{self, PeerId}; +use client::proto::{self, PeerId}; use futures::future; -use gpui2::{AppContext, AsyncAppContext, Model}; -use language2::{ +use gpui::{AppContext, AsyncAppContext, Model}; +use language::{ language_settings::{language_settings, InlayHintKind}, point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, @@ -16,29 +16,29 @@ use language2::{ CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; -use lsp2::{ +use lsp::{ CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, ServerCapabilities, }; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use text::LineEnding; -pub fn lsp_formatting_options(tab_size: u32) -> lsp2::FormattingOptions { - lsp2::FormattingOptions { +pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { + lsp::FormattingOptions { tab_size, insert_spaces: true, insert_final_newline: Some(true), - ..lsp2::FormattingOptions::default() + ..lsp::FormattingOptions::default() } } #[async_trait(?Send)] pub(crate) trait LspCommand: 'static + Sized + Send { type Response: 'static + Default + Send; - type LspRequest: 'static + Send + lsp2::request::Request; + type LspRequest: 'static + Send + lsp::request::Request; type ProtoRequest: 'static + Send + proto::RequestMessage; - fn check_capabilities(&self, _: &lsp2::ServerCapabilities) -> bool { + fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool { true } @@ -48,11 +48,11 @@ pub(crate) trait LspCommand: 'static + Sized + Send { buffer: &Buffer, language_server: &Arc, cx: &AppContext, - ) -> ::Params; + ) -> ::Params; async fn response_from_lsp( self, - message: ::Result, + message: ::Result, project: Model, buffer: Model, server_id: LanguageServerId, @@ -140,8 +140,8 @@ pub(crate) struct FormattingOptions { tab_size: u32, } -impl From for FormattingOptions { - fn from(value: lsp2::FormattingOptions) -> Self { +impl From for FormattingOptions { + fn from(value: lsp::FormattingOptions) -> Self { Self { tab_size: value.tab_size, } @@ -151,11 +151,11 @@ impl From for FormattingOptions { #[async_trait(?Send)] impl LspCommand for PrepareRename { type Response = Option>; - type LspRequest = lsp2::request::PrepareRenameRequest; + type LspRequest = lsp::request::PrepareRenameRequest; type ProtoRequest = proto::PrepareRename; fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - if let Some(lsp2::OneOf::Right(rename)) = &capabilities.rename_provider { + if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider { rename.prepare_provider == Some(true) } else { false @@ -168,10 +168,10 @@ impl LspCommand for PrepareRename { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::TextDocumentPositionParams { - lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::TextDocumentPositionParams { + lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), } @@ -179,7 +179,7 @@ impl LspCommand for PrepareRename { async fn response_from_lsp( self, - message: Option, + message: Option, _: Model, buffer: Model, _: LanguageServerId, @@ -187,8 +187,8 @@ impl LspCommand for PrepareRename { ) -> Result>> { buffer.update(&mut cx, |buffer, _| { if let Some( - lsp2::PrepareRenameResponse::Range(range) - | lsp2::PrepareRenameResponse::RangeWithPlaceholder { range, .. }, + lsp::PrepareRenameResponse::Range(range) + | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. }, ) = message { let Range { start, end } = range_from_lsp(range); @@ -206,7 +206,7 @@ impl LspCommand for PrepareRename { proto::PrepareRename { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), @@ -245,10 +245,10 @@ impl LspCommand for PrepareRename { can_rename: range.is_some(), start: range .as_ref() - .map(|range| language2::proto::serialize_anchor(&range.start)), + .map(|range| language::proto::serialize_anchor(&range.start)), end: range .as_ref() - .map(|range| language2::proto::serialize_anchor(&range.end)), + .map(|range| language::proto::serialize_anchor(&range.end)), version: serialize_version(buffer_version), } } @@ -282,7 +282,7 @@ impl LspCommand for PrepareRename { #[async_trait(?Send)] impl LspCommand for PerformRename { type Response = ProjectTransaction; - type LspRequest = lsp2::request::Rename; + type LspRequest = lsp::request::Rename; type ProtoRequest = proto::PerformRename; fn to_lsp( @@ -291,11 +291,11 @@ impl LspCommand for PerformRename { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::RenameParams { - lsp2::RenameParams { - text_document_position: lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::RenameParams { + lsp::RenameParams { + text_document_position: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, @@ -306,7 +306,7 @@ impl LspCommand for PerformRename { async fn response_from_lsp( self, - message: Option, + message: Option, project: Model, buffer: Model, server_id: LanguageServerId, @@ -333,7 +333,7 @@ impl LspCommand for PerformRename { proto::PerformRename { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), new_name: self.new_name.clone(), @@ -401,7 +401,7 @@ impl LspCommand for PerformRename { #[async_trait(?Send)] impl LspCommand for GetDefinition { type Response = Vec; - type LspRequest = lsp2::request::GotoDefinition; + type LspRequest = lsp::request::GotoDefinition; type ProtoRequest = proto::GetDefinition; fn to_lsp( @@ -410,11 +410,11 @@ impl LspCommand for GetDefinition { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::GotoDefinitionParams { - lsp2::GotoDefinitionParams { - text_document_position_params: lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::GotoDefinitionParams { + lsp::GotoDefinitionParams { + text_document_position_params: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, @@ -425,7 +425,7 @@ impl LspCommand for GetDefinition { async fn response_from_lsp( self, - message: Option, + message: Option, project: Model, buffer: Model, server_id: LanguageServerId, @@ -438,7 +438,7 @@ impl LspCommand for GetDefinition { proto::GetDefinition { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), @@ -494,13 +494,13 @@ impl LspCommand for GetDefinition { #[async_trait(?Send)] impl LspCommand for GetTypeDefinition { type Response = Vec; - type LspRequest = lsp2::request::GotoTypeDefinition; + type LspRequest = lsp::request::GotoTypeDefinition; type ProtoRequest = proto::GetTypeDefinition; fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { match &capabilities.type_definition_provider { None => false, - Some(lsp2::TypeDefinitionProviderCapability::Simple(false)) => false, + Some(lsp::TypeDefinitionProviderCapability::Simple(false)) => false, _ => true, } } @@ -511,11 +511,11 @@ impl LspCommand for GetTypeDefinition { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::GotoTypeDefinitionParams { - lsp2::GotoTypeDefinitionParams { - text_document_position_params: lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::GotoTypeDefinitionParams { + lsp::GotoTypeDefinitionParams { + text_document_position_params: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, @@ -526,7 +526,7 @@ impl LspCommand for GetTypeDefinition { async fn response_from_lsp( self, - message: Option, + message: Option, project: Model, buffer: Model, server_id: LanguageServerId, @@ -539,7 +539,7 @@ impl LspCommand for GetTypeDefinition { proto::GetTypeDefinition { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), @@ -670,7 +670,7 @@ async fn location_links_from_proto( } async fn location_links_from_lsp( - message: Option, + message: Option, project: Model, buffer: Model, server_id: LanguageServerId, @@ -683,15 +683,15 @@ async fn location_links_from_lsp( let mut unresolved_links = Vec::new(); match message { - lsp2::GotoDefinitionResponse::Scalar(loc) => { + lsp::GotoDefinitionResponse::Scalar(loc) => { unresolved_links.push((None, loc.uri, loc.range)); } - lsp2::GotoDefinitionResponse::Array(locs) => { + lsp::GotoDefinitionResponse::Array(locs) => { unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range))); } - lsp2::GotoDefinitionResponse::Link(links) => { + lsp::GotoDefinitionResponse::Link(links) => { unresolved_links.extend(links.into_iter().map(|l| { ( l.origin_selection_range, @@ -786,7 +786,7 @@ fn location_links_to_proto( #[async_trait(?Send)] impl LspCommand for GetReferences { type Response = Vec; - type LspRequest = lsp2::request::References; + type LspRequest = lsp::request::References; type ProtoRequest = proto::GetReferences; fn to_lsp( @@ -795,17 +795,17 @@ impl LspCommand for GetReferences { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::ReferenceParams { - lsp2::ReferenceParams { - text_document_position: lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::ReferenceParams { + lsp::ReferenceParams { + text_document_position: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, work_done_progress_params: Default::default(), partial_result_params: Default::default(), - context: lsp2::ReferenceContext { + context: lsp::ReferenceContext { include_declaration: true, }, } @@ -813,7 +813,7 @@ impl LspCommand for GetReferences { async fn response_from_lsp( self, - locations: Option>, + locations: Option>, project: Model, buffer: Model, server_id: LanguageServerId, @@ -859,7 +859,7 @@ impl LspCommand for GetReferences { proto::GetReferences { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), @@ -948,7 +948,7 @@ impl LspCommand for GetReferences { #[async_trait(?Send)] impl LspCommand for GetDocumentHighlights { type Response = Vec; - type LspRequest = lsp2::request::DocumentHighlightRequest; + type LspRequest = lsp::request::DocumentHighlightRequest; type ProtoRequest = proto::GetDocumentHighlights; fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { @@ -961,11 +961,11 @@ impl LspCommand for GetDocumentHighlights { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::DocumentHighlightParams { - lsp2::DocumentHighlightParams { - text_document_position_params: lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::DocumentHighlightParams { + lsp::DocumentHighlightParams { + text_document_position_params: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, @@ -976,7 +976,7 @@ impl LspCommand for GetDocumentHighlights { async fn response_from_lsp( self, - lsp_highlights: Option>, + lsp_highlights: Option>, _: Model, buffer: Model, _: LanguageServerId, @@ -996,7 +996,7 @@ impl LspCommand for GetDocumentHighlights { range: buffer.anchor_after(start)..buffer.anchor_before(end), kind: lsp_highlight .kind - .unwrap_or(lsp2::DocumentHighlightKind::READ), + .unwrap_or(lsp::DocumentHighlightKind::READ), } }) .collect() @@ -1007,7 +1007,7 @@ impl LspCommand for GetDocumentHighlights { proto::GetDocumentHighlights { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), @@ -1099,7 +1099,7 @@ impl LspCommand for GetDocumentHighlights { #[async_trait(?Send)] impl LspCommand for GetHover { type Response = Option; - type LspRequest = lsp2::request::HoverRequest; + type LspRequest = lsp::request::HoverRequest; type ProtoRequest = proto::GetHover; fn to_lsp( @@ -1108,11 +1108,11 @@ impl LspCommand for GetHover { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::HoverParams { - lsp2::HoverParams { - text_document_position_params: lsp2::TextDocumentPositionParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::HoverParams { + lsp::HoverParams { + text_document_position_params: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, @@ -1122,7 +1122,7 @@ impl LspCommand for GetHover { async fn response_from_lsp( self, - message: Option, + message: Option, _: Model, buffer: Model, _: LanguageServerId, @@ -1144,15 +1144,13 @@ impl LspCommand for GetHover { ) })?; - fn hover_blocks_from_marked_string( - marked_string: lsp2::MarkedString, - ) -> Option { + fn hover_blocks_from_marked_string(marked_string: lsp::MarkedString) -> Option { let block = match marked_string { - lsp2::MarkedString::String(content) => HoverBlock { + lsp::MarkedString::String(content) => HoverBlock { text: content, kind: HoverBlockKind::Markdown, }, - lsp2::MarkedString::LanguageString(lsp2::LanguageString { language, value }) => { + lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => { HoverBlock { text: value, kind: HoverBlockKind::Code { language }, @@ -1167,18 +1165,18 @@ impl LspCommand for GetHover { } let contents = match hover.contents { - lsp2::HoverContents::Scalar(marked_string) => { + lsp::HoverContents::Scalar(marked_string) => { hover_blocks_from_marked_string(marked_string) .into_iter() .collect() } - lsp2::HoverContents::Array(marked_strings) => marked_strings + lsp::HoverContents::Array(marked_strings) => marked_strings .into_iter() .filter_map(hover_blocks_from_marked_string) .collect(), - lsp2::HoverContents::Markup(markup_content) => vec![HoverBlock { + lsp::HoverContents::Markup(markup_content) => vec![HoverBlock { text: markup_content.value, - kind: if markup_content.kind == lsp2::MarkupKind::Markdown { + kind: if markup_content.kind == lsp::MarkupKind::Markdown { HoverBlockKind::Markdown } else { HoverBlockKind::PlainText @@ -1197,7 +1195,7 @@ impl LspCommand for GetHover { proto::GetHover { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version), @@ -1234,8 +1232,8 @@ impl LspCommand for GetHover { if let Some(response) = response { let (start, end) = if let Some(range) = response.range { ( - Some(language2::proto::serialize_anchor(&range.start)), - Some(language2::proto::serialize_anchor(&range.end)), + Some(language::proto::serialize_anchor(&range.start)), + Some(language::proto::serialize_anchor(&range.end)), ) } else { (None, None) @@ -1296,8 +1294,8 @@ impl LspCommand for GetHover { let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?; let range = if let (Some(start), Some(end)) = (message.start, message.end) { - language2::proto::deserialize_anchor(start) - .and_then(|start| language2::proto::deserialize_anchor(end).map(|end| start..end)) + language::proto::deserialize_anchor(start) + .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end)) } else { None }; @@ -1317,7 +1315,7 @@ impl LspCommand for GetHover { #[async_trait(?Send)] impl LspCommand for GetCompletions { type Response = Vec; - type LspRequest = lsp2::request::Completion; + type LspRequest = lsp::request::Completion; type ProtoRequest = proto::GetCompletions; fn to_lsp( @@ -1326,10 +1324,10 @@ impl LspCommand for GetCompletions { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::CompletionParams { - lsp2::CompletionParams { - text_document_position: lsp2::TextDocumentPositionParams::new( - lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path(path).unwrap()), + ) -> lsp::CompletionParams { + lsp::CompletionParams { + text_document_position: lsp::TextDocumentPositionParams::new( + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()), point_to_lsp(self.position), ), context: Default::default(), @@ -1340,7 +1338,7 @@ impl LspCommand for GetCompletions { async fn response_from_lsp( self, - completions: Option, + completions: Option, _: Model, buffer: Model, server_id: LanguageServerId, @@ -1349,9 +1347,9 @@ impl LspCommand for GetCompletions { let mut response_list = None; let completions = if let Some(completions) = completions { match completions { - lsp2::CompletionResponse::Array(completions) => completions, + lsp::CompletionResponse::Array(completions) => completions, - lsp2::CompletionResponse::List(mut list) => { + lsp::CompletionResponse::List(mut list) => { let items = std::mem::take(&mut list.items); response_list = Some(list); items @@ -1373,7 +1371,7 @@ impl LspCommand for GetCompletions { let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then // check that the range is valid. - Some(lsp2::CompletionTextEdit::Edit(edit)) => { + Some(lsp::CompletionTextEdit::Edit(edit)) => { let range = range_from_lsp(edit.range); let start = snapshot.clip_point_utf16(range.start, Bias::Left); let end = snapshot.clip_point_utf16(range.end, Bias::Left); @@ -1439,7 +1437,7 @@ impl LspCommand for GetCompletions { (range, text) } - Some(lsp2::CompletionTextEdit::InsertAndReplace(_)) => { + Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { log::info!("unsupported insert/replace completion"); return None; } @@ -1457,7 +1455,7 @@ impl LspCommand for GetCompletions { old_range, new_text, label: label.unwrap_or_else(|| { - language2::CodeLabel::plain( + language::CodeLabel::plain( lsp_completion.label.clone(), lsp_completion.filter_text.as_deref(), ) @@ -1477,7 +1475,7 @@ impl LspCommand for GetCompletions { proto::GetCompletions { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor(&anchor)), + position: Some(language::proto::serialize_anchor(&anchor)), version: serialize_version(&buffer.version()), } } @@ -1494,7 +1492,7 @@ impl LspCommand for GetCompletions { .await?; let position = message .position - .and_then(language2::proto::deserialize_anchor) + .and_then(language::proto::deserialize_anchor) .map(|p| { buffer.update(&mut cx, |buffer, _| { buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left) @@ -1514,7 +1512,7 @@ impl LspCommand for GetCompletions { proto::GetCompletionsResponse { completions: completions .iter() - .map(language2::proto::serialize_completion) + .map(language::proto::serialize_completion) .collect(), version: serialize_version(&buffer_version), } @@ -1535,7 +1533,7 @@ impl LspCommand for GetCompletions { let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?; let completions = message.completions.into_iter().map(|completion| { - language2::proto::deserialize_completion(completion, language.clone()) + language::proto::deserialize_completion(completion, language.clone()) }); future::try_join_all(completions).await } @@ -1548,13 +1546,13 @@ impl LspCommand for GetCompletions { #[async_trait(?Send)] impl LspCommand for GetCodeActions { type Response = Vec; - type LspRequest = lsp2::request::CodeActionRequest; + type LspRequest = lsp::request::CodeActionRequest; type ProtoRequest = proto::GetCodeActions; fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { match &capabilities.code_action_provider { None => false, - Some(lsp2::CodeActionProviderCapability::Simple(false)) => false, + Some(lsp::CodeActionProviderCapability::Simple(false)) => false, _ => true, } } @@ -1565,30 +1563,30 @@ impl LspCommand for GetCodeActions { buffer: &Buffer, language_server: &Arc, _: &AppContext, - ) -> lsp2::CodeActionParams { + ) -> lsp::CodeActionParams { let relevant_diagnostics = buffer .snapshot() .diagnostics_in_range::<_, usize>(self.range.clone(), false) .map(|entry| entry.to_lsp_diagnostic_stub()) .collect(); - lsp2::CodeActionParams { - text_document: lsp2::TextDocumentIdentifier::new( - lsp2::Url::from_file_path(path).unwrap(), + lsp::CodeActionParams { + text_document: lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path(path).unwrap(), ), range: range_to_lsp(self.range.to_point_utf16(buffer)), work_done_progress_params: Default::default(), partial_result_params: Default::default(), - context: lsp2::CodeActionContext { + context: lsp::CodeActionContext { diagnostics: relevant_diagnostics, only: language_server.code_action_kinds(), - ..lsp2::CodeActionContext::default() + ..lsp::CodeActionContext::default() }, } } async fn response_from_lsp( self, - actions: Option, + actions: Option, _: Model, _: Model, server_id: LanguageServerId, @@ -1598,7 +1596,7 @@ impl LspCommand for GetCodeActions { .unwrap_or_default() .into_iter() .filter_map(|entry| { - if let lsp2::CodeActionOrCommand::CodeAction(lsp_action) = entry { + if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry { Some(CodeAction { server_id, range: self.range.clone(), @@ -1615,8 +1613,8 @@ impl LspCommand for GetCodeActions { proto::GetCodeActions { project_id, buffer_id: buffer.remote_id(), - start: Some(language2::proto::serialize_anchor(&self.range.start)), - end: Some(language2::proto::serialize_anchor(&self.range.end)), + start: Some(language::proto::serialize_anchor(&self.range.start)), + end: Some(language::proto::serialize_anchor(&self.range.end)), version: serialize_version(&buffer.version()), } } @@ -1629,11 +1627,11 @@ impl LspCommand for GetCodeActions { ) -> Result { let start = message .start - .and_then(language2::proto::deserialize_anchor) + .and_then(language::proto::deserialize_anchor) .ok_or_else(|| anyhow!("invalid start"))?; let end = message .end - .and_then(language2::proto::deserialize_anchor) + .and_then(language::proto::deserialize_anchor) .ok_or_else(|| anyhow!("invalid end"))?; buffer .update(&mut cx, |buffer, _| { @@ -1654,7 +1652,7 @@ impl LspCommand for GetCodeActions { proto::GetCodeActionsResponse { actions: code_actions .iter() - .map(language2::proto::serialize_code_action) + .map(language::proto::serialize_code_action) .collect(), version: serialize_version(&buffer_version), } @@ -1675,7 +1673,7 @@ impl LspCommand for GetCodeActions { message .actions .into_iter() - .map(language2::proto::deserialize_code_action) + .map(language::proto::deserialize_code_action) .collect() } @@ -1687,10 +1685,10 @@ impl LspCommand for GetCodeActions { #[async_trait(?Send)] impl LspCommand for OnTypeFormatting { type Response = Option; - type LspRequest = lsp2::request::OnTypeFormatting; + type LspRequest = lsp::request::OnTypeFormatting; type ProtoRequest = proto::OnTypeFormatting; - fn check_capabilities(&self, server_capabilities: &lsp2::ServerCapabilities) -> bool { + fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { let Some(on_type_formatting_options) = &server_capabilities.document_on_type_formatting_provider else { @@ -1712,10 +1710,10 @@ impl LspCommand for OnTypeFormatting { _: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::DocumentOnTypeFormattingParams { - lsp2::DocumentOnTypeFormattingParams { - text_document_position: lsp2::TextDocumentPositionParams::new( - lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path(path).unwrap()), + ) -> lsp::DocumentOnTypeFormattingParams { + lsp::DocumentOnTypeFormattingParams { + text_document_position: lsp::TextDocumentPositionParams::new( + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()), point_to_lsp(self.position), ), ch: self.trigger.clone(), @@ -1725,7 +1723,7 @@ impl LspCommand for OnTypeFormatting { async fn response_from_lsp( self, - message: Option>, + message: Option>, project: Model, buffer: Model, server_id: LanguageServerId, @@ -1753,7 +1751,7 @@ impl LspCommand for OnTypeFormatting { proto::OnTypeFormatting { project_id, buffer_id: buffer.remote_id(), - position: Some(language2::proto::serialize_anchor( + position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), trigger: self.trigger.clone(), @@ -1798,7 +1796,7 @@ impl LspCommand for OnTypeFormatting { ) -> proto::OnTypeFormattingResponse { proto::OnTypeFormattingResponse { transaction: response - .map(|transaction| language2::proto::serialize_transaction(&transaction)), + .map(|transaction| language::proto::serialize_transaction(&transaction)), } } @@ -1812,9 +1810,7 @@ impl LspCommand for OnTypeFormatting { let Some(transaction) = message.transaction else { return Ok(None); }; - Ok(Some(language2::proto::deserialize_transaction( - transaction, - )?)) + Ok(Some(language::proto::deserialize_transaction(transaction)?)) } fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 { @@ -1824,7 +1820,7 @@ impl LspCommand for OnTypeFormatting { impl InlayHints { pub async fn lsp_to_project_hint( - lsp_hint: lsp2::InlayHint, + lsp_hint: lsp::InlayHint, buffer_handle: &Model, server_id: LanguageServerId, resolve_state: ResolveState, @@ -1832,8 +1828,8 @@ impl InlayHints { cx: &mut AsyncAppContext, ) -> anyhow::Result { let kind = lsp_hint.kind.and_then(|kind| match kind { - lsp2::InlayHintKind::TYPE => Some(InlayHintKind::Type), - lsp2::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), + lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type), + lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), _ => None, }); @@ -1861,12 +1857,12 @@ impl InlayHints { label, kind, tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip { - lsp2::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), - lsp2::InlayHintTooltip::MarkupContent(markup_content) => { + lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), + lsp::InlayHintTooltip::MarkupContent(markup_content) => { InlayHintTooltip::MarkupContent(MarkupContent { kind: match markup_content.kind { - lsp2::MarkupKind::PlainText => HoverBlockKind::PlainText, - lsp2::MarkupKind::Markdown => HoverBlockKind::Markdown, + lsp::MarkupKind::PlainText => HoverBlockKind::PlainText, + lsp::MarkupKind::Markdown => HoverBlockKind::Markdown, }, value: markup_content.value, }) @@ -1877,25 +1873,25 @@ impl InlayHints { } async fn lsp_inlay_label_to_project( - lsp_label: lsp2::InlayHintLabel, + lsp_label: lsp::InlayHintLabel, server_id: LanguageServerId, ) -> anyhow::Result { let label = match lsp_label { - lsp2::InlayHintLabel::String(s) => InlayHintLabel::String(s), - lsp2::InlayHintLabel::LabelParts(lsp_parts) => { + lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), + lsp::InlayHintLabel::LabelParts(lsp_parts) => { let mut parts = Vec::with_capacity(lsp_parts.len()); for lsp_part in lsp_parts { parts.push(InlayHintLabelPart { value: lsp_part.value, tooltip: lsp_part.tooltip.map(|tooltip| match tooltip { - lsp2::InlayHintLabelPartTooltip::String(s) => { + lsp::InlayHintLabelPartTooltip::String(s) => { InlayHintLabelPartTooltip::String(s) } - lsp2::InlayHintLabelPartTooltip::MarkupContent(markup_content) => { + lsp::InlayHintLabelPartTooltip::MarkupContent(markup_content) => { InlayHintLabelPartTooltip::MarkupContent(MarkupContent { kind: match markup_content.kind { - lsp2::MarkupKind::PlainText => HoverBlockKind::PlainText, - lsp2::MarkupKind::Markdown => HoverBlockKind::Markdown, + lsp::MarkupKind::PlainText => HoverBlockKind::PlainText, + lsp::MarkupKind::Markdown => HoverBlockKind::Markdown, }, value: markup_content.value, }) @@ -1933,7 +1929,7 @@ impl InlayHints { lsp_resolve_state, }); proto::InlayHint { - position: Some(language2::proto::serialize_anchor(&response_hint.position)), + position: Some(language::proto::serialize_anchor(&response_hint.position)), padding_left: response_hint.padding_left, padding_right: response_hint.padding_right, label: Some(proto::InlayHintLabel { @@ -1992,7 +1988,7 @@ impl InlayHints { let resolve_state_data = resolve_state .lsp_resolve_state.as_ref() .map(|lsp_resolve_state| { - serde_json::from_str::>(&lsp_resolve_state.value) + serde_json::from_str::>(&lsp_resolve_state.value) .with_context(|| format!("incorrect proto inlay hint message: non-json resolve state {lsp_resolve_state:?}")) .map(|state| (LanguageServerId(lsp_resolve_state.server_id as usize), state)) }) @@ -2015,7 +2011,7 @@ impl InlayHints { Ok(InlayHint { position: message_hint .position - .and_then(language2::proto::deserialize_anchor) + .and_then(language::proto::deserialize_anchor) .context("invalid position")?, label: match message_hint .label @@ -2058,10 +2054,10 @@ impl InlayHints { { Some(((uri, range), server_id)) => Some(( LanguageServerId(server_id as usize), - lsp2::Location { - uri: lsp2::Url::parse(&uri) + lsp::Location { + uri: lsp::Url::parse(&uri) .context("invalid uri in hint part {part:?}")?, - range: lsp2::Range::new( + range: lsp::Range::new( point_to_lsp(PointUtf16::new( range.start.row, range.start.column, @@ -2107,22 +2103,22 @@ impl InlayHints { }) } - pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp2::InlayHint { - lsp2::InlayHint { + pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp::InlayHint { + lsp::InlayHint { position: point_to_lsp(hint.position.to_point_utf16(snapshot)), kind: hint.kind.map(|kind| match kind { - InlayHintKind::Type => lsp2::InlayHintKind::TYPE, - InlayHintKind::Parameter => lsp2::InlayHintKind::PARAMETER, + InlayHintKind::Type => lsp::InlayHintKind::TYPE, + InlayHintKind::Parameter => lsp::InlayHintKind::PARAMETER, }), text_edits: None, tooltip: hint.tooltip.and_then(|tooltip| { Some(match tooltip { - InlayHintTooltip::String(s) => lsp2::InlayHintTooltip::String(s), + InlayHintTooltip::String(s) => lsp::InlayHintTooltip::String(s), InlayHintTooltip::MarkupContent(markup_content) => { - lsp2::InlayHintTooltip::MarkupContent(lsp2::MarkupContent { + lsp::InlayHintTooltip::MarkupContent(lsp::MarkupContent { kind: match markup_content.kind { - HoverBlockKind::PlainText => lsp2::MarkupKind::PlainText, - HoverBlockKind::Markdown => lsp2::MarkupKind::Markdown, + HoverBlockKind::PlainText => lsp::MarkupKind::PlainText, + HoverBlockKind::Markdown => lsp::MarkupKind::Markdown, HoverBlockKind::Code { .. } => return None, }, value: markup_content.value, @@ -2131,26 +2127,26 @@ impl InlayHints { }) }), label: match hint.label { - InlayHintLabel::String(s) => lsp2::InlayHintLabel::String(s), - InlayHintLabel::LabelParts(label_parts) => lsp2::InlayHintLabel::LabelParts( + InlayHintLabel::String(s) => lsp::InlayHintLabel::String(s), + InlayHintLabel::LabelParts(label_parts) => lsp::InlayHintLabel::LabelParts( label_parts .into_iter() - .map(|part| lsp2::InlayHintLabelPart { + .map(|part| lsp::InlayHintLabelPart { value: part.value, tooltip: part.tooltip.and_then(|tooltip| { Some(match tooltip { InlayHintLabelPartTooltip::String(s) => { - lsp2::InlayHintLabelPartTooltip::String(s) + lsp::InlayHintLabelPartTooltip::String(s) } InlayHintLabelPartTooltip::MarkupContent(markup_content) => { - lsp2::InlayHintLabelPartTooltip::MarkupContent( - lsp2::MarkupContent { + lsp::InlayHintLabelPartTooltip::MarkupContent( + lsp::MarkupContent { kind: match markup_content.kind { HoverBlockKind::PlainText => { - lsp2::MarkupKind::PlainText + lsp::MarkupKind::PlainText } HoverBlockKind::Markdown => { - lsp2::MarkupKind::Markdown + lsp::MarkupKind::Markdown } HoverBlockKind::Code { .. } => return None, }, @@ -2182,8 +2178,8 @@ impl InlayHints { .and_then(|options| match options { OneOf::Left(_is_supported) => None, OneOf::Right(capabilities) => match capabilities { - lsp2::InlayHintServerCapabilities::Options(o) => o.resolve_provider, - lsp2::InlayHintServerCapabilities::RegistrationOptions(o) => { + lsp::InlayHintServerCapabilities::Options(o) => o.resolve_provider, + lsp::InlayHintServerCapabilities::RegistrationOptions(o) => { o.inlay_hint_options.resolve_provider } }, @@ -2195,18 +2191,18 @@ impl InlayHints { #[async_trait(?Send)] impl LspCommand for InlayHints { type Response = Vec; - type LspRequest = lsp2::InlayHintRequest; + type LspRequest = lsp::InlayHintRequest; type ProtoRequest = proto::InlayHints; - fn check_capabilities(&self, server_capabilities: &lsp2::ServerCapabilities) -> bool { + fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { return false; }; match inlay_hint_provider { - lsp2::OneOf::Left(enabled) => *enabled, - lsp2::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities { - lsp2::InlayHintServerCapabilities::Options(_) => true, - lsp2::InlayHintServerCapabilities::RegistrationOptions(_) => false, + lsp::OneOf::Left(enabled) => *enabled, + lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities { + lsp::InlayHintServerCapabilities::Options(_) => true, + lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false, }, } } @@ -2217,10 +2213,10 @@ impl LspCommand for InlayHints { buffer: &Buffer, _: &Arc, _: &AppContext, - ) -> lsp2::InlayHintParams { - lsp2::InlayHintParams { - text_document: lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(path).unwrap(), + ) -> lsp::InlayHintParams { + lsp::InlayHintParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), }, range: range_to_lsp(self.range.to_point_utf16(buffer)), work_done_progress_params: Default::default(), @@ -2229,7 +2225,7 @@ impl LspCommand for InlayHints { async fn response_from_lsp( self, - message: Option>, + message: Option>, project: Model, buffer: Model, server_id: LanguageServerId, @@ -2278,8 +2274,8 @@ impl LspCommand for InlayHints { proto::InlayHints { project_id, buffer_id: buffer.remote_id(), - start: Some(language2::proto::serialize_anchor(&self.range.start)), - end: Some(language2::proto::serialize_anchor(&self.range.end)), + start: Some(language::proto::serialize_anchor(&self.range.start)), + end: Some(language::proto::serialize_anchor(&self.range.end)), version: serialize_version(&buffer.version()), } } @@ -2292,11 +2288,11 @@ impl LspCommand for InlayHints { ) -> Result { let start = message .start - .and_then(language2::proto::deserialize_anchor) + .and_then(language::proto::deserialize_anchor) .context("invalid start")?; let end = message .end - .and_then(language2::proto::deserialize_anchor) + .and_then(language::proto::deserialize_anchor) .context("invalid end")?; buffer .update(&mut cx, |buffer, _| { diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 05434b93b4..65d1aba820 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -11,10 +11,10 @@ mod project_tests; mod worktree_tests; use anyhow::{anyhow, Context as _, Result}; -use client2::{proto, Client, Collaborator, TypedEnvelope, UserStore}; +use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; -use copilot2::Copilot; +use copilot::Copilot; use futures::{ channel::{ mpsc::{self, UnboundedReceiver}, @@ -25,12 +25,12 @@ use futures::{ AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; use globset::{Glob, GlobSet, GlobSetBuilder}; -use gpui2::{ +use gpui::{ AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter, Model, ModelContext, Task, WeakModel, }; use itertools::Itertools; -use language2::{ +use language::{ language_settings::{ language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings, }, @@ -46,7 +46,7 @@ use language2::{ ToOffset, ToPointUtf16, Transaction, Unclipped, }; use log::error; -use lsp2::{ +use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; @@ -54,12 +54,12 @@ use lsp_command::*; use node_runtime::NodeRuntime; use parking_lot::Mutex; use postage::watch; -use prettier2::{LocateStart, Prettier}; +use prettier::{LocateStart, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; use serde::Serialize; -use settings2::{Settings, SettingsStore}; +use settings::{Settings, SettingsStore}; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; use smol::channel::{Receiver, Sender}; @@ -86,9 +86,9 @@ use util::{ paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, }; -pub use fs2::*; +pub use fs::*; #[cfg(any(test, feature = "test-support"))] -pub use prettier2::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX; +pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX; pub use worktree::*; const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4; @@ -123,7 +123,7 @@ pub struct Project { language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>, language_server_statuses: BTreeMap, last_workspace_edits_by_language_server: HashMap, - client: Arc, + client: Arc, next_entry_id: Arc, join_project_response_message_id: u32, next_diagnostic_group_id: usize, @@ -131,8 +131,8 @@ pub struct Project { fs: Arc, client_state: Option, collaborators: HashMap, - client_subscriptions: Vec, - _subscriptions: Vec, + client_subscriptions: Vec, + _subscriptions: Vec, next_buffer_id: u64, opened_buffer: (watch::Sender<()>, watch::Receiver<()>), shared_buffers: HashMap>, @@ -158,8 +158,8 @@ pub struct Project { _maintain_buffer_languages: Task<()>, _maintain_workspace_config: Task>, terminals: Terminals, - copilot_lsp_subscription: Option, - copilot_log_subscription: Option, + copilot_lsp_subscription: Option, + copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, node: Option>, #[cfg(not(any(test, feature = "test-support")))] @@ -352,12 +352,12 @@ pub struct DiagnosticSummary { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Location { pub buffer: Model, - pub range: Range, + pub range: Range, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHint { - pub position: language2::Anchor, + pub position: language::Anchor, pub label: InlayHintLabel, pub kind: Option, pub padding_left: bool, @@ -369,7 +369,7 @@ pub struct InlayHint { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResolveState { Resolved, - CanResolve(LanguageServerId, Option), + CanResolve(LanguageServerId, Option), Resolving, } @@ -392,7 +392,7 @@ pub enum InlayHintLabel { pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, - pub location: Option<(LanguageServerId, lsp2::Location)>, + pub location: Option<(LanguageServerId, lsp::Location)>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -421,7 +421,7 @@ pub struct LocationLink { #[derive(Debug)] pub struct DocumentHighlight { - pub range: Range, + pub range: Range, pub kind: DocumentHighlightKind, } @@ -432,7 +432,7 @@ pub struct Symbol { pub path: ProjectPath, pub label: CodeLabel, pub name: String, - pub kind: lsp2::SymbolKind, + pub kind: lsp::SymbolKind, pub range: Range>, pub signature: [u8; 32], } @@ -453,7 +453,7 @@ pub enum HoverBlockKind { #[derive(Debug)] pub struct Hover { pub contents: Vec, - pub range: Option>, + pub range: Option>, pub language: Option>, } @@ -464,7 +464,7 @@ impl Hover { } #[derive(Default)] -pub struct ProjectTransaction(pub HashMap, language2::Transaction>); +pub struct ProjectTransaction(pub HashMap, language::Transaction>); impl DiagnosticSummary { fn new<'a, T: 'a>(diagnostics: impl IntoIterator>) -> Self { @@ -859,12 +859,12 @@ impl Project { pub async fn test( fs: Arc, root_paths: impl IntoIterator, - cx: &mut gpui2::TestAppContext, + cx: &mut gpui::TestAppContext, ) -> Model { let mut languages = LanguageRegistry::test(); languages.set_executor(cx.executor().clone()); let http_client = util::http::FakeHttpClient::with_404_response(); - let client = cx.update(|cx| client2::Client::new(http_client.clone(), cx)); + let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx)); let project = cx.update(|cx| { Project::local( @@ -1669,10 +1669,8 @@ impl Project { } let id = post_inc(&mut self.next_buffer_id); let buffer = cx.build_model(|cx| { - Buffer::new(self.replica_id(), id, text).with_language( - language.unwrap_or_else(|| language2::PLAIN_TEXT.clone()), - cx, - ) + Buffer::new(self.replica_id(), id, text) + .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx) }); self.register_buffer(&buffer, cx)?; Ok(buffer) @@ -1812,7 +1810,7 @@ impl Project { /// LanguageServerName is owned, because it is inserted into a map pub fn open_local_buffer_via_lsp( &mut self, - abs_path: lsp2::Url, + abs_path: lsp::Url, language_server_id: LanguageServerId, language_server_name: LanguageServerName, cx: &mut ModelContext, @@ -2019,13 +2017,13 @@ impl Project { cx.observe_release(buffer, |this, buffer, cx| { if let Some(file) = File::from_dyn(buffer.file()) { if file.is_local() { - let uri = lsp2::Url::from_file_path(file.abs_path(cx)).unwrap(); + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); for server in this.language_servers_for_buffer(buffer, cx) { server .1 - .notify::( - lsp2::DidCloseTextDocumentParams { - text_document: lsp2::TextDocumentIdentifier::new(uri.clone()), + .notify::( + lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(uri.clone()), }, ) .log_err(); @@ -2053,7 +2051,7 @@ impl Project { } let abs_path = file.abs_path(cx); - let uri = lsp2::Url::from_file_path(&abs_path) + let uri = lsp::Url::from_file_path(&abs_path) .unwrap_or_else(|()| panic!("Failed to register file {abs_path:?}")); let initial_snapshot = buffer.text_snapshot(); let language = buffer.language().cloned(); @@ -2086,9 +2084,9 @@ impl Project { }; server - .notify::( - lsp2::DidOpenTextDocumentParams { - text_document: lsp2::TextDocumentItem::new( + .notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( uri.clone(), language_id.unwrap_or_default(), 0, @@ -2145,12 +2143,12 @@ impl Project { } self.buffer_snapshots.remove(&buffer.remote_id()); - let file_url = lsp2::Url::from_file_path(old_path).unwrap(); + let file_url = lsp::Url::from_file_path(old_path).unwrap(); for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { language_server - .notify::( - lsp2::DidCloseTextDocumentParams { - text_document: lsp2::TextDocumentIdentifier::new(file_url.clone()), + .notify::( + lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new(file_url.clone()), }, ) .log_err(); @@ -2294,7 +2292,7 @@ impl Project { self.buffer_ordered_messages_tx .unbounded_send(BufferOrderedMessage::Operation { buffer_id: buffer.read(cx).remote_id(), - operation: language2::proto::serialize_operation(operation), + operation: language::proto::serialize_operation(operation), }) .ok(); } @@ -2303,7 +2301,7 @@ impl Project { let buffer = buffer.read(cx); let file = File::from_dyn(buffer.file())?; let abs_path = file.as_local()?.abs_path(cx); - let uri = lsp2::Url::from_file_path(abs_path).unwrap(); + let uri = lsp::Url::from_file_path(abs_path).unwrap(); let next_snapshot = buffer.text_snapshot(); let language_servers: Vec<_> = self @@ -2331,8 +2329,8 @@ impl Project { let new_text = next_snapshot .text_for_range(edit.new.start.1..edit.new.end.1) .collect(); - lsp2::TextDocumentContentChangeEvent { - range: Some(lsp2::Range::new( + lsp::TextDocumentContentChangeEvent { + range: Some(lsp::Range::new( point_to_lsp(edit_start), point_to_lsp(edit_end), )), @@ -2348,19 +2346,19 @@ impl Project { .text_document_sync .as_ref() .and_then(|sync| match sync { - lsp2::TextDocumentSyncCapability::Kind(kind) => Some(*kind), - lsp2::TextDocumentSyncCapability::Options(options) => options.change, + lsp::TextDocumentSyncCapability::Kind(kind) => Some(*kind), + lsp::TextDocumentSyncCapability::Options(options) => options.change, }); let content_changes: Vec<_> = match document_sync_kind { - Some(lsp2::TextDocumentSyncKind::FULL) => { - vec![lsp2::TextDocumentContentChangeEvent { + Some(lsp::TextDocumentSyncKind::FULL) => { + vec![lsp::TextDocumentContentChangeEvent { range: None, range_length: None, text: next_snapshot.text(), }] } - Some(lsp2::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(), + Some(lsp::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(), _ => { #[cfg(any(test, feature = "test-support"))] { @@ -2382,9 +2380,9 @@ impl Project { }); language_server - .notify::( - lsp2::DidChangeTextDocumentParams { - text_document: lsp2::VersionedTextDocumentIdentifier::new( + .notify::( + lsp::DidChangeTextDocumentParams { + text_document: lsp::VersionedTextDocumentIdentifier::new( uri.clone(), next_version, ), @@ -2399,16 +2397,16 @@ impl Project { let file = File::from_dyn(buffer.read(cx).file())?; let worktree_id = file.worktree_id(cx); let abs_path = file.as_local()?.abs_path(cx); - let text_document = lsp2::TextDocumentIdentifier { - uri: lsp2::Url::from_file_path(abs_path).unwrap(), + let text_document = lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(abs_path).unwrap(), }; for (_, _, server) in self.language_servers_for_worktree(worktree_id) { let text = include_text(server.as_ref()).then(|| buffer.read(cx).text()); server - .notify::( - lsp2::DidSaveTextDocumentParams { + .notify::( + lsp::DidSaveTextDocumentParams { text_document: text_document.clone(), text, }, @@ -2621,7 +2619,7 @@ impl Project { if let Some(handle) = buffer.upgrade() { let buffer = &handle.read(cx); if buffer.language().is_none() - || buffer.language() == Some(&*language2::PLAIN_TEXT) + || buffer.language() == Some(&*language::PLAIN_TEXT) { plain_text_buffers.push(handle); } else if buffer.contains_unknown_injections() { @@ -2671,8 +2669,8 @@ impl Project { let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await; server - .notify::( - lsp2::DidChangeConfigurationParams { + .notify::( + lsp::DidChangeConfigurationParams { settings: workspace_config.clone(), }, ) @@ -2992,7 +2990,7 @@ impl Project { let language_server = pending_server.task.await?; language_server - .on_notification::({ + .on_notification::({ let adapter = adapter.clone(); let this = this.clone(); move |mut params, mut cx| { @@ -3015,7 +3013,7 @@ impl Project { .detach(); language_server - .on_request::({ + .on_request::({ let adapter = adapter.clone(); move |params, cx| { let adapter = adapter.clone(); @@ -3045,7 +3043,7 @@ impl Project { // avoid stalling any language server like `gopls` which waits for a response // to these requests when initializing. language_server - .on_request::({ + .on_request::({ let this = this.clone(); move |params, mut cx| { let this = this.clone(); @@ -3053,7 +3051,7 @@ impl Project { this.update(&mut cx, |this, _| { if let Some(status) = this.language_server_statuses.get_mut(&server_id) { - if let lsp2::NumberOrString::String(token) = params.token { + if let lsp::NumberOrString::String(token) = params.token { status.progress_tokens.insert(token); } } @@ -3066,7 +3064,7 @@ impl Project { .detach(); language_server - .on_request::({ + .on_request::({ let this = this.clone(); move |params, mut cx| { let this = this.clone(); @@ -3090,7 +3088,7 @@ impl Project { .detach(); language_server - .on_request::({ + .on_request::({ let adapter = adapter.clone(); let this = this.clone(); move |params, cx| { @@ -3106,7 +3104,7 @@ impl Project { .detach(); language_server - .on_request::({ + .on_request::({ let this = this.clone(); move |(), mut cx| { let this = this.clone(); @@ -3128,7 +3126,7 @@ impl Project { adapter.disk_based_diagnostics_progress_token.clone(); language_server - .on_notification::(move |params, mut cx| { + .on_notification::(move |params, mut cx| { if let Some(this) = this.upgrade() { this.update(&mut cx, |this, cx| { this.on_lsp_progress( @@ -3146,8 +3144,8 @@ impl Project { let language_server = language_server.initialize(initialization_options).await?; language_server - .notify::( - lsp2::DidChangeConfigurationParams { + .notify::( + lsp::DidChangeConfigurationParams { settings: workspace_config, }, ) @@ -3250,10 +3248,10 @@ impl Project { let snapshot = versions.last().unwrap(); let version = snapshot.version; let initial_snapshot = &snapshot.snapshot; - let uri = lsp2::Url::from_file_path(file.abs_path(cx)).unwrap(); - language_server.notify::( - lsp2::DidOpenTextDocumentParams { - text_document: lsp2::TextDocumentItem::new( + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + language_server.notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( uri, adapter .language_ids @@ -3516,19 +3514,19 @@ impl Project { fn on_lsp_progress( &mut self, - progress: lsp2::ProgressParams, + progress: lsp::ProgressParams, language_server_id: LanguageServerId, disk_based_diagnostics_progress_token: Option, cx: &mut ModelContext, ) { let token = match progress.token { - lsp2::NumberOrString::String(token) => token, - lsp2::NumberOrString::Number(token) => { + lsp::NumberOrString::String(token) => token, + lsp::NumberOrString::Number(token) => { log::info!("skipping numeric progress token {}", token); return; } }; - let lsp2::ProgressParamsValue::WorkDone(progress) = progress.value; + let lsp::ProgressParamsValue::WorkDone(progress) = progress.value; let language_server_status = if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { status @@ -3547,7 +3545,7 @@ impl Project { }); match progress { - lsp2::WorkDoneProgress::Begin(report) => { + lsp::WorkDoneProgress::Begin(report) => { if is_disk_based_diagnostics_progress { language_server_status.has_pending_diagnostic_updates = true; self.disk_based_diagnostics_started(language_server_id, cx); @@ -3582,7 +3580,7 @@ impl Project { .ok(); } } - lsp2::WorkDoneProgress::Report(report) => { + lsp::WorkDoneProgress::Report(report) => { if !is_disk_based_diagnostics_progress { self.on_lsp_work_progress( language_server_id, @@ -3608,7 +3606,7 @@ impl Project { .ok(); } } - lsp2::WorkDoneProgress::End(_) => { + lsp::WorkDoneProgress::End(_) => { language_server_status.progress_tokens.remove(&token); if is_disk_based_diagnostics_progress { @@ -3707,15 +3705,15 @@ impl Project { let glob_is_inside_worktree = worktree.update(cx, |tree, _| { if let Some(abs_path) = tree.abs_path().to_str() { let relative_glob_pattern = match &watcher.glob_pattern { - lsp2::GlobPattern::String(s) => s + lsp::GlobPattern::String(s) => s .strip_prefix(abs_path) .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)), - lsp2::GlobPattern::Relative(rp) => { + lsp::GlobPattern::Relative(rp) => { let base_uri = match &rp.base_uri { - lsp2::OneOf::Left(workspace_folder) => { + lsp::OneOf::Left(workspace_folder) => { &workspace_folder.uri } - lsp2::OneOf::Right(base_uri) => base_uri, + lsp::OneOf::Right(base_uri) => base_uri, }; base_uri.to_file_path().ok().and_then(|file_path| { (file_path.to_str() == Some(abs_path)) @@ -3760,11 +3758,11 @@ impl Project { async fn on_lsp_workspace_edit( this: WeakModel, - params: lsp2::ApplyWorkspaceEditParams, + params: lsp::ApplyWorkspaceEditParams, server_id: LanguageServerId, adapter: Arc, mut cx: AsyncAppContext, - ) -> Result { + ) -> Result { let this = this .upgrade() .ok_or_else(|| anyhow!("project project closed"))?; @@ -3787,7 +3785,7 @@ impl Project { .insert(server_id, transaction); } })?; - Ok(lsp2::ApplyWorkspaceEditResponse { + Ok(lsp::ApplyWorkspaceEditResponse { applied: true, failed_change: None, failure_reason: None, @@ -3803,7 +3801,7 @@ impl Project { pub fn update_diagnostics( &mut self, language_server_id: LanguageServerId, - mut params: lsp2::PublishDiagnosticsParams, + mut params: lsp::PublishDiagnosticsParams, disk_based_sources: &[String], cx: &mut ModelContext, ) -> Result<()> { @@ -3822,8 +3820,8 @@ impl Project { for diagnostic in ¶ms.diagnostics { let source = diagnostic.source.as_ref(); let code = diagnostic.code.as_ref().map(|code| match code { - lsp2::NumberOrString::Number(code) => code.to_string(), - lsp2::NumberOrString::String(code) => code.clone(), + lsp::NumberOrString::Number(code) => code.to_string(), + lsp::NumberOrString::String(code) => code.clone(), }); let range = range_from_lsp(diagnostic.range); let is_supporting = diagnostic @@ -4378,9 +4376,9 @@ impl Project { tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { - let uri = lsp2::Url::from_file_path(abs_path) + let uri = lsp::Url::from_file_path(abs_path) .map_err(|_| anyhow!("failed to convert abs path to uri"))?; - let text_document = lsp2::TextDocumentIdentifier::new(uri); + let text_document = lsp::TextDocumentIdentifier::new(uri); let capabilities = &language_server.capabilities(); let formatting_provider = capabilities.document_formatting_provider.as_ref(); @@ -4388,20 +4386,20 @@ impl Project { let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) { language_server - .request::(lsp2::DocumentFormattingParams { + .request::(lsp::DocumentFormattingParams { text_document, options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) .await? } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) { - let buffer_start = lsp2::Position::new(0, 0); + let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.update(cx, |b, _| point_to_lsp(b.max_point_utf16()))?; language_server - .request::(lsp2::DocumentRangeFormattingParams { + .request::(lsp::DocumentRangeFormattingParams { text_document, - range: lsp2::Range::new(buffer_start, buffer_end), + range: lsp::Range::new(buffer_start, buffer_end), options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) @@ -4564,8 +4562,8 @@ impl Project { requests.push( server - .request::( - lsp2::WorkspaceSymbolParams { + .request::( + lsp::WorkspaceSymbolParams { query: query.to_string(), ..Default::default() }, @@ -4573,12 +4571,12 @@ impl Project { .log_err() .map(move |response| { let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { - lsp2::WorkspaceSymbolResponse::Flat(flat_responses) => { + lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { flat_responses.into_iter().map(|lsp_symbol| { (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) }).collect::>() } - lsp2::WorkspaceSymbolResponse::Nested(nested_responses) => { + lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { nested_responses.into_iter().filter_map(|lsp_symbol| { let location = match lsp_symbol.location { OneOf::Left(location) => location, @@ -4728,7 +4726,7 @@ impl Project { return Task::ready(Err(anyhow!("worktree not found for symbol"))); }; let symbol_abs_path = worktree_abs_path.join(&symbol.path.path); - let symbol_uri = if let Ok(uri) = lsp2::Url::from_file_path(symbol_abs_path) { + let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) { uri } else { return Task::ready(Err(anyhow!("invalid symbol path"))); @@ -4852,7 +4850,7 @@ impl Project { .unwrap_or(false); let additional_text_edits = if can_resolve { lang_server - .request::(completion.lsp_completion) + .request::(completion.lsp_completion) .await? .additional_text_edits } else { @@ -4911,12 +4909,12 @@ impl Project { .request(proto::ApplyCompletionAdditionalEdits { project_id, buffer_id, - completion: Some(language2::proto::serialize_completion(&completion)), + completion: Some(language::proto::serialize_completion(&completion)), }) .await?; if let Some(transaction) = response.transaction { - let transaction = language2::proto::deserialize_transaction(transaction)?; + let transaction = language::proto::deserialize_transaction(transaction)?; buffer_handle .update(&mut cx, |buffer, _| { buffer.wait_for_edits(transaction.edit_ids.iter().copied()) @@ -4981,7 +4979,7 @@ impl Project { { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); action.lsp_action = lang_server - .request::(action.lsp_action) + .request::(action.lsp_action) .await?; } else { let actions = this @@ -5017,7 +5015,7 @@ impl Project { })?; let result = lang_server - .request::(lsp2::ExecuteCommandParams { + .request::(lsp::ExecuteCommandParams { command: command.command, arguments: command.arguments.unwrap_or_default(), ..Default::default() @@ -5043,7 +5041,7 @@ impl Project { let request = proto::ApplyCodeAction { project_id, buffer_id: buffer_handle.read(cx).remote_id(), - action: Some(language2::proto::serialize_code_action(&action)), + action: Some(language::proto::serialize_code_action(&action)), }; cx.spawn(move |this, mut cx| async move { let response = client @@ -5115,7 +5113,7 @@ impl Project { .request(request) .await? .transaction - .map(language2::proto::deserialize_transaction) + .map(language::proto::deserialize_transaction) .transpose() }) } else { @@ -5126,7 +5124,7 @@ impl Project { async fn deserialize_edits( this: Model, buffer_to_edit: Model, - edits: Vec, + edits: Vec, push_to_history: bool, _: Arc, language_server: Arc, @@ -5167,7 +5165,7 @@ impl Project { async fn deserialize_workspace_edit( this: Model, - edit: lsp2::WorkspaceEdit, + edit: lsp::WorkspaceEdit, push_to_history: bool, lsp_adapter: Arc, language_server: Arc, @@ -5177,15 +5175,15 @@ impl Project { let mut operations = Vec::new(); if let Some(document_changes) = edit.document_changes { match document_changes { - lsp2::DocumentChanges::Edits(edits) => { - operations.extend(edits.into_iter().map(lsp2::DocumentChangeOperation::Edit)) + lsp::DocumentChanges::Edits(edits) => { + operations.extend(edits.into_iter().map(lsp::DocumentChangeOperation::Edit)) } - lsp2::DocumentChanges::Operations(ops) => operations = ops, + lsp::DocumentChanges::Operations(ops) => operations = ops, } } else if let Some(changes) = edit.changes { operations.extend(changes.into_iter().map(|(uri, edits)| { - lsp2::DocumentChangeOperation::Edit(lsp2::TextDocumentEdit { - text_document: lsp2::OptionalVersionedTextDocumentIdentifier { + lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit { + text_document: lsp::OptionalVersionedTextDocumentIdentifier { uri, version: None, }, @@ -5197,7 +5195,7 @@ impl Project { let mut project_transaction = ProjectTransaction::default(); for operation in operations { match operation { - lsp2::DocumentChangeOperation::Op(lsp2::ResourceOp::Create(op)) => { + lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => { let abs_path = op .uri .to_file_path() @@ -5212,7 +5210,7 @@ impl Project { fs.create_file( &abs_path, op.options - .map(|options| fs2::CreateOptions { + .map(|options| fs::CreateOptions { overwrite: options.overwrite.unwrap_or(false), ignore_if_exists: options.ignore_if_exists.unwrap_or(false), }) @@ -5222,7 +5220,7 @@ impl Project { } } - lsp2::DocumentChangeOperation::Op(lsp2::ResourceOp::Rename(op)) => { + lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => { let source_abs_path = op .old_uri .to_file_path() @@ -5235,7 +5233,7 @@ impl Project { &source_abs_path, &target_abs_path, op.options - .map(|options| fs2::RenameOptions { + .map(|options| fs::RenameOptions { overwrite: options.overwrite.unwrap_or(false), ignore_if_exists: options.ignore_if_exists.unwrap_or(false), }) @@ -5244,14 +5242,14 @@ impl Project { .await?; } - lsp2::DocumentChangeOperation::Op(lsp2::ResourceOp::Delete(op)) => { + lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => { let abs_path = op .uri .to_file_path() .map_err(|_| anyhow!("can't convert URI to path"))?; let options = op .options - .map(|options| fs2::RemoveOptions { + .map(|options| fs::RemoveOptions { recursive: options.recursive.unwrap_or(false), ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false), }) @@ -5263,7 +5261,7 @@ impl Project { } } - lsp2::DocumentChangeOperation::Edit(op) => { + lsp::DocumentChangeOperation::Edit(op) => { let buffer_to_edit = this .update(cx, |this, cx| { this.open_local_buffer_via_lsp( @@ -5466,7 +5464,7 @@ impl Project { let buffer_snapshot = buffer.snapshot(); cx.spawn(move |_, mut cx| async move { - let resolve_task = lang_server.request::( + let resolve_task = lang_server.request::( InlayHints::project_to_lsp_hint(hint, &buffer_snapshot), ); let resolved_hint = resolve_task @@ -5846,8 +5844,8 @@ impl Project { cx: &mut ModelContext, ) -> Task> where - ::Result: Send, - ::Params: Send, + ::Result: Send, + ::Params: Send, { let buffer = buffer_handle.read(cx); if self.is_local() { @@ -6281,7 +6279,7 @@ impl Project { }) = self.language_servers.get(server_id) { if let Some(watched_paths) = watched_paths.get(&worktree_id) { - let params = lsp2::DidChangeWatchedFilesParams { + let params = lsp::DidChangeWatchedFilesParams { changes: changes .iter() .filter_map(|(path, _, change)| { @@ -6290,13 +6288,13 @@ impl Project { } let typ = match change { PathChange::Loaded => return None, - PathChange::Added => lsp2::FileChangeType::CREATED, - PathChange::Removed => lsp2::FileChangeType::DELETED, - PathChange::Updated => lsp2::FileChangeType::CHANGED, - PathChange::AddedOrUpdated => lsp2::FileChangeType::CHANGED, + PathChange::Added => lsp::FileChangeType::CREATED, + PathChange::Removed => lsp::FileChangeType::DELETED, + PathChange::Updated => lsp::FileChangeType::CHANGED, + PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, }; - Some(lsp2::FileEvent { - uri: lsp2::Url::from_file_path(abs_path.join(path)).unwrap(), + Some(lsp::FileEvent { + uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), typ, }) }) @@ -6305,7 +6303,7 @@ impl Project { if !params.changes.is_empty() { server - .notify::(params) + .notify::(params) .log_err(); } } @@ -7080,7 +7078,7 @@ impl Project { let ops = payload .operations .into_iter() - .map(language2::proto::deserialize_operation) + .map(language::proto::deserialize_operation) .collect::, _>>()?; let is_remote = this.is_remote(); match this.opened_buffers.entry(buffer_id) { @@ -7124,7 +7122,7 @@ impl Project { anyhow!("no worktree found for id {}", file.worktree_id) })?; buffer_file = Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?) - as Arc); + as Arc); } let buffer_id = state.id; @@ -7149,7 +7147,7 @@ impl Project { let operations = chunk .operations .into_iter() - .map(language2::proto::deserialize_operation) + .map(language::proto::deserialize_operation) .collect::>>()?; buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?; @@ -7255,9 +7253,7 @@ impl Project { buffer_id, version: serialize_version(buffer.saved_version()), mtime: Some(buffer.saved_mtime().into()), - fingerprint: language2::proto::serialize_fingerprint( - buffer.saved_version_fingerprint(), - ), + fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()), })?) } @@ -7310,7 +7306,7 @@ impl Project { this.shared_buffers.entry(guest_id).or_default().clear(); for buffer in envelope.payload.buffers { let buffer_id = buffer.id; - let remote_version = language2::proto::deserialize_version(&buffer.version); + let remote_version = language::proto::deserialize_version(&buffer.version); if let Some(buffer) = this.buffer_for_id(buffer_id) { this.shared_buffers .entry(guest_id) @@ -7320,7 +7316,7 @@ impl Project { let buffer = buffer.read(cx); response.buffers.push(proto::BufferVersion { id: buffer_id, - version: language2::proto::serialize_version(&buffer.version), + version: language::proto::serialize_version(&buffer.version), }); let operations = buffer.serialize_ops(Some(remote_version), cx); @@ -7347,12 +7343,12 @@ impl Project { .send(proto::BufferReloaded { project_id, buffer_id, - version: language2::proto::serialize_version(buffer.saved_version()), + version: language::proto::serialize_version(buffer.saved_version()), mtime: Some(buffer.saved_mtime().into()), - fingerprint: language2::proto::serialize_fingerprint( + fingerprint: language::proto::serialize_fingerprint( buffer.saved_version_fingerprint(), ), - line_ending: language2::proto::serialize_line_ending( + line_ending: language::proto::serialize_line_ending( buffer.line_ending(), ) as i32, }) @@ -7426,7 +7422,7 @@ impl Project { .and_then(|buffer| buffer.upgrade()) .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?; let language = buffer.read(cx).language(); - let completion = language2::proto::deserialize_completion( + let completion = language::proto::deserialize_completion( envelope .payload .completion @@ -7446,7 +7442,7 @@ impl Project { transaction: apply_additional_edits .await? .as_ref() - .map(language2::proto::serialize_transaction), + .map(language::proto::serialize_transaction), }) } @@ -7457,7 +7453,7 @@ impl Project { mut cx: AsyncAppContext, ) -> Result { let sender_id = envelope.original_sender_id()?; - let action = language2::proto::deserialize_code_action( + let action = language::proto::deserialize_code_action( envelope .payload .action @@ -7509,7 +7505,7 @@ impl Project { let transaction = on_type_formatting .await? .as_ref() - .map(language2::proto::serialize_transaction); + .map(language::proto::serialize_transaction); Ok(proto::OnTypeFormattingResponse { transaction }) } @@ -7616,8 +7612,8 @@ impl Project { mut cx: AsyncAppContext, ) -> Result<::Response> where - ::Params: Send, - ::Result: Send, + ::Params: Send, + ::Result: Send, { let sender_id = envelope.original_sender_id()?; let buffer_id = T::buffer_id_from_proto(&envelope.payload); @@ -7801,7 +7797,7 @@ impl Project { .push(self.create_buffer_for_peer(&buffer, peer_id, cx)); serialized_transaction .transactions - .push(language2::proto::serialize_transaction(&transaction)); + .push(language::proto::serialize_transaction(&transaction)); } serialized_transaction } @@ -7821,7 +7817,7 @@ impl Project { this.wait_for_remote_buffer(buffer_id, cx) })? .await?; - let transaction = language2::proto::deserialize_transaction(transaction)?; + let transaction = language::proto::deserialize_transaction(transaction)?; project_transaction.0.insert(buffer, transaction); } @@ -7930,7 +7926,7 @@ impl Project { let buffer = buffer.upgrade()?; Some(proto::BufferVersion { id: *id, - version: language2::proto::serialize_version(&buffer.read(cx).version), + version: language::proto::serialize_version(&buffer.read(cx).version), }) }) .collect(); @@ -7956,7 +7952,7 @@ impl Project { .map(|buffer| { let client = client.clone(); let buffer_id = buffer.id; - let remote_version = language2::proto::deserialize_version(&buffer.version); + let remote_version = language::proto::deserialize_version(&buffer.version); if let Some(buffer) = this.buffer_for_id(buffer_id) { let operations = buffer.read(cx).serialize_ops(Some(remote_version), cx); @@ -8192,7 +8188,7 @@ impl Project { fn edits_from_lsp( &mut self, buffer: &Model, - lsp_edits: impl 'static + Send + IntoIterator, + lsp_edits: impl 'static + Send + IntoIterator, server_id: LanguageServerId, version: Option, cx: &mut ModelContext, @@ -8669,10 +8665,10 @@ impl Project { } cx.spawn(move |_| async move { - let prettier_wrapper_path = default_prettier_dir.join(prettier2::PRETTIER_SERVER_FILE); + let prettier_wrapper_path = default_prettier_dir.join(prettier::PRETTIER_SERVER_FILE); // method creates parent directory if it doesn't exist - fs.save(&prettier_wrapper_path, &text::Rope::from(prettier2::PRETTIER_SERVER_JS), text::LineEnding::Unix).await - .with_context(|| format!("writing {} file at {prettier_wrapper_path:?}", prettier2::PRETTIER_SERVER_FILE))?; + fs.save(&prettier_wrapper_path, &text::Rope::from(prettier::PRETTIER_SERVER_JS), text::LineEnding::Unix).await + .with_context(|| format!("writing {} file at {prettier_wrapper_path:?}", prettier::PRETTIER_SERVER_FILE))?; let packages_to_versions = future::try_join_all( plugins_to_install @@ -8714,19 +8710,19 @@ impl Project { fn subscribe_for_copilot_events( copilot: &Model, cx: &mut ModelContext<'_, Project>, -) -> gpui2::Subscription { +) -> gpui::Subscription { cx.subscribe( copilot, |project, copilot, copilot_event, cx| match copilot_event { - copilot2::Event::CopilotLanguageServerStarted => { + copilot::Event::CopilotLanguageServerStarted => { match copilot.read(cx).language_server() { Some((name, copilot_server)) => { // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again. - if !copilot_server.has_notification_handler::() { + if !copilot_server.has_notification_handler::() { let new_server_id = copilot_server.server_id(); let weak_project = cx.weak_model(); let copilot_log_subscription = copilot_server - .on_notification::( + .on_notification::( move |params, mut cx| { weak_project.update(&mut cx, |_, cx| { cx.emit(Event::LanguageServerLog( @@ -8796,7 +8792,7 @@ pub struct PathMatchCandidateSet { pub include_root_name: bool, } -impl<'a> fuzzy2::PathMatchCandidateSet<'a> for PathMatchCandidateSet { +impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet { type Candidates = PathMatchCandidateSetIter<'a>; fn id(&self) -> usize { @@ -8833,12 +8829,12 @@ pub struct PathMatchCandidateSetIter<'a> { } impl<'a> Iterator for PathMatchCandidateSetIter<'a> { - type Item = fuzzy2::PathMatchCandidate<'a>; + type Item = fuzzy::PathMatchCandidate<'a>; fn next(&mut self) -> Option { self.traversal.next().map(|entry| { if let EntryKind::File(char_bag) = entry.kind { - fuzzy2::PathMatchCandidate { + fuzzy::PathMatchCandidate { path: &entry.path, char_bag, } @@ -8958,18 +8954,18 @@ async fn wait_for_loading_buffer( } } -fn include_text(server: &lsp2::LanguageServer) -> bool { +fn include_text(server: &lsp::LanguageServer) -> bool { server .capabilities() .text_document_sync .as_ref() .and_then(|sync| match sync { - lsp2::TextDocumentSyncCapability::Kind(_) => None, - lsp2::TextDocumentSyncCapability::Options(options) => options.save.as_ref(), + lsp::TextDocumentSyncCapability::Kind(_) => None, + lsp::TextDocumentSyncCapability::Options(options) => options.save.as_ref(), }) .and_then(|save_options| match save_options { - lsp2::TextDocumentSyncSaveOptions::Supported(_) => None, - lsp2::TextDocumentSyncSaveOptions::SaveOptions(options) => options.include_text, + lsp::TextDocumentSyncSaveOptions::Supported(_) => None, + lsp::TextDocumentSyncSaveOptions::SaveOptions(options) => options.include_text, }) .unwrap_or(false) } diff --git a/crates/project2/src/project_settings.rs b/crates/project2/src/project_settings.rs index b85226f7cc..028a564b9c 100644 --- a/crates/project2/src/project_settings.rs +++ b/crates/project2/src/project_settings.rs @@ -1,8 +1,8 @@ use collections::HashMap; -use gpui2::AppContext; +use gpui::AppContext; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; use std::sync::Arc; #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 5a2f82c375..490b3a0788 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -1,13 +1,13 @@ use crate::{search::PathMatcher, Event, *}; -use fs2::FakeFs; +use fs::FakeFs; use futures::{future, StreamExt}; -use gpui2::AppContext; -use language2::{ +use gpui::AppContext; +use language::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, LineEnding, OffsetRangeExt, Point, ToPoint, }; -use lsp2::Url; +use lsp::Url; use parking_lot::Mutex; use pretty_assertions::assert_eq; use serde_json::json; @@ -15,8 +15,8 @@ use std::{os, task::Poll}; use unindent::Unindent as _; use util::{assert_set_eq, test::temp_tree}; -#[gpui2::test] -async fn test_block_via_channel(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_block_via_channel(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); let (tx, mut rx) = futures::channel::mpsc::unbounded(); @@ -28,8 +28,8 @@ async fn test_block_via_channel(cx: &mut gpui2::TestAppContext) { rx.next().await.unwrap(); } -#[gpui2::test] -async fn test_block_via_smol(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_block_via_smol(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); let io_task = smol::unblock(move || { @@ -45,8 +45,8 @@ async fn test_block_via_smol(cx: &mut gpui2::TestAppContext) { task.await; } -#[gpui2::test] -async fn test_symlinks(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_symlinks(cx: &mut gpui::TestAppContext) { init_test(cx); cx.executor().allow_parking(); @@ -85,8 +85,8 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test] -async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -142,8 +142,8 @@ async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) }); } -#[gpui2::test] -async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { init_test(cx); let mut rust_language = Language::new( @@ -165,8 +165,8 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { let mut fake_rust_servers = rust_language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "the-rust-language-server", - capabilities: lsp2::ServerCapabilities { - completion_provider: Some(lsp2::CompletionOptions { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string(), "::".to_string()]), ..Default::default() }), @@ -178,8 +178,8 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { let mut fake_json_servers = json_language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "the-json-language-server", - capabilities: lsp2::ServerCapabilities { - completion_provider: Some(lsp2::CompletionOptions { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..Default::default() }), @@ -237,11 +237,11 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), version: 0, text: "const A: i32 = 1;".to_string(), language_id: Default::default() @@ -263,11 +263,11 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx)); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::VersionedTextDocumentIdentifier::new( - lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), + lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test.rs").unwrap(), 1 ) ); @@ -284,11 +284,11 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { let mut fake_json_server = fake_json_servers.next().await.unwrap(); assert_eq!( fake_json_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), version: 0, text: "{\"a\": 1}".to_string(), language_id: Default::default() @@ -323,11 +323,11 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { }); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::VersionedTextDocumentIdentifier::new( - lsp2::Url::from_file_path("/the-root/test2.rs").unwrap(), + lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test2.rs").unwrap(), 1 ) ); @@ -339,21 +339,17 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { .unwrap(); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentIdentifier::new( - lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap() - ) + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap()) ); assert_eq!( fake_json_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentIdentifier::new( - lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap() - ) + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap()) ); // Renames are reported only to servers matching the buffer's language. @@ -366,18 +362,18 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { .unwrap(); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test2.rs").unwrap()), + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test2.rs").unwrap()), ); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(), version: 0, text: rust_buffer2.update(cx, |buffer, _| buffer.text()), language_id: Default::default() @@ -416,18 +412,18 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { .unwrap(); assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(),), + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),), ); assert_eq!( fake_json_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(), version: 0, text: rust_buffer2.update(cx, |buffer, _| buffer.text()), language_id: Default::default() @@ -449,11 +445,11 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx)); assert_eq!( fake_json_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::VersionedTextDocumentIdentifier::new( - lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), + lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test3.json").unwrap(), 1 ) ); @@ -467,9 +463,9 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { }); let mut rust_shutdown_requests = fake_rust_server - .handle_request::(|_, _| future::ready(Ok(()))); + .handle_request::(|_, _| future::ready(Ok(()))); let mut json_shutdown_requests = fake_json_server - .handle_request::(|_, _| future::ready(Ok(()))); + .handle_request::(|_, _| future::ready(Ok(()))); futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next()); let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); @@ -478,11 +474,11 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { // Ensure rust document is reopened in new rust language server assert_eq!( fake_rust_server - .receive_notification::() + .receive_notification::() .await .text_document, - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), version: 0, text: rust_buffer.update(cx, |buffer, _| buffer.text()), language_id: Default::default() @@ -493,23 +489,23 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { assert_set_eq!( [ fake_json_server - .receive_notification::() + .receive_notification::() .await .text_document, fake_json_server - .receive_notification::() + .receive_notification::() .await .text_document, ], [ - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), version: 0, text: json_buffer.update(cx, |buffer, _| buffer.text()), language_id: Default::default() }, - lsp2::TextDocumentItem { - uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(), version: 0, text: rust_buffer2.update(cx, |buffer, _| buffer.text()), language_id: Default::default() @@ -519,21 +515,21 @@ async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { // Close notifications are reported only to servers matching the buffer's language. cx.update(|_| drop(json_buffer)); - let close_message = lsp2::DidCloseTextDocumentParams { - text_document: lsp2::TextDocumentIdentifier::new( - lsp2::Url::from_file_path("/the-root/package.json").unwrap(), + let close_message = lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/package.json").unwrap(), ), }; assert_eq!( fake_json_server - .receive_notification::() + .receive_notification::() .await, close_message, ); } -#[gpui2::test] -async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -622,27 +618,27 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui2::TestAppCo let fake_server = fake_servers.next().await.unwrap(); let file_changes = Arc::new(Mutex::new(Vec::new())); fake_server - .request::(lsp2::RegistrationParams { - registrations: vec![lsp2::Registration { + .request::(lsp::RegistrationParams { + registrations: vec![lsp::Registration { id: Default::default(), method: "workspace/didChangeWatchedFiles".to_string(), register_options: serde_json::to_value( - lsp2::DidChangeWatchedFilesRegistrationOptions { + lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![ - lsp2::FileSystemWatcher { - glob_pattern: lsp2::GlobPattern::String( + lsp::FileSystemWatcher { + glob_pattern: lsp::GlobPattern::String( "/the-root/Cargo.toml".to_string(), ), kind: None, }, - lsp2::FileSystemWatcher { - glob_pattern: lsp2::GlobPattern::String( + lsp::FileSystemWatcher { + glob_pattern: lsp::GlobPattern::String( "/the-root/src/*.{rs,c}".to_string(), ), kind: None, }, - lsp2::FileSystemWatcher { - glob_pattern: lsp2::GlobPattern::String( + lsp::FileSystemWatcher { + glob_pattern: lsp::GlobPattern::String( "/the-root/target/y/**/*.rs".to_string(), ), kind: None, @@ -655,7 +651,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui2::TestAppCo }) .await .unwrap(); - fake_server.handle_notification::({ + fake_server.handle_notification::({ let file_changes = file_changes.clone(); move |params, _| { let mut file_changes = file_changes.lock(); @@ -718,24 +714,24 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui2::TestAppCo assert_eq!( &*file_changes.lock(), &[ - lsp2::FileEvent { - uri: lsp2::Url::from_file_path("/the-root/src/b.rs").unwrap(), - typ: lsp2::FileChangeType::DELETED, + lsp::FileEvent { + uri: lsp::Url::from_file_path("/the-root/src/b.rs").unwrap(), + typ: lsp::FileChangeType::DELETED, }, - lsp2::FileEvent { - uri: lsp2::Url::from_file_path("/the-root/src/c.rs").unwrap(), - typ: lsp2::FileChangeType::CREATED, + lsp::FileEvent { + uri: lsp::Url::from_file_path("/the-root/src/c.rs").unwrap(), + typ: lsp::FileChangeType::CREATED, }, - lsp2::FileEvent { - uri: lsp2::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(), - typ: lsp2::FileChangeType::CREATED, + lsp::FileEvent { + uri: lsp::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(), + typ: lsp::FileChangeType::CREATED, }, ] ); } -#[gpui2::test] -async fn test_single_file_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -763,15 +759,12 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) project .update_diagnostics( LanguageServerId(0), - lsp2::PublishDiagnosticsParams { + lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/dir/a.rs").unwrap(), version: None, - diagnostics: vec![lsp2::Diagnostic { - range: lsp2::Range::new( - lsp2::Position::new(0, 4), - lsp2::Position::new(0, 5), - ), - severity: Some(lsp2::DiagnosticSeverity::ERROR), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)), + severity: Some(lsp::DiagnosticSeverity::ERROR), message: "error 1".to_string(), ..Default::default() }], @@ -783,15 +776,12 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) project .update_diagnostics( LanguageServerId(0), - lsp2::PublishDiagnosticsParams { + lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/dir/b.rs").unwrap(), version: None, - diagnostics: vec![lsp2::Diagnostic { - range: lsp2::Range::new( - lsp2::Position::new(0, 4), - lsp2::Position::new(0, 5), - ), - severity: Some(lsp2::DiagnosticSeverity::WARNING), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)), + severity: Some(lsp::DiagnosticSeverity::WARNING), message: "error 2".to_string(), ..Default::default() }], @@ -832,8 +822,8 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) }); } -#[gpui2::test] -async fn test_hidden_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -862,15 +852,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) { project .update_diagnostics( LanguageServerId(0), - lsp2::PublishDiagnosticsParams { + lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/root/other.rs").unwrap(), version: None, - diagnostics: vec![lsp2::Diagnostic { - range: lsp2::Range::new( - lsp2::Position::new(0, 8), - lsp2::Position::new(0, 9), - ), - severity: Some(lsp2::DiagnosticSeverity::ERROR), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 9)), + severity: Some(lsp::DiagnosticSeverity::ERROR), message: "unknown variable 'c'".to_string(), ..Default::default() }], @@ -906,8 +893,8 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test] -async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { init_test(cx); let progress_token = "the-progress-token"; @@ -965,12 +952,12 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { } ); - fake_server.notify::(lsp2::PublishDiagnosticsParams { + fake_server.notify::(lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/dir/a.rs").unwrap(), version: None, - diagnostics: vec![lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)), - severity: Some(lsp2::DiagnosticSeverity::ERROR), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(lsp::DiagnosticSeverity::ERROR), message: "undefined variable 'A'".to_string(), ..Default::default() }], @@ -1006,7 +993,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { &[DiagnosticEntry { range: Point::new(0, 9)..Point::new(0, 10), diagnostic: Diagnostic { - severity: lsp2::DiagnosticSeverity::ERROR, + severity: lsp::DiagnosticSeverity::ERROR, message: "undefined variable 'A'".to_string(), group_id: 0, is_primary: true, @@ -1017,7 +1004,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { }); // Ensure publishing empty diagnostics twice only results in one update event. - fake_server.notify::(lsp2::PublishDiagnosticsParams { + fake_server.notify::(lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/dir/a.rs").unwrap(), version: None, diagnostics: Default::default(), @@ -1030,7 +1017,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { } ); - fake_server.notify::(lsp2::PublishDiagnosticsParams { + fake_server.notify::(lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/dir/a.rs").unwrap(), version: None, diagnostics: Default::default(), @@ -1039,8 +1026,8 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { assert_eq!(futures::poll!(events.next()), Poll::Pending); } -#[gpui2::test] -async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) { init_test(cx); let progress_token = "the-progress-token"; @@ -1121,8 +1108,8 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui2::TestApp }); } -#[gpui2::test] -async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -1151,12 +1138,12 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui2::TestA // Publish diagnostics let fake_server = fake_servers.next().await.unwrap(); - fake_server.notify::(lsp2::PublishDiagnosticsParams { + fake_server.notify::(lsp::PublishDiagnosticsParams { uri: Url::from_file_path("/dir/a.rs").unwrap(), version: None, - diagnostics: vec![lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(0, 0), lsp2::Position::new(0, 0)), - severity: Some(lsp2::DiagnosticSeverity::ERROR), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), + severity: Some(lsp::DiagnosticSeverity::ERROR), message: "the message".to_string(), ..Default::default() }], @@ -1210,8 +1197,8 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui2::TestA }); } -#[gpui2::test] -async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -1241,8 +1228,8 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui2:: // Before restarting the server, report diagnostics with an unknown buffer version. let fake_server = fake_servers.next().await.unwrap(); - fake_server.notify::(lsp2::PublishDiagnosticsParams { - uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(), + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), version: Some(10000), diagnostics: Vec::new(), }); @@ -1253,14 +1240,14 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui2:: }); let mut fake_server = fake_servers.next().await.unwrap(); let notification = fake_server - .receive_notification::() + .receive_notification::() .await .text_document; assert_eq!(notification.version, 0); } -#[gpui2::test] -async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { init_test(cx); let mut rust = Language::new( @@ -1314,7 +1301,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap(); assert_eq!( fake_rust_server_1 - .receive_notification::() + .receive_notification::() .await .text_document .uri @@ -1325,7 +1312,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { let mut fake_js_server = fake_js_servers.next().await.unwrap(); assert_eq!( fake_js_server - .receive_notification::() + .receive_notification::() .await .text_document .uri @@ -1348,7 +1335,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { }) }); fake_rust_server_1 - .receive_notification::() + .receive_notification::() .await; // Enable Rust and disable JavaScript language servers, ensuring that the @@ -1376,7 +1363,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); assert_eq!( fake_rust_server_2 - .receive_notification::() + .receive_notification::() .await .text_document .uri @@ -1384,12 +1371,12 @@ async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { "file:///dir/a.rs" ); fake_js_server - .receive_notification::() + .receive_notification::() .await; } -#[gpui2::test(iterations = 3)] -async fn test_transforming_diagnostics(cx: &mut gpui2::TestAppContext) { +#[gpui::test(iterations = 3)] +async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -1427,37 +1414,37 @@ async fn test_transforming_diagnostics(cx: &mut gpui2::TestAppContext) { let mut fake_server = fake_servers.next().await.unwrap(); let open_notification = fake_server - .receive_notification::() + .receive_notification::() .await; // Edit the buffer, moving the content down buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], None, cx)); let change_notification_1 = fake_server - .receive_notification::() + .receive_notification::() .await; assert!(change_notification_1.text_document.version > open_notification.text_document.version); // Report some diagnostics for the initial version of the buffer - fake_server.notify::(lsp2::PublishDiagnosticsParams { - uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(), + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), version: Some(open_notification.text_document.version), diagnostics: vec![ - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), severity: Some(DiagnosticSeverity::ERROR), message: "undefined variable 'A'".to_string(), source: Some("disk".to_string()), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(1, 9), lsp2::Position::new(1, 11)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)), severity: Some(DiagnosticSeverity::ERROR), message: "undefined variable 'BB'".to_string(), source: Some("disk".to_string()), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(2, 9), lsp2::Position::new(2, 12)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)), severity: Some(DiagnosticSeverity::ERROR), source: Some("disk".to_string()), message: "undefined variable 'CCC'".to_string(), @@ -1524,19 +1511,19 @@ async fn test_transforming_diagnostics(cx: &mut gpui2::TestAppContext) { }); // Ensure overlapping diagnostics are highlighted correctly. - fake_server.notify::(lsp2::PublishDiagnosticsParams { - uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(), + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), version: Some(open_notification.text_document.version), diagnostics: vec![ - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), severity: Some(DiagnosticSeverity::ERROR), message: "undefined variable 'A'".to_string(), source: Some("disk".to_string()), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 12)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)), severity: Some(DiagnosticSeverity::WARNING), message: "unreachable statement".to_string(), source: Some("disk".to_string()), @@ -1609,26 +1596,26 @@ async fn test_transforming_diagnostics(cx: &mut gpui2::TestAppContext) { buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx); }); let change_notification_2 = fake_server - .receive_notification::() + .receive_notification::() .await; assert!( change_notification_2.text_document.version > change_notification_1.text_document.version ); // Handle out-of-order diagnostics - fake_server.notify::(lsp2::PublishDiagnosticsParams { - uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(), + fake_server.notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), version: Some(change_notification_2.text_document.version), diagnostics: vec![ - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(1, 9), lsp2::Position::new(1, 11)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)), severity: Some(DiagnosticSeverity::ERROR), message: "undefined variable 'BB'".to_string(), source: Some("disk".to_string()), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), severity: Some(DiagnosticSeverity::WARNING), message: "undefined variable 'A'".to_string(), source: Some("disk".to_string()), @@ -1674,8 +1661,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test] -async fn test_empty_diagnostic_ranges(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { init_test(cx); let text = concat!( @@ -1743,8 +1730,8 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test] -async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -1799,8 +1786,8 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui2::TestApp }); } -#[gpui2::test] -async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -1844,7 +1831,7 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui2::TestAppContext) let mut fake_server = fake_servers.next().await.unwrap(); let lsp_document_version = fake_server - .receive_notification::() + .receive_notification::() .await .text_document .version; @@ -1901,11 +1888,8 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui2::TestAppContext) &buffer, vec![ // replace body of first function - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 0), - lsp2::Position::new(3, 0), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(3, 0)), new_text: " fn a() { f10(); @@ -1914,26 +1898,17 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui2::TestAppContext) .unindent(), }, // edit inside second function - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(4, 6), - lsp2::Position::new(4, 6), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(4, 6), lsp::Position::new(4, 6)), new_text: "00".into(), }, // edit inside third function via two distinct edits - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(7, 5), - lsp2::Position::new(7, 5), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 5)), new_text: "4000".into(), }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(7, 5), - lsp2::Position::new(7, 6), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 6)), new_text: "".into(), }, ], @@ -1969,8 +1944,8 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui2::TestAppContext) }); } -#[gpui2::test] -async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) { init_test(cx); let text = " @@ -2007,27 +1982,18 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui2::TestA &buffer, [ // Replace the first use statement without editing the semicolon. - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 4), - lsp2::Position::new(0, 8), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 8)), new_text: "a::{b, c}".into(), }, // Reinsert the remainder of the file between the semicolon and the final // newline of the file. - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 9), - lsp2::Position::new(0, 9), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)), new_text: "\n\n".into(), }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 9), - lsp2::Position::new(0, 9), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)), new_text: " fn f() { b(); @@ -2036,11 +2002,8 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui2::TestA .unindent(), }, // Delete everything after the first newline of the file. - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(1, 0), - lsp2::Position::new(7, 0), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(7, 0)), new_text: "".into(), }, ], @@ -2089,8 +2052,8 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui2::TestA }); } -#[gpui2::test] -async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) { init_test(cx); let text = " @@ -2126,32 +2089,20 @@ async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) { project.edits_from_lsp( &buffer, [ - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 9), - lsp2::Position::new(0, 9), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)), new_text: "\n\n".into(), }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 8), - lsp2::Position::new(0, 4), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 4)), new_text: "a::{b, c}".into(), }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(1, 0), - lsp2::Position::new(99, 0), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(99, 0)), new_text: "".into(), }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 9), - lsp2::Position::new(0, 9), - ), + lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)), new_text: " fn f() { b(); @@ -2222,8 +2173,8 @@ fn chunks_with_diagnostics( chunks } -#[gpui2::test(iterations = 10)] -async fn test_definition(cx: &mut gpui2::TestAppContext) { +#[gpui::test(iterations = 10)] +async fn test_definition(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -2255,18 +2206,18 @@ async fn test_definition(cx: &mut gpui2::TestAppContext) { .unwrap(); let fake_server = fake_servers.next().await.unwrap(); - fake_server.handle_request::(|params, _| async move { + fake_server.handle_request::(|params, _| async move { let params = params.text_document_position_params; assert_eq!( params.text_document.uri.to_file_path().unwrap(), Path::new("/dir/b.rs"), ); - assert_eq!(params.position, lsp2::Position::new(0, 22)); + assert_eq!(params.position, lsp::Position::new(0, 22)); - Ok(Some(lsp2::GotoDefinitionResponse::Scalar( - lsp2::Location::new( - lsp2::Url::from_file_path("/dir/a.rs").unwrap(), - lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)), + Ok(Some(lsp::GotoDefinitionResponse::Scalar( + lsp::Location::new( + lsp::Url::from_file_path("/dir/a.rs").unwrap(), + lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), ), ))) }); @@ -2323,8 +2274,8 @@ async fn test_definition(cx: &mut gpui2::TestAppContext) { } } -#[gpui2::test] -async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -2337,8 +2288,8 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { ); let mut fake_language_servers = language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp2::ServerCapabilities { - completion_provider: Some(lsp2::CompletionOptions { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..Default::default() }), @@ -2373,9 +2324,9 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { }); fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp2::CompletionResponse::Array(vec![ - lsp2::CompletionItem { + .handle_request::(|_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { label: "fullyQualifiedName?".into(), insert_text: Some("fullyQualifiedName".into()), ..Default::default() @@ -2400,9 +2351,9 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { }); fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp2::CompletionResponse::Array(vec![ - lsp2::CompletionItem { + .handle_request::(|_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { label: "component".into(), ..Default::default() }, @@ -2420,8 +2371,8 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -2434,8 +2385,8 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) ); let mut fake_language_servers = language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp2::ServerCapabilities { - completion_provider: Some(lsp2::CompletionOptions { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..Default::default() }), @@ -2470,9 +2421,9 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) }); fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp2::CompletionResponse::Array(vec![ - lsp2::CompletionItem { + .handle_request::(|_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { label: "fullyQualifiedName?".into(), insert_text: Some("fully\rQualified\r\nName".into()), ..Default::default() @@ -2486,8 +2437,8 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) assert_eq!(completions[0].new_text, "fully\nQualified\nName"); } -#[gpui2::test(iterations = 10)] -async fn test_apply_code_actions_with_commands(cx: &mut gpui2::TestAppContext) { +#[gpui::test(iterations = 10)] +async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -2521,18 +2472,18 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui2::TestAppContext) { // Language server returns code actions that contain commands, and not edits. let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx)); fake_server - .handle_request::(|_, _| async move { + .handle_request::(|_, _| async move { Ok(Some(vec![ - lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction { + lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { title: "The code action".into(), - command: Some(lsp2::Command { + command: Some(lsp::Command { title: "The command".into(), command: "_the/command".into(), arguments: Some(vec![json!("the-argument")]), }), ..Default::default() }), - lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction { + lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { title: "two".into(), ..Default::default() }), @@ -2548,31 +2499,31 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui2::TestAppContext) { // Resolving the code action does not populate its edits. In absence of // edits, we must execute the given command. - fake_server.handle_request::( + fake_server.handle_request::( |action, _| async move { Ok(action) }, ); // While executing the command, the language server sends the editor // a `workspaceEdit` request. fake_server - .handle_request::({ + .handle_request::({ let fake = fake_server.clone(); move |params, _| { assert_eq!(params.command, "_the/command"); let fake = fake.clone(); async move { fake.server - .request::( - lsp2::ApplyWorkspaceEditParams { + .request::( + lsp::ApplyWorkspaceEditParams { label: None, - edit: lsp2::WorkspaceEdit { + edit: lsp::WorkspaceEdit { changes: Some( [( - lsp2::Url::from_file_path("/dir/a.ts").unwrap(), - vec![lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 0), - lsp2::Position::new(0, 0), + lsp::Url::from_file_path("/dir/a.ts").unwrap(), + vec![lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 0), ), new_text: "X".into(), }], @@ -2604,8 +2555,8 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test(iterations = 10)] -async fn test_save_file(cx: &mut gpui2::TestAppContext) { +#[gpui::test(iterations = 10)] +async fn test_save_file(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -2636,8 +2587,8 @@ async fn test_save_file(cx: &mut gpui2::TestAppContext) { assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text())); } -#[gpui2::test] -async fn test_save_in_single_file_worktree(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -2667,8 +2618,8 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui2::TestAppContext) { assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text())); } -#[gpui2::test] -async fn test_save_as(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_save_as(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -2726,8 +2677,8 @@ async fn test_save_as(cx: &mut gpui2::TestAppContext) { assert_eq!(opened_buffer, buffer); } -#[gpui2::test(retries = 5)] -async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { +#[gpui::test(retries = 5)] +async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) { init_test(cx); cx.executor().allow_parking(); @@ -2748,11 +2699,11 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; let rpc = project.update(cx, |p, _| p.client.clone()); - let buffer_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| { + let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx)); async move { buffer.await.unwrap() } }; - let id_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| { + let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { project.update(cx, |project, cx| { let tree = project.worktrees().next().unwrap(); tree.read(cx) @@ -2875,8 +2826,8 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test(iterations = 10)] -async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { +#[gpui::test(iterations = 10)] +async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -2894,7 +2845,7 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { let tree = project.update(cx, |project, _| project.worktrees().next().unwrap()); let tree_id = tree.update(cx, |tree, _| tree.id()); - let id_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| { + let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { project.update(cx, |project, cx| { let tree = project.worktrees().next().unwrap(); tree.read(cx) @@ -2926,8 +2877,8 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty())); } -#[gpui2::test] -async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -2972,8 +2923,8 @@ async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) { assert_eq!(buffer_a_3.entity_id(), buffer_a_id); } -#[gpui2::test] -async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -3018,7 +2969,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { assert!(buffer.is_dirty()); assert_eq!( *events.lock(), - &[language2::Event::Edited, language2::Event::DirtyChanged] + &[language::Event::Edited, language::Event::DirtyChanged] ); events.lock().clear(); buffer.did_save( @@ -3032,7 +2983,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { // after saving, the buffer is not dirty, and emits a saved event. buffer1.update(cx, |buffer, cx| { assert!(!buffer.is_dirty()); - assert_eq!(*events.lock(), &[language2::Event::Saved]); + assert_eq!(*events.lock(), &[language::Event::Saved]); events.lock().clear(); buffer.edit([(1..1, "B")], None, cx); @@ -3046,9 +2997,9 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { assert_eq!( *events.lock(), &[ - language2::Event::Edited, - language2::Event::DirtyChanged, - language2::Event::Edited, + language::Event::Edited, + language::Event::DirtyChanged, + language::Event::Edited, ], ); events.lock().clear(); @@ -3062,7 +3013,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { assert_eq!( *events.lock(), - &[language2::Event::Edited, language2::Event::DirtyChanged] + &[language::Event::Edited, language::Event::DirtyChanged] ); // When a file is deleted, the buffer is considered dirty. @@ -3087,8 +3038,8 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { assert_eq!( *events.lock(), &[ - language2::Event::DirtyChanged, - language2::Event::FileHandleChanged + language::Event::DirtyChanged, + language::Event::FileHandleChanged ] ); @@ -3114,12 +3065,12 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .await .unwrap(); cx.executor().run_until_parked(); - assert_eq!(*events.lock(), &[language2::Event::FileHandleChanged]); + assert_eq!(*events.lock(), &[language::Event::FileHandleChanged]); cx.update(|cx| assert!(buffer3.read(cx).is_dirty())); } -#[gpui2::test] -async fn test_buffer_file_changes_on_disk(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { init_test(cx); let initial_contents = "aaa\nbbbbb\nc\n"; @@ -3199,8 +3150,8 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui2::TestAppContext) { }); } -#[gpui2::test] -async fn test_buffer_line_endings(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -3261,8 +3212,8 @@ async fn test_buffer_line_endings(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_grouped_diagnostics(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -3288,62 +3239,56 @@ async fn test_grouped_diagnostics(cx: &mut gpui2::TestAppContext) { .unwrap(); let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap(); - let message = lsp2::PublishDiagnosticsParams { + let message = lsp::PublishDiagnosticsParams { uri: buffer_uri.clone(), diagnostics: vec![ - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(1, 8), lsp2::Position::new(1, 9)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), severity: Some(DiagnosticSeverity::WARNING), message: "error 1".to_string(), - related_information: Some(vec![lsp2::DiagnosticRelatedInformation { - location: lsp2::Location { + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { uri: buffer_uri.clone(), - range: lsp2::Range::new( - lsp2::Position::new(1, 8), - lsp2::Position::new(1, 9), - ), + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), }, message: "error 1 hint 1".to_string(), }]), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(1, 8), lsp2::Position::new(1, 9)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), severity: Some(DiagnosticSeverity::HINT), message: "error 1 hint 1".to_string(), - related_information: Some(vec![lsp2::DiagnosticRelatedInformation { - location: lsp2::Location { + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { uri: buffer_uri.clone(), - range: lsp2::Range::new( - lsp2::Position::new(1, 8), - lsp2::Position::new(1, 9), - ), + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), }, message: "original diagnostic".to_string(), }]), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(2, 8), lsp2::Position::new(2, 17)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), severity: Some(DiagnosticSeverity::ERROR), message: "error 2".to_string(), related_information: Some(vec![ - lsp2::DiagnosticRelatedInformation { - location: lsp2::Location { + lsp::DiagnosticRelatedInformation { + location: lsp::Location { uri: buffer_uri.clone(), - range: lsp2::Range::new( - lsp2::Position::new(1, 13), - lsp2::Position::new(1, 15), + range: lsp::Range::new( + lsp::Position::new(1, 13), + lsp::Position::new(1, 15), ), }, message: "error 2 hint 1".to_string(), }, - lsp2::DiagnosticRelatedInformation { - location: lsp2::Location { + lsp::DiagnosticRelatedInformation { + location: lsp::Location { uri: buffer_uri.clone(), - range: lsp2::Range::new( - lsp2::Position::new(1, 13), - lsp2::Position::new(1, 15), + range: lsp::Range::new( + lsp::Position::new(1, 13), + lsp::Position::new(1, 15), ), }, message: "error 2 hint 2".to_string(), @@ -3351,33 +3296,27 @@ async fn test_grouped_diagnostics(cx: &mut gpui2::TestAppContext) { ]), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), severity: Some(DiagnosticSeverity::HINT), message: "error 2 hint 1".to_string(), - related_information: Some(vec![lsp2::DiagnosticRelatedInformation { - location: lsp2::Location { + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { uri: buffer_uri.clone(), - range: lsp2::Range::new( - lsp2::Position::new(2, 8), - lsp2::Position::new(2, 17), - ), + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), }, message: "original diagnostic".to_string(), }]), ..Default::default() }, - lsp2::Diagnostic { - range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)), + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), severity: Some(DiagnosticSeverity::HINT), message: "error 2 hint 2".to_string(), - related_information: Some(vec![lsp2::DiagnosticRelatedInformation { - location: lsp2::Location { + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { uri: buffer_uri, - range: lsp2::Range::new( - lsp2::Position::new(2, 8), - lsp2::Position::new(2, 17), - ), + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), }, message: "original diagnostic".to_string(), }]), @@ -3515,8 +3454,8 @@ async fn test_grouped_diagnostics(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_rename(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_rename(cx: &mut gpui::TestAppContext) { init_test(cx); let mut language = Language::new( @@ -3529,8 +3468,8 @@ async fn test_rename(cx: &mut gpui2::TestAppContext) { ); let mut fake_servers = language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp2::ServerCapabilities { - rename_provider: Some(lsp2::OneOf::Right(lsp2::RenameOptions { + capabilities: lsp::ServerCapabilities { + rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { prepare_provider: Some(true), work_done_progress_options: Default::default(), })), @@ -3565,12 +3504,12 @@ async fn test_rename(cx: &mut gpui2::TestAppContext) { project.prepare_rename(buffer.clone(), 7, cx) }); fake_server - .handle_request::(|params, _| async move { + .handle_request::(|params, _| async move { assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); - assert_eq!(params.position, lsp2::Position::new(0, 7)); - Ok(Some(lsp2::PrepareRenameResponse::Range(lsp2::Range::new( - lsp2::Position::new(0, 6), - lsp2::Position::new(0, 9), + assert_eq!(params.position, lsp::Position::new(0, 7)); + Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( + lsp::Position::new(0, 6), + lsp::Position::new(0, 9), )))) }) .next() @@ -3584,43 +3523,40 @@ async fn test_rename(cx: &mut gpui2::TestAppContext) { project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx) }); fake_server - .handle_request::(|params, _| async move { + .handle_request::(|params, _| async move { assert_eq!( params.text_document_position.text_document.uri.as_str(), "file:///dir/one.rs" ); assert_eq!( params.text_document_position.position, - lsp2::Position::new(0, 7) + lsp::Position::new(0, 7) ); assert_eq!(params.new_name, "THREE"); - Ok(Some(lsp2::WorkspaceEdit { + Ok(Some(lsp::WorkspaceEdit { changes: Some( [ ( - lsp2::Url::from_file_path("/dir/one.rs").unwrap(), - vec![lsp2::TextEdit::new( - lsp2::Range::new( - lsp2::Position::new(0, 6), - lsp2::Position::new(0, 9), - ), + lsp::Url::from_file_path("/dir/one.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)), "THREE".to_string(), )], ), ( - lsp2::Url::from_file_path("/dir/two.rs").unwrap(), + lsp::Url::from_file_path("/dir/two.rs").unwrap(), vec![ - lsp2::TextEdit::new( - lsp2::Range::new( - lsp2::Position::new(0, 24), - lsp2::Position::new(0, 27), + lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 24), + lsp::Position::new(0, 27), ), "THREE".to_string(), ), - lsp2::TextEdit::new( - lsp2::Range::new( - lsp2::Position::new(0, 35), - lsp2::Position::new(0, 38), + lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 35), + lsp::Position::new(0, 38), ), "THREE".to_string(), ), @@ -3656,8 +3592,8 @@ async fn test_rename(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_search(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_search(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor().clone()); @@ -3713,8 +3649,8 @@ async fn test_search(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_search_with_inclusions(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { init_test(cx); let search_query = "file"; @@ -3825,8 +3761,8 @@ async fn test_search_with_inclusions(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_search_with_exclusions(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { init_test(cx); let search_query = "file"; @@ -3936,8 +3872,8 @@ async fn test_search_with_exclusions(cx: &mut gpui2::TestAppContext) { ); } -#[gpui2::test] -async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui2::TestAppContext) { +#[gpui::test] +async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) { init_test(cx); let search_query = "file"; @@ -4057,7 +3993,7 @@ fn test_glob_literal_prefix() { async fn search( project: &Model, query: SearchQuery, - cx: &mut gpui2::TestAppContext, + cx: &mut gpui::TestAppContext, ) -> Result>>> { let mut search_rx = project.update(cx, |project, cx| project.search(query, cx)); let mut result = HashMap::default(); @@ -4079,7 +4015,7 @@ async fn search( .collect()) } -fn init_test(cx: &mut gpui2::TestAppContext) { +fn init_test(cx: &mut gpui::TestAppContext) { if std::env::var("RUST_LOG").is_ok() { env_logger::init(); } @@ -4087,7 +4023,7 @@ fn init_test(cx: &mut gpui2::TestAppContext) { cx.update(|cx| { let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); - language2::init(cx); + language::init(cx); Project::init_settings(cx); }); } diff --git a/crates/project2/src/search.rs b/crates/project2/src/search.rs index 9af13c7732..46dd30c8a0 100644 --- a/crates/project2/src/search.rs +++ b/crates/project2/src/search.rs @@ -1,9 +1,9 @@ use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; use anyhow::{Context, Result}; -use client2::proto; +use client::proto; use globset::{Glob, GlobMatcher}; use itertools::Itertools; -use language2::{char_kind, BufferSnapshot}; +use language::{char_kind, BufferSnapshot}; use regex::{Regex, RegexBuilder}; use smol::future::yield_now; use std::{ diff --git a/crates/project2/src/terminals.rs b/crates/project2/src/terminals.rs index ce89914dc6..1bf69aa8b5 100644 --- a/crates/project2/src/terminals.rs +++ b/crates/project2/src/terminals.rs @@ -1,8 +1,8 @@ use crate::Project; -use gpui2::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel}; -use settings2::Settings; +use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel}; +use settings::Settings; use std::path::{Path, PathBuf}; -use terminal2::{ +use terminal::{ terminal_settings::{self, TerminalSettings, VenvSettingsContent}, Terminal, TerminalBuilder, }; @@ -11,7 +11,7 @@ use terminal2::{ use std::os::unix::ffi::OsStrExt; pub struct Terminals { - pub(crate) local_handles: Vec>, + pub(crate) local_handles: Vec>, } impl Project { @@ -121,7 +121,7 @@ impl Project { } } - pub fn local_terminal_handles(&self) -> &Vec> { + pub fn local_terminal_handles(&self) -> &Vec> { &self.terminals.local_handles } } diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index 060fefe6b3..937a549a31 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -3,10 +3,10 @@ use crate::{ }; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context as _, Result}; -use client2::{proto, Client}; +use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, HashSet, VecDeque}; -use fs2::{ +use fs::{ repository::{GitFileStatus, GitRepository, RepoPath}, Fs, }; @@ -19,20 +19,20 @@ use futures::{ task::Poll, FutureExt as _, Stream, StreamExt, }; -use fuzzy2::CharBag; +use fuzzy::CharBag; use git::{DOT_GIT, GITIGNORE}; -use gpui2::{ +use gpui::{ AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext, Task, }; -use language2::{ +use language::{ proto::{ deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending, serialize_version, }, Buffer, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint, Unclipped, }; -use lsp2::LanguageServerId; +use lsp::LanguageServerId; use parking_lot::Mutex; use postage::{ barrier, @@ -2587,8 +2587,8 @@ pub struct File { pub(crate) is_deleted: bool, } -impl language2::File for File { - fn as_local(&self) -> Option<&dyn language2::LocalFile> { +impl language::File for File { + fn as_local(&self) -> Option<&dyn language::LocalFile> { if self.is_local { Some(self) } else { @@ -2648,8 +2648,8 @@ impl language2::File for File { self } - fn to_proto(&self) -> rpc2::proto::File { - rpc2::proto::File { + fn to_proto(&self) -> rpc::proto::File { + rpc::proto::File { worktree_id: self.worktree.entity_id().as_u64(), entry_id: self.entry_id.to_proto(), path: self.path.to_string_lossy().into(), @@ -2659,7 +2659,7 @@ impl language2::File for File { } } -impl language2::LocalFile for File { +impl language::LocalFile for File { fn abs_path(&self, cx: &AppContext) -> PathBuf { let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path; if self.path.as_ref() == Path::new("") { @@ -2716,7 +2716,7 @@ impl File { } pub fn from_proto( - proto: rpc2::proto::File, + proto: rpc::proto::File, worktree: Model, cx: &AppContext, ) -> Result { @@ -2740,7 +2740,7 @@ impl File { }) } - pub fn from_dyn(file: Option<&Arc>) -> Option<&Self> { + pub fn from_dyn(file: Option<&Arc>) -> Option<&Self> { file.and_then(|f| f.as_any().downcast_ref()) } @@ -2818,7 +2818,7 @@ pub type UpdatedGitRepositoriesSet = Arc<[(Arc, GitRepositoryChange)]>; impl Entry { fn new( path: Arc, - metadata: &fs2::Metadata, + metadata: &fs::Metadata, next_entry_id: &AtomicUsize, root_char_bag: CharBag, ) -> Self { @@ -4037,7 +4037,7 @@ pub trait WorktreeModelHandle { #[cfg(any(test, feature = "test-support"))] fn flush_fs_events<'a>( &self, - cx: &'a mut gpui2::TestAppContext, + cx: &'a mut gpui::TestAppContext, ) -> futures::future::LocalBoxFuture<'a, ()>; } @@ -4051,7 +4051,7 @@ impl WorktreeModelHandle for Model { #[cfg(any(test, feature = "test-support"))] fn flush_fs_events<'a>( &self, - cx: &'a mut gpui2::TestAppContext, + cx: &'a mut gpui::TestAppContext, ) -> futures::future::LocalBoxFuture<'a, ()> { let file_name = "fs-event-sentinel"; diff --git a/crates/rpc2/Cargo.toml b/crates/rpc2/Cargo.toml index f108af3d3f..0995029b30 100644 --- a/crates/rpc2/Cargo.toml +++ b/crates/rpc2/Cargo.toml @@ -10,12 +10,12 @@ path = "src/rpc.rs" doctest = false [features] -test-support = ["collections/test-support", "gpui2/test-support"] +test-support = ["collections/test-support", "gpui/test-support"] [dependencies] clock = { path = "../clock" } collections = { path = "../collections" } -gpui2 = { path = "../gpui2", optional = true } +gpui = { package = "gpui2", path = "../gpui2", optional = true } util = { path = "../util" } anyhow.workspace = true async-lock = "2.4" @@ -37,7 +37,7 @@ prost-build = "0.9" [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } smol.workspace = true tempdir.workspace = true ctor.workspace = true diff --git a/crates/rpc2/src/conn.rs b/crates/rpc2/src/conn.rs index ec3c5b68cf..ae5c9fd226 100644 --- a/crates/rpc2/src/conn.rs +++ b/crates/rpc2/src/conn.rs @@ -34,7 +34,7 @@ impl Connection { #[cfg(any(test, feature = "test-support"))] pub fn in_memory( - executor: gpui2::BackgroundExecutor, + executor: gpui::BackgroundExecutor, ) -> (Self, Self, std::sync::Arc) { use std::sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -53,7 +53,7 @@ impl Connection { #[allow(clippy::type_complexity)] fn channel( killed: Arc, - executor: gpui2::BackgroundExecutor, + executor: gpui::BackgroundExecutor, ) -> ( Box>, Box>>, diff --git a/crates/rpc2/src/peer.rs b/crates/rpc2/src/peer.rs index 104ab1b421..80a2ab4378 100644 --- a/crates/rpc2/src/peer.rs +++ b/crates/rpc2/src/peer.rs @@ -342,7 +342,7 @@ impl Peer { pub fn add_test_connection( self: &Arc, connection: Connection, - executor: gpui2::BackgroundExecutor, + executor: gpui::BackgroundExecutor, ) -> ( ConnectionId, impl Future> + Send, @@ -557,7 +557,7 @@ mod tests { use super::*; use crate::TypedEnvelope; use async_tungstenite::tungstenite::Message as WebSocketMessage; - use gpui2::TestAppContext; + use gpui::TestAppContext; fn init_logger() { if std::env::var("RUST_LOG").is_ok() { @@ -565,7 +565,7 @@ mod tests { } } - #[gpui2::test(iterations = 50)] + #[gpui::test(iterations = 50)] async fn test_request_response(cx: &mut TestAppContext) { init_logger(); @@ -663,7 +663,7 @@ mod tests { } } - #[gpui2::test(iterations = 50)] + #[gpui::test(iterations = 50)] async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) { let executor = cx.executor(); let server = Peer::new(0); @@ -761,7 +761,7 @@ mod tests { ); } - #[gpui2::test(iterations = 50)] + #[gpui::test(iterations = 50)] async fn test_dropping_request_before_completion(cx: &mut TestAppContext) { let executor = cx.executor().clone(); let server = Peer::new(0); @@ -873,7 +873,7 @@ mod tests { ); } - #[gpui2::test(iterations = 50)] + #[gpui::test(iterations = 50)] async fn test_disconnect(cx: &mut TestAppContext) { let executor = cx.executor(); @@ -909,7 +909,7 @@ mod tests { .is_err()); } - #[gpui2::test(iterations = 50)] + #[gpui::test(iterations = 50)] async fn test_io_error(cx: &mut TestAppContext) { let executor = cx.executor(); let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone()); diff --git a/crates/rpc2/src/proto.rs b/crates/rpc2/src/proto.rs index c1a7af3e4d..f0d7937f6f 100644 --- a/crates/rpc2/src/proto.rs +++ b/crates/rpc2/src/proto.rs @@ -616,7 +616,7 @@ pub fn split_worktree_update( mod tests { use super::*; - #[gpui2::test] + #[gpui::test] async fn test_buffer_size() { let (tx, rx) = futures::channel::mpsc::unbounded(); let mut sink = MessageStream::new(tx.sink_map_err(|_| anyhow!(""))); @@ -648,7 +648,7 @@ mod tests { assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN); } - #[gpui2::test] + #[gpui::test] fn test_converting_peer_id_from_and_to_u64() { let peer_id = PeerId { owner_id: 10, diff --git a/crates/settings2/Cargo.toml b/crates/settings2/Cargo.toml index b455b1e38a..0a4051cbb3 100644 --- a/crates/settings2/Cargo.toml +++ b/crates/settings2/Cargo.toml @@ -9,14 +9,14 @@ path = "src/settings2.rs" doctest = false [features] -test-support = ["gpui2/test-support", "fs/test-support"] +test-support = ["gpui/test-support", "fs/test-support"] [dependencies] collections = { path = "../collections" } -gpui2 = { path = "../gpui2" } +gpui = {package = "gpui2", path = "../gpui2" } sqlez = { path = "../sqlez" } -fs2 = { path = "../fs2" } -feature_flags2 = { path = "../feature_flags2" } +fs = {package = "fs2", path = "../fs2" } +feature_flags = {package = "feature_flags2", path = "../feature_flags2" } util = { path = "../util" } anyhow.workspace = true @@ -35,8 +35,8 @@ tree-sitter.workspace = true tree-sitter-json = "*" [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } +gpui = {package = "gpui2", path = "../gpui2", features = ["test-support"] } +fs = { package = "fs2", path = "../fs2", features = ["test-support"] } indoc.workspace = true pretty_assertions.workspace = true unindent.workspace = true diff --git a/crates/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index d0a32131b5..e51bd76e5e 100644 --- a/crates/settings2/src/keymap_file.rs +++ b/crates/settings2/src/keymap_file.rs @@ -1,7 +1,7 @@ use crate::{settings_store::parse_json_with_comments, SettingsAssets}; use anyhow::{anyhow, Context, Result}; use collections::BTreeMap; -use gpui2::{AppContext, KeyBinding, SharedString}; +use gpui::{AppContext, KeyBinding, SharedString}; use schemars::{ gen::{SchemaGenerator, SchemaSettings}, schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation}, @@ -137,7 +137,7 @@ impl KeymapFile { } } -fn no_action() -> Box { +fn no_action() -> Box { todo!() } diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index 002c9daf12..c623ae9caf 100644 --- a/crates/settings2/src/settings_file.rs +++ b/crates/settings2/src/settings_file.rs @@ -1,8 +1,8 @@ use crate::{settings_store::SettingsStore, Settings}; use anyhow::Result; -use fs2::Fs; +use fs::Fs; use futures::{channel::mpsc, StreamExt}; -use gpui2::{AppContext, BackgroundExecutor}; +use gpui::{AppContext, BackgroundExecutor}; use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; use util::{paths, ResultExt}; diff --git a/crates/settings2/src/settings_store.rs b/crates/settings2/src/settings_store.rs index e2c370bcac..3317a50f52 100644 --- a/crates/settings2/src/settings_store.rs +++ b/crates/settings2/src/settings_store.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context, Result}; use collections::{btree_map, hash_map, BTreeMap, HashMap}; -use gpui2::AppContext; +use gpui::AppContext; use lazy_static::lazy_static; use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; @@ -877,7 +877,7 @@ mod tests { use serde_derive::Deserialize; use unindent::Unindent; - #[gpui2::test] + #[gpui::test] fn test_settings_store_basic(cx: &mut AppContext) { let mut store = SettingsStore::default(); store.register_setting::(cx); @@ -994,7 +994,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_setting_store_assign_json_before_register(cx: &mut AppContext) { let mut store = SettingsStore::default(); store @@ -1037,7 +1037,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_setting_store_update(cx: &mut AppContext) { let mut store = SettingsStore::default(); store.register_setting::(cx); diff --git a/crates/terminal2/Cargo.toml b/crates/terminal2/Cargo.toml index 3ca5dc9aba..e37b949881 100644 --- a/crates/terminal2/Cargo.toml +++ b/crates/terminal2/Cargo.toml @@ -10,10 +10,10 @@ doctest = false [dependencies] -gpui2 = { path = "../gpui2" } -settings2 = { path = "../settings2" } -db2 = { path = "../db2" } -theme2 = { path = "../theme2" } +gpui = { package = "gpui2", path = "../gpui2" } +settings = { package = "settings2", path = "../settings2" } +db = { package = "db2", path = "../db2" } +theme = { package = "theme2", path = "../theme2" } util = { path = "../util" } alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "33306142195b354ef3485ca2b1d8a85dfc6605ca" } diff --git a/crates/terminal2/src/mappings/colors.rs b/crates/terminal2/src/mappings/colors.rs index 99b66b9e14..fc3557b4e8 100644 --- a/crates/terminal2/src/mappings/colors.rs +++ b/crates/terminal2/src/mappings/colors.rs @@ -113,7 +113,7 @@ use alacritty_terminal::term::color::Rgb as AlacRgb; // let b = (i % 36) % 6; // (r, g, b) // } -use gpui2::Rgba; +use gpui::Rgba; //Convenience method to convert from a GPUI color to an alacritty Rgb pub fn to_alac_rgb(color: impl Into) -> AlacRgb { diff --git a/crates/terminal2/src/mappings/keys.rs b/crates/terminal2/src/mappings/keys.rs index 0009d39e13..f8a26fbe2b 100644 --- a/crates/terminal2/src/mappings/keys.rs +++ b/crates/terminal2/src/mappings/keys.rs @@ -1,6 +1,6 @@ /// The mappings defined in this file where created from reading the alacritty source use alacritty_terminal::term::TermMode; -use gpui2::Keystroke; +use gpui::Keystroke; #[derive(Debug, PartialEq, Eq)] enum AlacModifiers { @@ -278,7 +278,7 @@ fn modifier_code(keystroke: &Keystroke) -> u32 { #[cfg(test)] mod test { - use gpui2::Modifiers; + use gpui::Modifiers; use super::*; diff --git a/crates/terminal2/src/mappings/mouse.rs b/crates/terminal2/src/mappings/mouse.rs index 28ad510fcd..eac6ad17ff 100644 --- a/crates/terminal2/src/mappings/mouse.rs +++ b/crates/terminal2/src/mappings/mouse.rs @@ -6,7 +6,7 @@ use alacritty_terminal::grid::Dimensions; /// with modifications for our circumstances use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side}; use alacritty_terminal::term::TermMode; -use gpui2::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent}; +use gpui::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent}; use crate::TerminalSize; @@ -45,10 +45,10 @@ impl AlacMouseButton { fn from_move(e: &MouseMoveEvent) -> Self { match e.pressed_button { Some(b) => match b { - gpui2::MouseButton::Left => AlacMouseButton::LeftMove, - gpui2::MouseButton::Middle => AlacMouseButton::MiddleMove, - gpui2::MouseButton::Right => AlacMouseButton::RightMove, - gpui2::MouseButton::Navigate(_) => AlacMouseButton::Other, + gpui::MouseButton::Left => AlacMouseButton::LeftMove, + gpui::MouseButton::Middle => AlacMouseButton::MiddleMove, + gpui::MouseButton::Right => AlacMouseButton::RightMove, + gpui::MouseButton::Navigate(_) => AlacMouseButton::Other, }, None => AlacMouseButton::NoneMove, } @@ -56,17 +56,17 @@ impl AlacMouseButton { fn from_button(e: MouseButton) -> Self { match e { - gpui2::MouseButton::Left => AlacMouseButton::LeftButton, - gpui2::MouseButton::Right => AlacMouseButton::MiddleButton, - gpui2::MouseButton::Middle => AlacMouseButton::RightButton, - gpui2::MouseButton::Navigate(_) => AlacMouseButton::Other, + gpui::MouseButton::Left => AlacMouseButton::LeftButton, + gpui::MouseButton::Right => AlacMouseButton::MiddleButton, + gpui::MouseButton::Middle => AlacMouseButton::RightButton, + gpui::MouseButton::Navigate(_) => AlacMouseButton::Other, } } fn from_scroll(e: &ScrollWheelEvent) -> Self { let is_positive = match e.delta { - gpui2::ScrollDelta::Pixels(pixels) => pixels.y > px(0.), - gpui2::ScrollDelta::Lines(lines) => lines.y > 0., + gpui::ScrollDelta::Pixels(pixels) => pixels.y > px(0.), + gpui::ScrollDelta::Lines(lines) => lines.y > 0., }; if is_positive { @@ -118,7 +118,7 @@ pub fn alt_scroll(scroll_lines: i32) -> Vec { pub fn mouse_button_report( point: AlacPoint, - button: gpui2::MouseButton, + button: gpui::MouseButton, modifiers: Modifiers, pressed: bool, mode: TermMode, diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index adc5dd3511..ba5c4815f2 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -33,7 +33,7 @@ use mappings::mouse::{ use procinfo::LocalProcessInfo; use serde::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings}; use util::truncate_and_trailoff; @@ -49,7 +49,7 @@ use std::{ }; use thiserror::Error; -use gpui2::{ +use gpui::{ px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollWheelEvent, Size, Task, TouchPhase, @@ -1409,7 +1409,7 @@ mod tests { index::{Column, Line, Point as AlacPoint}, term::cell::Cell, }; - use gpui2::{point, size, Pixels}; + use gpui::{point, size, Pixels}; use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng}; use crate::{content_index_for_mouse, IndexedCell, TerminalContent, TerminalSize}; diff --git a/crates/terminal2/src/terminal_settings.rs b/crates/terminal2/src/terminal_settings.rs index 1be9ac5000..1d1e1cea2a 100644 --- a/crates/terminal2/src/terminal_settings.rs +++ b/crates/terminal2/src/terminal_settings.rs @@ -1,4 +1,4 @@ -use gpui2::{AppContext, FontFeatures}; +use gpui::{AppContext, FontFeatures}; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; @@ -98,7 +98,7 @@ impl TerminalSettings { // } } -impl settings2::Settings for TerminalSettings { +impl settings::Settings for TerminalSettings { const KEY: Option<&'static str> = Some("terminal"); type FileContent = TerminalSettingsContent; diff --git a/crates/text2/Cargo.toml b/crates/text2/Cargo.toml index 6891fef680..7c12d22adf 100644 --- a/crates/text2/Cargo.toml +++ b/crates/text2/Cargo.toml @@ -30,7 +30,7 @@ regex.workspace = true [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true diff --git a/crates/text2/src/locator.rs b/crates/text2/src/locator.rs index 27fdb34cde..07b73ace05 100644 --- a/crates/text2/src/locator.rs +++ b/crates/text2/src/locator.rs @@ -91,7 +91,7 @@ mod tests { use rand::prelude::*; use std::mem; - #[gpui2::test(iterations = 100)] + #[gpui::test(iterations = 100)] fn test_locators(mut rng: StdRng) { let mut lhs = Default::default(); let mut rhs = Default::default(); diff --git a/crates/text2/src/patch.rs b/crates/text2/src/patch.rs index 20e4a4d889..f10acbc2d3 100644 --- a/crates/text2/src/patch.rs +++ b/crates/text2/src/patch.rs @@ -256,7 +256,7 @@ mod tests { use rand::prelude::*; use std::env; - #[gpui2::test] + #[gpui::test] fn test_one_disjoint_edit() { assert_patch_composition( Patch(vec![Edit { @@ -301,7 +301,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_one_overlapping_edit() { assert_patch_composition( Patch(vec![Edit { @@ -319,7 +319,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_two_disjoint_and_overlapping() { assert_patch_composition( Patch(vec![ @@ -355,7 +355,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_two_new_edits_overlapping_one_old_edit() { assert_patch_composition( Patch(vec![Edit { @@ -421,7 +421,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_two_new_edits_touching_one_old_edit() { assert_patch_composition( Patch(vec![ @@ -457,7 +457,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] fn test_old_to_new() { let patch = Patch(vec![ Edit { @@ -481,7 +481,7 @@ mod tests { assert_eq!(patch.old_to_new(9), 12); } - #[gpui2::test(iterations = 100)] + #[gpui::test(iterations = 100)] fn test_random_patch_compositions(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) diff --git a/crates/text2/src/tests.rs b/crates/text2/src/tests.rs index 96248285ea..7e26e0a296 100644 --- a/crates/text2/src/tests.rs +++ b/crates/text2/src/tests.rs @@ -32,7 +32,7 @@ fn test_edit() { assert_eq!(buffer.text(), "ghiamnoef"); } -#[gpui2::test(iterations = 100)] +#[gpui::test(iterations = 100)] fn test_random_edits(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) @@ -687,7 +687,7 @@ fn test_concurrent_edits() { assert_eq!(buffer3.text(), "a12c34e56"); } -#[gpui2::test(iterations = 100)] +#[gpui::test(iterations = 100)] fn test_random_concurrent_edits(mut rng: StdRng) { let peers = env::var("PEERS") .map(|i| i.parse().expect("invalid `PEERS` variable")) diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index 6b273e5042..a051468b00 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -6,9 +6,9 @@ publish = false [features] test-support = [ - "gpui2/test-support", + "gpui/test-support", "fs/test-support", - "settings2/test-support" + "settings/test-support" ] [lib] @@ -18,7 +18,7 @@ doctest = false [dependencies] anyhow.workspace = true fs = { path = "../fs" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } indexmap = "1.6.2" parking_lot.workspace = true refineable.workspace = true @@ -26,11 +26,11 @@ schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings2 = { path = "../settings2" } +settings = { package = "settings2", path = "../settings2" } toml.workspace = true util = { path = "../util" } [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } fs = { path = "../fs", features = ["test-support"] } -settings2 = { path = "../settings2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index ee69eed612..422e33e4f8 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -1,4 +1,4 @@ -use gpui2::Hsla; +use gpui::Hsla; use refineable::Refineable; use crate::SyntaxTheme; @@ -109,7 +109,7 @@ mod tests { fn override_a_single_theme_color() { let mut colors = ThemeColors::default_light(); - let magenta: Hsla = gpui2::rgb(0xff00ff); + let magenta: Hsla = gpui::rgb(0xff00ff); assert_ne!(colors.text, magenta); @@ -127,8 +127,8 @@ mod tests { fn override_multiple_theme_colors() { let mut colors = ThemeColors::default_light(); - let magenta: Hsla = gpui2::rgb(0xff00ff); - let green: Hsla = gpui2::rgb(0x00ff00); + let magenta: Hsla = gpui::rgb(0xff00ff); + let green: Hsla = gpui::rgb(0x00ff00); assert_ne!(colors.text, magenta); assert_ne!(colors.background, green); diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 8b7137683c..802392d296 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,6 +1,6 @@ use std::num::ParseIntError; -use gpui2::{hsla, Hsla, Rgba}; +use gpui::{hsla, Hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, diff --git a/crates/theme2/src/registry.rs b/crates/theme2/src/registry.rs index f30f5ead91..c1bba121e1 100644 --- a/crates/theme2/src/registry.rs +++ b/crates/theme2/src/registry.rs @@ -1,6 +1,6 @@ use crate::{zed_pro_family, ThemeFamily, ThemeVariant}; use anyhow::{anyhow, Result}; -use gpui2::SharedString; +use gpui::SharedString; use std::{collections::HashMap, sync::Arc}; pub struct ThemeRegistry { diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index bd28e43db9..22d191bd4a 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -1,4 +1,4 @@ -use gpui2::{AppContext, Hsla, SharedString}; +use gpui::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index c8d2b52273..5e8f9de873 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -1,6 +1,6 @@ use crate::{ThemeRegistry, ThemeVariant}; use anyhow::Result; -use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; +use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, @@ -8,7 +8,7 @@ use schemars::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use settings2::{Settings, SettingsJsonSchemaParams}; +use settings::{Settings, SettingsJsonSchemaParams}; use std::sync::Arc; use util::ResultExt as _; @@ -105,7 +105,7 @@ pub fn reset_font_size(cx: &mut AppContext) { } } -impl settings2::Settings for ThemeSettings { +impl settings::Settings for ThemeSettings { const KEY: Option<&'static str> = None; type FileContent = ThemeSettingsContent; diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs index a8127f0c44..3a068349fb 100644 --- a/crates/theme2/src/syntax.rs +++ b/crates/theme2/src/syntax.rs @@ -1,4 +1,4 @@ -use gpui2::{HighlightStyle, Hsla}; +use gpui::{HighlightStyle, Hsla}; #[derive(Clone, Default)] pub struct SyntaxTheme { diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 88dcbd1286..faf252e2e5 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -6,6 +6,7 @@ mod scale; mod settings; mod syntax; +use ::settings::Settings; pub use colors::*; pub use default_colors::*; pub use default_theme::*; @@ -14,8 +15,7 @@ pub use scale::*; pub use settings::*; pub use syntax::*; -use gpui2::{AppContext, Hsla, SharedString}; -use settings2::Settings; +use gpui::{AppContext, Hsla, SharedString}; #[derive(Debug, Clone, PartialEq)] pub enum Appearance { diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index a54e68b51a..3e07b4a76f 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -15,12 +15,12 @@ name = "Zed" path = "src/main.rs" [dependencies] -ai2 = { path = "../ai2"} +ai = { package = "ai2", path = "../ai2"} # audio = { path = "../audio" } # activity_indicator = { path = "../activity_indicator" } # auto_update = { path = "../auto_update" } # breadcrumbs = { path = "../breadcrumbs" } -call2 = { path = "../call2" } +call = { package = "call2", path = "../call2" } # channel = { path = "../channel" } cli = { path = "../cli" } # collab_ui = { path = "../collab_ui" } @@ -28,44 +28,44 @@ collections = { path = "../collections" } # command_palette = { path = "../command_palette" } # component_test = { path = "../component_test" } # context_menu = { path = "../context_menu" } -client2 = { path = "../client2" } +client = { package = "client2", path = "../client2" } # clock = { path = "../clock" } -copilot2 = { path = "../copilot2" } +copilot = { package = "copilot2", path = "../copilot2" } # copilot_button = { path = "../copilot_button" } # diagnostics = { path = "../diagnostics" } -db2 = { path = "../db2" } +db = { package = "db2", path = "../db2" } # editor = { path = "../editor" } # feedback = { path = "../feedback" } # file_finder = { path = "../file_finder" } # search = { path = "../search" } -fs2 = { path = "../fs2" } +fs = { package = "fs2", path = "../fs2" } fsevent = { path = "../fsevent" } fuzzy = { path = "../fuzzy" } # go_to_line = { path = "../go_to_line" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } install_cli = { path = "../install_cli" } -journal2 = { path = "../journal2" } -language2 = { path = "../language2" } +journal = { package = "journal2", path = "../journal2" } +language = { package = "language2", path = "../language2" } # language_selector = { path = "../language_selector" } -lsp2 = { path = "../lsp2" } +lsp = { package = "lsp2", path = "../lsp2" } language_tools = { path = "../language_tools" } node_runtime = { path = "../node_runtime" } # assistant = { path = "../assistant" } # outline = { path = "../outline" } # plugin_runtime = { path = "../plugin_runtime",optional = true } -project2 = { path = "../project2" } +project = { package = "project2", path = "../project2" } # project_panel = { path = "../project_panel" } # project_symbols = { path = "../project_symbols" } # quick_action_bar = { path = "../quick_action_bar" } # recent_projects = { path = "../recent_projects" } -rpc2 = { path = "../rpc2" } -settings2 = { path = "../settings2" } -feature_flags2 = { path = "../feature_flags2" } +rpc = { package = "rpc2", path = "../rpc2" } +settings = { package = "settings2", path = "../settings2" } +feature_flags = { package = "feature_flags2", path = "../feature_flags2" } sum_tree = { path = "../sum_tree" } shellexpand = "2.1.0" -text2 = { path = "../text2" } +text = { package = "text2", path = "../text2" } # terminal_view = { path = "../terminal_view" } -theme2 = { path = "../theme2" } +theme = { package = "theme2", path = "../theme2" } # theme_selector = { path = "../theme_selector" } util = { path = "../util" } # semantic_index = { path = "../semantic_index" } @@ -142,17 +142,17 @@ urlencoding = "2.1.2" uuid.workspace = true [dev-dependencies] -call2 = { path = "../call2", features = ["test-support"] } +call = { package = "call2", path = "../call2", features = ["test-support"] } # client = { path = "../client", features = ["test-support"] } # editor = { path = "../editor", features = ["test-support"] } # gpui = { path = "../gpui", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } -language2 = { path = "../language2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +language = { package = "language2", path = "../language2", features = ["test-support"] } # lsp = { path = "../lsp", features = ["test-support"] } -project2 = { path = "../project2", features = ["test-support"] } +project = { package = "project2", path = "../project2", features = ["test-support"] } # rpc = { path = "../rpc", features = ["test-support"] } # settings = { path = "../settings", features = ["test-support"] } -text2 = { path = "../text2", features = ["test-support"] } +text = { package = "text2", path = "../text2", features = ["test-support"] } # util = { path = "../util", features = ["test-support"] } # workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true diff --git a/crates/zed2/src/assets.rs b/crates/zed2/src/assets.rs index c4010edc9f..873138c244 100644 --- a/crates/zed2/src/assets.rs +++ b/crates/zed2/src/assets.rs @@ -1,5 +1,6 @@ use anyhow::anyhow; -use gpui2::{AssetSource, Result, SharedString}; + +use gpui::{AssetSource, Result, SharedString}; use rust_embed::RustEmbed; #[derive(RustEmbed)] diff --git a/crates/zed2/src/languages.rs b/crates/zed2/src/languages.rs index 4f7a97cb97..555f12dd0f 100644 --- a/crates/zed2/src/languages.rs +++ b/crates/zed2/src/languages.rs @@ -1,9 +1,9 @@ use anyhow::Context; -use gpui2::AppContext; -pub use language2::*; +use gpui::AppContext; +pub use language::*; use node_runtime::NodeRuntime; use rust_embed::RustEmbed; -use settings2::Settings; +use settings::Settings; use std::{borrow::Cow, str, sync::Arc}; use util::asset_str; diff --git a/crates/zed2/src/languages/c.rs b/crates/zed2/src/languages/c.rs index c836fdc740..280d9dd921 100644 --- a/crates/zed2/src/languages/c.rs +++ b/crates/zed2/src/languages/c.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use futures::StreamExt; -pub use language2::*; -use lsp2::LanguageServerBinary; +pub use language::*; +use lsp::LanguageServerBinary; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ @@ -108,7 +108,7 @@ impl super::LspAdapter for CLspAdapter { async fn label_for_completion( &self, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { let label = completion @@ -118,7 +118,7 @@ impl super::LspAdapter for CLspAdapter { .trim(); match completion.kind { - Some(lsp2::CompletionItemKind::FIELD) if completion.detail.is_some() => { + Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => { let detail = completion.detail.as_ref().unwrap(); let text = format!("{} {}", detail, label); let source = Rope::from(format!("struct S {{ {} }}", text).as_str()); @@ -129,7 +129,7 @@ impl super::LspAdapter for CLspAdapter { runs, }); } - Some(lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE) + Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) if completion.detail.is_some() => { let detail = completion.detail.as_ref().unwrap(); @@ -141,7 +141,7 @@ impl super::LspAdapter for CLspAdapter { runs, }); } - Some(lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD) + Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) if completion.detail.is_some() => { let detail = completion.detail.as_ref().unwrap(); @@ -155,13 +155,13 @@ impl super::LspAdapter for CLspAdapter { } Some(kind) => { let highlight_name = match kind { - lsp2::CompletionItemKind::STRUCT - | lsp2::CompletionItemKind::INTERFACE - | lsp2::CompletionItemKind::CLASS - | lsp2::CompletionItemKind::ENUM => Some("type"), - lsp2::CompletionItemKind::ENUM_MEMBER => Some("variant"), - lsp2::CompletionItemKind::KEYWORD => Some("keyword"), - lsp2::CompletionItemKind::VALUE | lsp2::CompletionItemKind::CONSTANT => { + lsp::CompletionItemKind::STRUCT + | lsp::CompletionItemKind::INTERFACE + | lsp::CompletionItemKind::CLASS + | lsp::CompletionItemKind::ENUM => Some("type"), + lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"), + lsp::CompletionItemKind::KEYWORD => Some("keyword"), + lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => { Some("constant") } _ => None, @@ -186,47 +186,47 @@ impl super::LspAdapter for CLspAdapter { async fn label_for_symbol( &self, name: &str, - kind: lsp2::SymbolKind, + kind: lsp::SymbolKind, language: &Arc, ) -> Option { let (text, filter_range, display_range) = match kind { - lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => { + lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("void {} () {{}}", name); let filter_range = 0..name.len(); let display_range = 5..5 + name.len(); (text, filter_range, display_range) } - lsp2::SymbolKind::STRUCT => { + lsp::SymbolKind::STRUCT => { let text = format!("struct {} {{}}", name); let filter_range = 7..7 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::ENUM => { + lsp::SymbolKind::ENUM => { let text = format!("enum {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::INTERFACE | lsp2::SymbolKind::CLASS => { + lsp::SymbolKind::INTERFACE | lsp::SymbolKind::CLASS => { let text = format!("class {} {{}}", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::CONSTANT => { + lsp::SymbolKind::CONSTANT => { let text = format!("const int {} = 0;", name); let filter_range = 10..10 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::MODULE => { + lsp::SymbolKind::MODULE => { let text = format!("namespace {} {{}}", name); let filter_range = 10..10 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::TYPE_PARAMETER => { + lsp::SymbolKind::TYPE_PARAMETER => { let text = format!("typename {} {{}};", name); let filter_range = 9..9 + name.len(); let display_range = 0..filter_range.end; @@ -273,18 +273,18 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option(|store, cx| { store.update_user_settings::(cx, |s| { s.defaults.tab_size = NonZeroU32::new(2); diff --git a/crates/zed2/src/languages/css.rs b/crates/zed2/src/languages/css.rs index fb6fcabe8e..fdbc179209 100644 --- a/crates/zed2/src/languages/css.rs +++ b/crates/zed2/src/languages/css.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use smol::fs; diff --git a/crates/zed2/src/languages/elixir.rs b/crates/zed2/src/languages/elixir.rs index 09c7305fb0..bd38377c99 100644 --- a/crates/zed2/src/languages/elixir.rs +++ b/crates/zed2/src/languages/elixir.rs @@ -1,12 +1,12 @@ use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use futures::StreamExt; -use gpui2::{AsyncAppContext, Task}; -pub use language2::*; -use lsp2::{CompletionItemKind, LanguageServerBinary, SymbolKind}; +use gpui::{AsyncAppContext, Task}; +pub use language::*; +use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings2::Settings; +use settings::Settings; use smol::fs::{self, File}; use std::{ any::Any, @@ -54,7 +54,7 @@ impl Settings for ElixirSettings { fn load( default_value: &Self::FileContent, user_values: &[&Self::FileContent], - _: &mut gpui2::AppContext, + _: &mut gpui::AppContext, ) -> Result where Self: Sized, @@ -200,7 +200,7 @@ impl LspAdapter for ElixirLspAdapter { async fn label_for_completion( &self, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { match completion.kind.zip(completion.detail.as_ref()) { @@ -404,7 +404,7 @@ impl LspAdapter for NextLspAdapter { async fn label_for_completion( &self, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { label_for_completion_elixir(completion, language) @@ -506,7 +506,7 @@ impl LspAdapter for LocalLspAdapter { async fn label_for_completion( &self, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { label_for_completion_elixir(completion, language) @@ -523,7 +523,7 @@ impl LspAdapter for LocalLspAdapter { } fn label_for_completion_elixir( - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { return Some(CodeLabel { diff --git a/crates/zed2/src/languages/go.rs b/crates/zed2/src/languages/go.rs index 21001015b9..0daf1527c3 100644 --- a/crates/zed2/src/languages/go.rs +++ b/crates/zed2/src/languages/go.rs @@ -1,10 +1,10 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use gpui2::{AsyncAppContext, Task}; -pub use language2::*; +use gpui::{AsyncAppContext, Task}; +pub use language::*; use lazy_static::lazy_static; -use lsp2::LanguageServerBinary; +use lsp::LanguageServerBinary; use regex::Regex; use smol::{fs, process}; use std::{ @@ -170,7 +170,7 @@ impl super::LspAdapter for GoLspAdapter { async fn label_for_completion( &self, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { let label = &completion.label; @@ -181,7 +181,7 @@ impl super::LspAdapter for GoLspAdapter { let name_offset = label.rfind('.').unwrap_or(0); match completion.kind.zip(completion.detail.as_ref()) { - Some((lsp2::CompletionItemKind::MODULE, detail)) => { + Some((lsp::CompletionItemKind::MODULE, detail)) => { let text = format!("{label} {detail}"); let source = Rope::from(format!("import {text}").as_str()); let runs = language.highlight_text(&source, 7..7 + text.len()); @@ -192,7 +192,7 @@ impl super::LspAdapter for GoLspAdapter { }); } Some(( - lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE, + lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE, detail, )) => { let text = format!("{label} {detail}"); @@ -208,7 +208,7 @@ impl super::LspAdapter for GoLspAdapter { filter_range: 0..label.len(), }); } - Some((lsp2::CompletionItemKind::STRUCT, _)) => { + Some((lsp::CompletionItemKind::STRUCT, _)) => { let text = format!("{label} struct {{}}"); let source = Rope::from(format!("type {}", &text[name_offset..]).as_str()); let runs = adjust_runs( @@ -221,7 +221,7 @@ impl super::LspAdapter for GoLspAdapter { filter_range: 0..label.len(), }); } - Some((lsp2::CompletionItemKind::INTERFACE, _)) => { + Some((lsp::CompletionItemKind::INTERFACE, _)) => { let text = format!("{label} interface {{}}"); let source = Rope::from(format!("type {}", &text[name_offset..]).as_str()); let runs = adjust_runs( @@ -234,7 +234,7 @@ impl super::LspAdapter for GoLspAdapter { filter_range: 0..label.len(), }); } - Some((lsp2::CompletionItemKind::FIELD, detail)) => { + Some((lsp::CompletionItemKind::FIELD, detail)) => { let text = format!("{label} {detail}"); let source = Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str()); @@ -248,10 +248,7 @@ impl super::LspAdapter for GoLspAdapter { filter_range: 0..label.len(), }); } - Some(( - lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD, - detail, - )) => { + Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => { if let Some(signature) = detail.strip_prefix("func") { let text = format!("{label}{signature}"); let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str()); @@ -274,47 +271,47 @@ impl super::LspAdapter for GoLspAdapter { async fn label_for_symbol( &self, name: &str, - kind: lsp2::SymbolKind, + kind: lsp::SymbolKind, language: &Arc, ) -> Option { let (text, filter_range, display_range) = match kind { - lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => { + lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("func {} () {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::STRUCT => { + lsp::SymbolKind::STRUCT => { let text = format!("type {} struct {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..text.len(); (text, filter_range, display_range) } - lsp2::SymbolKind::INTERFACE => { + lsp::SymbolKind::INTERFACE => { let text = format!("type {} interface {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..text.len(); (text, filter_range, display_range) } - lsp2::SymbolKind::CLASS => { + lsp::SymbolKind::CLASS => { let text = format!("type {} T", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::CONSTANT => { + lsp::SymbolKind::CONSTANT => { let text = format!("const {} = nil", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::VARIABLE => { + lsp::SymbolKind::VARIABLE => { let text = format!("var {} = nil", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::MODULE => { + lsp::SymbolKind::MODULE => { let text = format!("package {}", name); let filter_range = 8..8 + name.len(); let display_range = 0..filter_range.end; @@ -375,10 +372,10 @@ fn adjust_runs( mod tests { use super::*; use crate::languages::language; - use gpui2::Hsla; - use theme2::SyntaxTheme; + use gpui::Hsla; + use theme::SyntaxTheme; - #[gpui2::test] + #[gpui::test] async fn test_go_label_for_completion() { let language = language( "go", @@ -405,8 +402,8 @@ mod tests { assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::FUNCTION), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), label: "Hello".to_string(), detail: Some("func(a B) c.D".to_string()), ..Default::default() @@ -426,8 +423,8 @@ mod tests { // Nested methods assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::METHOD), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::METHOD), label: "one.two.Three".to_string(), detail: Some("func() [3]interface{}".to_string()), ..Default::default() @@ -447,8 +444,8 @@ mod tests { // Nested fields assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::FIELD), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FIELD), label: "two.Three".to_string(), detail: Some("a.Bcd".to_string()), ..Default::default() diff --git a/crates/zed2/src/languages/html.rs b/crates/zed2/src/languages/html.rs index b46675dd79..b8f1c70cce 100644 --- a/crates/zed2/src/languages/html.rs +++ b/crates/zed2/src/languages/html.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use smol::fs; diff --git a/crates/zed2/src/languages/json.rs b/crates/zed2/src/languages/json.rs index cb912f1042..63f909ae2a 100644 --- a/crates/zed2/src/languages/json.rs +++ b/crates/zed2/src/languages/json.rs @@ -1,14 +1,14 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use feature_flags2::FeatureFlagAppExt; +use feature_flags::FeatureFlagAppExt; use futures::{future::BoxFuture, FutureExt, StreamExt}; -use gpui2::AppContext; -use language2::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use gpui::AppContext; +use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; -use settings2::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; +use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; use smol::fs; use std::{ any::Any, diff --git a/crates/zed2/src/languages/lua.rs b/crates/zed2/src/languages/lua.rs index c92534925c..5fffb37e81 100644 --- a/crates/zed2/src/languages/lua.rs +++ b/crates/zed2/src/languages/lua.rs @@ -3,8 +3,8 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; -use language2::{LanguageServerName, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use smol::fs; use std::{any::Any, env::consts, path::PathBuf}; use util::{ diff --git a/crates/zed2/src/languages/php.rs b/crates/zed2/src/languages/php.rs index d6e462e186..3096fd16e6 100644 --- a/crates/zed2/src/languages/php.rs +++ b/crates/zed2/src/languages/php.rs @@ -3,8 +3,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use smol::{fs, stream::StreamExt}; @@ -91,9 +91,9 @@ impl LspAdapter for IntelephenseLspAdapter { async fn label_for_completion( &self, - _item: &lsp2::CompletionItem, - _language: &Arc, - ) -> Option { + _item: &lsp::CompletionItem, + _language: &Arc, + ) -> Option { None } diff --git a/crates/zed2/src/languages/python.rs b/crates/zed2/src/languages/python.rs index 8bbf022a17..3666237e69 100644 --- a/crates/zed2/src/languages/python.rs +++ b/crates/zed2/src/languages/python.rs @@ -1,7 +1,7 @@ use anyhow::Result; use async_trait::async_trait; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use smol::fs; use std::{ @@ -81,7 +81,7 @@ impl LspAdapter for PythonLspAdapter { get_cached_server_binary(container_dir, &*self.node).await } - async fn process_completion(&self, item: &mut lsp2::CompletionItem) { + async fn process_completion(&self, item: &mut lsp::CompletionItem) { // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. // Where `XX` is the sorting category, `YYYY` is based on most recent usage, // and `name` is the symbol name itself. @@ -104,19 +104,19 @@ impl LspAdapter for PythonLspAdapter { async fn label_for_completion( &self, - item: &lsp2::CompletionItem, - language: &Arc, - ) -> Option { + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { let label = &item.label; let grammar = language.grammar()?; let highlight_id = match item.kind? { - lsp2::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, - lsp2::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?, - lsp2::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?, - lsp2::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, + lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, + lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?, + lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?, + lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, _ => return None, }; - Some(language2::CodeLabel { + Some(language::CodeLabel { text: label.clone(), runs: vec![(0..label.len(), highlight_id)], filter_range: 0..label.len(), @@ -126,23 +126,23 @@ impl LspAdapter for PythonLspAdapter { async fn label_for_symbol( &self, name: &str, - kind: lsp2::SymbolKind, - language: &Arc, - ) -> Option { + kind: lsp::SymbolKind, + language: &Arc, + ) -> Option { let (text, filter_range, display_range) = match kind { - lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => { + lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("def {}():\n", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::CLASS => { + lsp::SymbolKind::CLASS => { let text = format!("class {}:", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::CONSTANT => { + lsp::SymbolKind::CONSTANT => { let text = format!("{} = 0", name); let filter_range = 0..name.len(); let display_range = 0..filter_range.end; @@ -151,7 +151,7 @@ impl LspAdapter for PythonLspAdapter { _ => return None, }; - Some(language2::CodeLabel { + Some(language::CodeLabel { runs: language.highlight_text(&text.as_str().into(), display_range.clone()), text: text[display_range].to_string(), filter_range, @@ -177,12 +177,12 @@ async fn get_cached_server_binary( #[cfg(test)] mod tests { - use gpui2::{Context, ModelContext, TestAppContext}; - use language2::{language_settings::AllLanguageSettings, AutoindentMode, Buffer}; - use settings2::SettingsStore; + use gpui::{Context, ModelContext, TestAppContext}; + use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer}; + use settings::SettingsStore; use std::num::NonZeroU32; - #[gpui2::test] + #[gpui::test] async fn test_python_autoindent(cx: &mut TestAppContext) { // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); let language = @@ -190,7 +190,7 @@ mod tests { cx.update(|cx| { let test_settings = SettingsStore::test(cx); cx.set_global(test_settings); - language2::init(cx); + language::init(cx); cx.update_global::(|store, cx| { store.update_user_settings::(cx, |s| { s.defaults.tab_size = NonZeroU32::new(2); diff --git a/crates/zed2/src/languages/ruby.rs b/crates/zed2/src/languages/ruby.rs index 8718f1c757..3890b90dbd 100644 --- a/crates/zed2/src/languages/ruby.rs +++ b/crates/zed2/src/languages/ruby.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use std::{any::Any, path::PathBuf, sync::Arc}; pub struct RubyLanguageServer; @@ -53,25 +53,25 @@ impl LspAdapter for RubyLanguageServer { async fn label_for_completion( &self, - item: &lsp2::CompletionItem, - language: &Arc, - ) -> Option { + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { let label = &item.label; let grammar = language.grammar()?; let highlight_id = match item.kind? { - lsp2::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, - lsp2::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, - lsp2::CompletionItemKind::CLASS | lsp2::CompletionItemKind::MODULE => { + lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, + lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, + lsp::CompletionItemKind::CLASS | lsp::CompletionItemKind::MODULE => { grammar.highlight_id_for_name("type")? } - lsp2::CompletionItemKind::KEYWORD => { + lsp::CompletionItemKind::KEYWORD => { if label.starts_with(':') { grammar.highlight_id_for_name("string.special.symbol")? } else { grammar.highlight_id_for_name("keyword")? } } - lsp2::CompletionItemKind::VARIABLE => { + lsp::CompletionItemKind::VARIABLE => { if label.starts_with('@') { grammar.highlight_id_for_name("property")? } else { @@ -80,7 +80,7 @@ impl LspAdapter for RubyLanguageServer { } _ => return None, }; - Some(language2::CodeLabel { + Some(language::CodeLabel { text: label.clone(), runs: vec![(0..label.len(), highlight_id)], filter_range: 0..label.len(), @@ -90,12 +90,12 @@ impl LspAdapter for RubyLanguageServer { async fn label_for_symbol( &self, label: &str, - kind: lsp2::SymbolKind, - language: &Arc, - ) -> Option { + kind: lsp::SymbolKind, + language: &Arc, + ) -> Option { let grammar = language.grammar()?; match kind { - lsp2::SymbolKind::METHOD => { + lsp::SymbolKind::METHOD => { let mut parts = label.split('#'); let classes = parts.next()?; let method = parts.next()?; @@ -120,21 +120,21 @@ impl LspAdapter for RubyLanguageServer { ix += 1; let end_ix = ix + method.len(); runs.push((ix..end_ix, method_id)); - Some(language2::CodeLabel { + Some(language::CodeLabel { text: label.to_string(), runs, filter_range: 0..label.len(), }) } - lsp2::SymbolKind::CONSTANT => { + lsp::SymbolKind::CONSTANT => { let constant_id = grammar.highlight_id_for_name("constant")?; - Some(language2::CodeLabel { + Some(language::CodeLabel { text: label.to_string(), runs: vec![(0..label.len(), constant_id)], filter_range: 0..label.len(), }) } - lsp2::SymbolKind::CLASS | lsp2::SymbolKind::MODULE => { + lsp::SymbolKind::CLASS | lsp::SymbolKind::MODULE => { let class_id = grammar.highlight_id_for_name("type")?; let mut ix = 0; @@ -148,7 +148,7 @@ impl LspAdapter for RubyLanguageServer { ix = end_ix; } - Some(language2::CodeLabel { + Some(language::CodeLabel { text: label.to_string(), runs, filter_range: 0..label.len(), diff --git a/crates/zed2/src/languages/rust.rs b/crates/zed2/src/languages/rust.rs index a0abcedd07..961e6fe7f0 100644 --- a/crates/zed2/src/languages/rust.rs +++ b/crates/zed2/src/languages/rust.rs @@ -2,9 +2,9 @@ use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; -pub use language2::*; +pub use language::*; use lazy_static::lazy_static; -use lsp2::LanguageServerBinary; +use lsp::LanguageServerBinary; use regex::Regex; use smol::fs::{self, File}; use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; @@ -106,7 +106,7 @@ impl LspAdapter for RustLspAdapter { Some("rust-analyzer/flycheck".into()) } - fn process_diagnostics(&self, params: &mut lsp2::PublishDiagnosticsParams) { + fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { lazy_static! { static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap(); } @@ -128,11 +128,11 @@ impl LspAdapter for RustLspAdapter { async fn label_for_completion( &self, - completion: &lsp2::CompletionItem, + completion: &lsp::CompletionItem, language: &Arc, ) -> Option { match completion.kind { - Some(lsp2::CompletionItemKind::FIELD) if completion.detail.is_some() => { + Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => { let detail = completion.detail.as_ref().unwrap(); let name = &completion.label; let text = format!("{}: {}", name, detail); @@ -144,9 +144,9 @@ impl LspAdapter for RustLspAdapter { filter_range: 0..name.len(), }); } - Some(lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE) + Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) if completion.detail.is_some() - && completion.insert_text_format != Some(lsp2::InsertTextFormat::SNIPPET) => + && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let detail = completion.detail.as_ref().unwrap(); let name = &completion.label; @@ -159,7 +159,7 @@ impl LspAdapter for RustLspAdapter { filter_range: 0..name.len(), }); } - Some(lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD) + Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) if completion.detail.is_some() => { lazy_static! { @@ -188,12 +188,12 @@ impl LspAdapter for RustLspAdapter { } Some(kind) => { let highlight_name = match kind { - lsp2::CompletionItemKind::STRUCT - | lsp2::CompletionItemKind::INTERFACE - | lsp2::CompletionItemKind::ENUM => Some("type"), - lsp2::CompletionItemKind::ENUM_MEMBER => Some("variant"), - lsp2::CompletionItemKind::KEYWORD => Some("keyword"), - lsp2::CompletionItemKind::VALUE | lsp2::CompletionItemKind::CONSTANT => { + lsp::CompletionItemKind::STRUCT + | lsp::CompletionItemKind::INTERFACE + | lsp::CompletionItemKind::ENUM => Some("type"), + lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"), + lsp::CompletionItemKind::KEYWORD => Some("keyword"), + lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => { Some("constant") } _ => None, @@ -214,47 +214,47 @@ impl LspAdapter for RustLspAdapter { async fn label_for_symbol( &self, name: &str, - kind: lsp2::SymbolKind, + kind: lsp::SymbolKind, language: &Arc, ) -> Option { let (text, filter_range, display_range) = match kind { - lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => { + lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("fn {} () {{}}", name); let filter_range = 3..3 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::STRUCT => { + lsp::SymbolKind::STRUCT => { let text = format!("struct {} {{}}", name); let filter_range = 7..7 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::ENUM => { + lsp::SymbolKind::ENUM => { let text = format!("enum {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::INTERFACE => { + lsp::SymbolKind::INTERFACE => { let text = format!("trait {} {{}}", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::CONSTANT => { + lsp::SymbolKind::CONSTANT => { let text = format!("const {}: () = ();", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::MODULE => { + lsp::SymbolKind::MODULE => { let text = format!("mod {} {{}}", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } - lsp2::SymbolKind::TYPE_PARAMETER => { + lsp::SymbolKind::TYPE_PARAMETER => { let text = format!("type {} {{}}", name); let filter_range = 5..5 + name.len(); let display_range = 0..filter_range.end; @@ -294,29 +294,29 @@ mod tests { use super::*; use crate::languages::language; - use gpui2::{Context, Hsla, TestAppContext}; - use language2::language_settings::AllLanguageSettings; - use settings2::SettingsStore; - use theme2::SyntaxTheme; + use gpui::{Context, Hsla, TestAppContext}; + use language::language_settings::AllLanguageSettings; + use settings::SettingsStore; + use theme::SyntaxTheme; - #[gpui2::test] + #[gpui::test] async fn test_process_rust_diagnostics() { - let mut params = lsp2::PublishDiagnosticsParams { - uri: lsp2::Url::from_file_path("/a").unwrap(), + let mut params = lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/a").unwrap(), version: None, diagnostics: vec![ // no newlines - lsp2::Diagnostic { + lsp::Diagnostic { message: "use of moved value `a`".to_string(), ..Default::default() }, // newline at the end of a code span - lsp2::Diagnostic { + lsp::Diagnostic { message: "consider importing this struct: `use b::c;\n`".to_string(), ..Default::default() }, // code span starting right after a newline - lsp2::Diagnostic { + lsp::Diagnostic { message: "cannot borrow `self.d` as mutable\n`self` is a `&` reference" .to_string(), ..Default::default() @@ -340,7 +340,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] async fn test_rust_label_for_completion() { let language = language( "rust", @@ -365,8 +365,8 @@ mod tests { assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::FUNCTION), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), detail: Some("fn(&mut Option) -> Vec".to_string()), ..Default::default() @@ -387,8 +387,8 @@ mod tests { ); assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::FUNCTION), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), detail: Some("async fn(&mut Option) -> Vec".to_string()), ..Default::default() @@ -409,8 +409,8 @@ mod tests { ); assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::FIELD), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FIELD), label: "len".to_string(), detail: Some("usize".to_string()), ..Default::default() @@ -425,8 +425,8 @@ mod tests { assert_eq!( language - .label_for_completion(&lsp2::CompletionItem { - kind: Some(lsp2::CompletionItemKind::FUNCTION), + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), detail: Some("fn(&mut Option) -> Vec".to_string()), ..Default::default() @@ -447,7 +447,7 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] async fn test_rust_label_for_symbol() { let language = language( "rust", @@ -471,7 +471,7 @@ mod tests { assert_eq!( language - .label_for_symbol("hello", lsp2::SymbolKind::FUNCTION) + .label_for_symbol("hello", lsp::SymbolKind::FUNCTION) .await, Some(CodeLabel { text: "fn hello".to_string(), @@ -482,7 +482,7 @@ mod tests { assert_eq!( language - .label_for_symbol("World", lsp2::SymbolKind::TYPE_PARAMETER) + .label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER) .await, Some(CodeLabel { text: "type World".to_string(), @@ -492,13 +492,13 @@ mod tests { ); } - #[gpui2::test] + #[gpui::test] async fn test_rust_autoindent(cx: &mut TestAppContext) { // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| { let test_settings = SettingsStore::test(cx); cx.set_global(test_settings); - language2::init(cx); + language::init(cx); cx.update_global::(|store, cx| { store.update_user_settings::(cx, |s| { s.defaults.tab_size = NonZeroU32::new(2); diff --git a/crates/zed2/src/languages/svelte.rs b/crates/zed2/src/languages/svelte.rs index 53f52a6a30..34dab81772 100644 --- a/crates/zed2/src/languages/svelte.rs +++ b/crates/zed2/src/languages/svelte.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use smol::fs; diff --git a/crates/zed2/src/languages/tailwind.rs b/crates/zed2/src/languages/tailwind.rs index 0aa2154f1e..6d6006dbd4 100644 --- a/crates/zed2/src/languages/tailwind.rs +++ b/crates/zed2/src/languages/tailwind.rs @@ -5,9 +5,9 @@ use futures::{ future::{self, BoxFuture}, FutureExt, StreamExt, }; -use gpui2::AppContext; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::LanguageServerBinary; +use gpui::AppContext; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::{json, Value}; use smol::fs; diff --git a/crates/zed2/src/languages/typescript.rs b/crates/zed2/src/languages/typescript.rs index 8eecf25540..de0139b3b2 100644 --- a/crates/zed2/src/languages/typescript.rs +++ b/crates/zed2/src/languages/typescript.rs @@ -3,9 +3,9 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; -use gpui2::AppContext; -use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp2::{CodeActionKind, LanguageServerBinary}; +use gpui::AppContext; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; use smol::{fs, io::BufReader, stream::StreamExt}; @@ -129,10 +129,10 @@ impl LspAdapter for TypeScriptLspAdapter { async fn label_for_completion( &self, - item: &lsp2::CompletionItem, - language: &Arc, - ) -> Option { - use lsp2::CompletionItemKind as Kind; + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + use lsp::CompletionItemKind as Kind; let len = item.label.len(); let grammar = language.grammar()?; let highlight_id = match item.kind? { @@ -149,7 +149,7 @@ impl LspAdapter for TypeScriptLspAdapter { None => item.label.clone(), }; - Some(language2::CodeLabel { + Some(language::CodeLabel { text, runs: vec![(0..len, highlight_id)], filter_range: 0..len, @@ -300,9 +300,9 @@ impl LspAdapter for EsLintLspAdapter { async fn label_for_completion( &self, - _item: &lsp2::CompletionItem, - _language: &Arc, - ) -> Option { + _item: &lsp::CompletionItem, + _language: &Arc, + ) -> Option { None } @@ -335,10 +335,10 @@ async fn get_cached_eslint_server_binary( #[cfg(test)] mod tests { - use gpui2::{Context, TestAppContext}; + use gpui::{Context, TestAppContext}; use unindent::Unindent; - #[gpui2::test] + #[gpui::test] async fn test_outline(cx: &mut TestAppContext) { let language = crate::languages::language( "typescript", @@ -363,7 +363,7 @@ mod tests { .unindent(); let buffer = cx.build_model(|cx| { - language2::Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx) + language::Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx) }); let outline = buffer.update(cx, |buffer, _| buffer.snapshot().outline(None).unwrap()); assert_eq!( diff --git a/crates/zed2/src/languages/vue.rs b/crates/zed2/src/languages/vue.rs index 0c87c4bee8..16afd2e299 100644 --- a/crates/zed2/src/languages/vue.rs +++ b/crates/zed2/src/languages/vue.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, ensure, Result}; use async_trait::async_trait; use futures::StreamExt; -pub use language2::*; -use lsp2::{CodeActionKind, LanguageServerBinary}; +pub use language::*; +use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use parking_lot::Mutex; use serde_json::Value; @@ -148,10 +148,10 @@ impl super::LspAdapter for VueLspAdapter { async fn label_for_completion( &self, - item: &lsp2::CompletionItem, - language: &Arc, - ) -> Option { - use lsp2::CompletionItemKind as Kind; + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + use lsp::CompletionItemKind as Kind; let len = item.label.len(); let grammar = language.grammar()?; let highlight_id = match item.kind? { @@ -171,7 +171,7 @@ impl super::LspAdapter for VueLspAdapter { None => item.label.clone(), }; - Some(language2::CodeLabel { + Some(language::CodeLabel { text, runs: vec![(0..len, highlight_id)], filter_range: 0..len, diff --git a/crates/zed2/src/languages/yaml.rs b/crates/zed2/src/languages/yaml.rs index 338a7a7ade..8b438d0949 100644 --- a/crates/zed2/src/languages/yaml.rs +++ b/crates/zed2/src/languages/yaml.rs @@ -1,11 +1,11 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; -use gpui2::AppContext; -use language2::{ +use gpui::AppContext; +use language::{ language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate, }; -use lsp2::LanguageServerBinary; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::Value; use smol::fs; diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 1233bee327..78e94c591c 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -8,19 +8,19 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; -use client2::UserStore; -use db2::kvp::KEY_VALUE_STORE; -use fs2::RealFs; +use client::UserStore; +use db::kvp::KEY_VALUE_STORE; +use fs::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; -use gpui2::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; +use gpui::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; use isahc::{prelude::Configurable, Request}; -use language2::LanguageRegistry; +use language::LanguageRegistry; use log::LevelFilter; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use settings2::{ +use settings::{ default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, }; use simplelog::ConfigBuilder; @@ -114,7 +114,7 @@ fn main() { handle_settings_file_changes(user_settings_file_rx, cx); // handle_keymap_file_changes(user_keymap_file_rx, cx); - let client = client2::Client::new(http.clone(), cx); + let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); let copilot_language_server_id = languages.next_language_server_id(); languages.set_executor(cx.background_executor().clone()); @@ -122,19 +122,19 @@ fn main() { let languages = Arc::new(languages); let node_runtime = RealNodeRuntime::new(http.clone()); - language2::init(cx); + language::init(cx); languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); cx.set_global(client.clone()); - theme2::init(cx); + theme::init(cx); // context_menu::init(cx); - project2::Project::init(&client, cx); - client2::init(&client, cx); + project::Project::init(&client, cx); + client::init(&client, cx); // command_palette::init(cx); - language2::init(cx); + language::init(cx); // editor::init(cx); // go_to_line::init(cx); // file_finder::init(cx); @@ -147,7 +147,7 @@ fn main() { // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); // vim::init(cx); // terminal_view::init(cx); - copilot2::init( + copilot::init( copilot_language_server_id, http.clone(), node_runtime.clone(), @@ -197,7 +197,7 @@ fn main() { // theme_selector::init(cx); // activity_indicator::init(cx); // language_tools::init(cx); - call2::init(app_state.client.clone(), app_state.user_store.clone(), cx); + call::init(app_state.client.clone(), app_state.user_store.clone(), cx); // collab_ui::init(&app_state, cx); // feedback::init(cx); // welcome::init(cx); @@ -444,7 +444,7 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin std::process::exit(-1); } - let app_version = client2::ZED_APP_VERSION + let app_version = client::ZED_APP_VERSION .or(app_metadata.app_version) .map_or("dev".to_string(), |v| v.to_string()); @@ -512,11 +512,11 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin } fn upload_previous_panics(http: Arc, cx: &mut AppContext) { - let telemetry_settings = *client2::TelemetrySettings::get_global(cx); + let telemetry_settings = *client::TelemetrySettings::get_global(cx); cx.background_executor() .spawn(async move { - let panic_report_url = format!("{}/api/panic", &*client2::ZED_SERVER_URL); + let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; while let Some(child) = children.next().await { let child = child?; @@ -559,7 +559,7 @@ fn upload_previous_panics(http: Arc, cx: &mut AppContext) { if let Some(panic) = panic { let body = serde_json::to_string(&PanicRequest { panic, - token: client2::ZED_SECRET_CLIENT_TOKEN.into(), + token: client::ZED_SECRET_CLIENT_TOKEN.into(), }) .unwrap(); diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 4389f3012a..ea8bdd9f1c 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -4,8 +4,8 @@ mod only_instance; mod open_listener; pub use assets::*; -use client2::{Client, UserStore}; -use gpui2::{AsyncAppContext, Model}; +use client::{Client, UserStore}; +use gpui::{AsyncAppContext, Model}; pub use only_instance::*; pub use open_listener::*; From 04a8ee222b9450903f3d3e8cf43b0d04757c2031 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Nov 2023 12:01:22 -0600 Subject: [PATCH 59/66] Enforce a Send bound on next frame callbacks This required using mpsc channels to invoke frame callbacks on the main thread and send the receiver to the platform display link. Co-Authored-By: Julia Risley --- crates/gpui2/src/app.rs | 24 +++-- crates/gpui2/src/app/async_context.rs | 7 -- crates/gpui2/src/app/test_context.rs | 4 +- crates/gpui2/src/elements/img.rs | 2 - crates/gpui2/src/platform/mac/dispatcher.rs | 2 - crates/gpui2/src/window.rs | 100 +++++++++----------- 6 files changed, 57 insertions(+), 82 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 8de3734c4c..38e64dcf1c 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -39,18 +39,20 @@ use std::{ }; use util::http::{self, HttpClient}; +/// Temporary(?) wrapper around RefCell to help us debug any double borrows. +/// Strongly consider removing after stabilization. pub struct AppCell { app: RefCell, } + impl AppCell { pub fn borrow(&self) -> AppRef { AppRef(self.app.borrow()) } pub fn borrow_mut(&self) -> AppRefMut { - let thread_id = std::thread::current().id(); - - eprintln!(">>> borrowing {thread_id:?}"); + // let thread_id = std::thread::current().id(); + // dbg!("borrowed {thread_id:?}"); AppRefMut(self.app.borrow_mut()) } } @@ -84,7 +86,6 @@ impl App { let this = self.0.clone(); let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { - dbg!("run callback"); let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); @@ -110,14 +111,11 @@ impl App { F: 'static + FnMut(&mut AppContext), { let this = Rc::downgrade(&self.0); - self.0 - .borrow_mut() - .platform - .on_reopen(Box::new(move || { - if let Some(app) = this.upgrade() { - callback(&mut app.borrow_mut()); - } - })); + self.0.borrow_mut().platform.on_reopen(Box::new(move || { + if let Some(app) = this.upgrade() { + callback(&mut app.borrow_mut()); + } + })); self } @@ -139,7 +137,7 @@ impl App { } type ActionBuilder = fn(json: Option) -> anyhow::Result>; -type FrameCallback = Box; +pub(crate) type FrameCallback = Box; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index f45457936c..e3ae78d78f 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -28,7 +28,6 @@ impl Context for AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - dbg!("BUILD MODEL A"); let mut app = app.borrow_mut(); Ok(app.build_model(build_model)) } @@ -42,7 +41,6 @@ impl Context for AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - dbg!("UPDATE MODEL B"); let mut app = app.borrow_mut(); Ok(app.update_model(handle, update)) } @@ -52,7 +50,6 @@ impl Context for AsyncAppContext { F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { let app = self.app.upgrade().context("app was released")?; - dbg!("UPDATE WINDOW C"); let mut lock = app.borrow_mut(); lock.update_window(window, f) } @@ -64,7 +61,6 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - dbg!("REFRESH"); let mut lock = app.borrow_mut(); lock.refresh(); Ok(()) @@ -125,7 +121,6 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - dbg!("read global"); let app = app.borrow_mut(); Ok(read(app.global(), &app)) } @@ -135,7 +130,6 @@ impl AsyncAppContext { read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { let app = self.app.upgrade()?; - dbg!("try read global"); let app = app.borrow_mut(); Some(read(app.try_global()?, &app)) } @@ -148,7 +142,6 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - dbg!("update global"); let mut app = app.borrow_mut(); Ok(app.update_global(update)) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 7ef53d3e12..e731dccc6e 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,7 +1,7 @@ use crate::{ - AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, + AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, - TestPlatform, WindowContext, AppCell, + TestPlatform, WindowContext, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index a9950bfc0a..a35436d74e 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -125,9 +125,7 @@ where } else { cx.spawn(|_, mut cx| async move { if image_future.await.log_err().is_some() { - eprintln!(">>> on_next_frame"); cx.on_next_frame(|cx| cx.notify()); - eprintln!("<<< on_next_frame") } }) .detach() diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index a39688bafd..f5334912c6 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -42,7 +42,6 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch(&self, runnable: Runnable) { - println!("DISPATCH"); unsafe { dispatch_async_f( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0), @@ -53,7 +52,6 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch_on_main_thread(&self, runnable: Runnable) { - println!("DISPATCH ON MAIN THREAD"); unsafe { dispatch_async_f( dispatch_get_main_queue(), diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 6034727d82..2897c9f38e 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -410,67 +410,55 @@ impl<'a> WindowContext<'a> { } /// Schedule the given closure to be run directly after the current frame is rendered. - pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { - let f = Box::new(f); + pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) { + let handle = self.window.handle; let display_id = self.window.display_id; + if !self.frame_consumers.contains_key(&display_id) { + let (tx, mut rx) = mpsc::unbounded::<()>(); + self.platform.set_display_link_output_callback( + display_id, + Box::new(move |_current_time, _output_time| _ = tx.unbounded_send(())), + ); + + let consumer_task = self.app.spawn(|cx| async move { + while rx.next().await.is_some() { + cx.update(|cx| { + for callback in cx + .next_frame_callbacks + .get_mut(&display_id) + .unwrap() + .drain(..) + .collect::>() + { + callback(cx); + } + }) + .ok(); + + // Flush effects, then stop the display link if no new next_frame_callbacks have been added. + + cx.update(|cx| { + if cx.next_frame_callbacks.is_empty() { + cx.platform.stop_display_link(display_id); + } + }) + .ok(); + } + }); + self.frame_consumers.insert(display_id, consumer_task); + } + + if self.next_frame_callbacks.is_empty() { + self.platform.start_display_link(display_id); + } + self.next_frame_callbacks .entry(display_id) .or_default() - .push(f); - - self.frame_consumers.entry(display_id).or_insert_with(|| { - let (tx, rx) = mpsc::unbounded::<()>(); - - self.spawn(|cx| async move { - while rx.next().await.is_some() { - let _ = cx.update(|_, cx| { - for callback in cx - .app - .next_frame_callbacks - .get_mut(&display_id) - .unwrap() - .drain(..) - { - callback(cx); - } - }); - } - }) - }); - - if let Some(callbacks) = self.next_frame_callbacks.get_mut(&display_id) { - callbacks.push(f); - // If there was already a callback, it means that we already scheduled a frame. - if callbacks.len() > 1 { - return; - } - } else { - let mut async_cx = self.to_async(); - self.next_frame_callbacks.insert(display_id, vec![f]); - self.platform.set_display_link_output_callback( - display_id, - Box::new(move |_current_time, _output_time| { - let _ = async_cx.update(|_, cx| { - let callbacks = cx - .next_frame_callbacks - .get_mut(&display_id) - .unwrap() - .drain(..) - .collect::>(); - for callback in callbacks { - callback(cx); - } - - if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { - cx.platform.stop_display_link(display_id); - } - }); - }), - ); - } - - self.platform.start_display_link(display_id); + .push(Box::new(move |cx: &mut AppContext| { + cx.update_window(handle, |_root_view, cx| callback(cx)).ok(); + })); } /// Spawn the future returned by the given closure on the application thread pool. From 29d8390743c3a466c6c2be43f311801cdd902d99 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Nov 2023 12:28:58 -0600 Subject: [PATCH 60/66] Fix formatting --- crates/ui2/src/elements/avatar.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ui2/src/elements/avatar.rs b/crates/ui2/src/elements/avatar.rs index 357e573f7c..ff574a2042 100644 --- a/crates/ui2/src/elements/avatar.rs +++ b/crates/ui2/src/elements/avatar.rs @@ -74,10 +74,10 @@ mod stories { // "https://avatars.githubusercontent.com/u/1486634?v=4", // )) .child(Story::label(cx, "Rounded rectangle")) - // .child( - // Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - // .shape(Shape::RoundedRectangle), - // ) + // .child( + // Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") + // .shape(Shape::RoundedRectangle), + // ) } } } From 8b1b7a2f80a89dccd255eaa175c1205736cbac1e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 14:34:48 -0400 Subject: [PATCH 61/66] Checkpoint: Basic tab bar structure --- crates/workspace2/src/pane.rs | 677 ++++++++++++++++++---------- crates/workspace2/src/pane_group.rs | 46 +- crates/workspace2/src/workspace2.rs | 30 +- 3 files changed, 467 insertions(+), 286 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 43e4aa1b01..0bc875ef2d 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1,7 +1,7 @@ // mod dragged_item_receiver; use crate::{ - item::{Item, ItemHandle, WeakItemHandle}, + item::{Item, ItemHandle, ItemSettings, WeakItemHandle}, toolbar::Toolbar, workspace_settings::{AutosaveSetting, WorkspaceSettings}, SplitDirection, Workspace, @@ -9,8 +9,8 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui2::{ - AppContext, AsyncWindowContext, EntityId, EventEmitter, Model, PromptLevel, Task, View, - ViewContext, VisualContext, WeakView, WindowContext, + AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, Model, PromptLevel, + Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -25,6 +25,8 @@ use std::{ Arc, }, }; +use ui::{prelude::*, Icon, IconButton, IconColor, IconElement}; +use ui::{v_stack}; use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -1345,6 +1347,162 @@ impl Pane { }); } + fn render_tab( + &self, + ix: usize, + item: &Box, + detail: usize, + cx: &mut ViewContext<'_, Pane>, + ) -> impl Component { + let label = item.tab_content(Some(detail), cx); + + // let label = match (self.git_status, is_deleted) { + // (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone()) + // .color(LabelColor::Hidden) + // .set_strikethrough(true), + // (GitStatus::None, false) => Label::new(self.title.clone()), + // (GitStatus::Created, false) => { + // Label::new(self.title.clone()).color(LabelColor::Created) + // } + // (GitStatus::Modified, false) => { + // Label::new(self.title.clone()).color(LabelColor::Modified) + // } + // (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent), + // (GitStatus::Conflict, false) => Label::new(self.title.clone()), + // }; + + let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted); + + let (tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index { + false => ( + cx.theme().colors().tab_inactive, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, + ), + true => ( + cx.theme().colors().tab_active, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, + ), + }; + + let close_right = ItemSettings::get_global(cx).close_position.right(); + + div() + .id(item.id()) + // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) + // .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) + // .on_drop(|_view, state: View, cx| { + // eprintln!("{:?}", state.read(cx)); + // }) + .px_2() + .py_0p5() + .flex() + .items_center() + .justify_center() + .bg(tab_bg) + .hover(|h| h.bg(tab_hover_bg)) + .active(|a| a.bg(tab_active_bg)) + .child( + div() + .px_1() + .flex() + .items_center() + .gap_1p5() + .children(if item.has_conflict(cx) { + Some( + IconElement::new(Icon::ExclamationTriangle) + .size(ui::IconSize::Small) + .color(IconColor::Warning), + ) + } else if item.is_dirty(cx) { + Some( + IconElement::new(Icon::ExclamationTriangle) + .size(ui::IconSize::Small) + .color(IconColor::Info), + ) + } else { + None + }) + .children(if !close_right { + Some(close_icon()) + } else { + None + }) + .child(label) + .children(if close_right { + Some(close_icon()) + } else { + None + }), + ) + } + + fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl Component { + div() + .group("tab_bar") + .id("tab_bar") + .w_full() + .flex() + .bg(cx.theme().colors().tab_bar) + // Left Side + .child( + div() + .relative() + .px_1() + .flex() + .flex_none() + .gap_2() + // Nav Buttons + .child( + div() + .right_0() + .flex() + .items_center() + .gap_px() + .child(IconButton::new("navigate_backward", Icon::ArrowLeft).state( + InteractionState::Enabled.if_enabled(self.can_navigate_backward()), + )) + .child(IconButton::new("navigate_forward", Icon::ArrowRight).state( + InteractionState::Enabled.if_enabled(self.can_navigate_forward()), + )), + ), + ) + .child( + div().w_0().flex_1().h_full().child( + div().id("tabs").flex().overflow_x_scroll().children( + self.items + .iter() + .enumerate() + .zip(self.tab_details(cx)) + .map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)), + ), + ), + ) + // Right Side + .child( + div() + // We only use absolute here since we don't + // have opacity or `hidden()` yet + .absolute() + .neg_top_7() + .px_1() + .flex() + .flex_none() + .gap_2() + .group_hover("tab_bar", |this| this.top_0()) + // Nav Buttons + .child( + div() + .flex() + .items_center() + .gap_px() + .child(IconButton::new("plus", Icon::Plus)) + .child(IconButton::new("split", Icon::Split)), + ), + ) + } + // fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { // let theme = theme::current(cx).clone(); @@ -1500,42 +1658,42 @@ impl Pane { // row // } - // fn tab_details(&self, cx: &AppContext) -> Vec { - // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); + fn tab_details(&self, cx: &AppContext) -> Vec { + let mut tab_details = self.items.iter().map(|_| 0).collect::>(); - // let mut tab_descriptions = HashMap::default(); - // let mut done = false; - // while !done { - // done = true; + 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); - // } - // } - // } + // 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; - // } - // } - // } - // } + // If two or more items have the same tab description, increase eir 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 - // } + tab_details + } // fn render_tab( // item: &Box, @@ -1737,237 +1895,243 @@ impl Pane { // type Event = Event; // } -// impl View for Pane { -// fn ui_name() -> &'static str { -// "Pane" -// } +impl Render for Pane { + type Element = Div; + // fn ui_name() -> &'static str { + // "Pane" + // } -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// enum MouseNavigationHandler {} + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + v_stack() + .child(self.render_tab_bar(cx)) + .child(div() /* toolbar */) + .child(div() /* active item */) -// MouseEventHandler::new::(0, cx, |_, cx| { -// let active_item_index = self.active_item_index; + // enum MouseNavigationHandler {} -// if let Some(active_item) = self.active_item() { -// Flex::column() -// .with_child({ -// let theme = theme::current(cx).clone(); + // MouseEventHandler::new::(0, cx, |_, cx| { + // let active_item_index = self.active_item_index; -// let mut stack = Stack::new(); + // if let Some(active_item) = self.active_item() { + // Flex::column() + // .with_child({ + // let theme = theme::current(cx).clone(); -// enum TabBarEventHandler {} -// stack.add_child( -// MouseEventHandler::new::(0, cx, |_, _| { -// Empty::new() -// .contained() -// .with_style(theme.workspace.tab_bar.container) -// }) -// .on_down( -// MouseButton::Left, -// move |_, this, cx| { -// this.activate_item(active_item_index, true, true, cx); -// }, -// ), -// ); -// let tooltip_style = theme.tooltip.clone(); -// let tab_bar_theme = theme.workspace.tab_bar.clone(); + // let mut stack = Stack::new(); -// 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(); + // enum TabBarEventHandler {} + // stack.add_child( + // MouseEventHandler::new::(0, cx, |_, _| { + // Empty::new() + // .contained() + // .with_style(theme.workspace.tab_bar.container) + // }) + // .on_down( + // MouseButton::Left, + // move |_, this, cx| { + // this.activate_item(active_item_index, true, true, cx); + // }, + // ), + // ); + // let tooltip_style = theme.tooltip.clone(); + // let tab_bar_theme = theme.workspace.tab_bar.clone(); -// let 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")); + // 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(); -// 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(), -// ) -// } + // 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")); -// stack.add_child(tab_row); -// stack -// .constrained() -// .with_height(theme.workspace.tab_bar.height) -// .flex(1., false) -// .into_any_named("tab bar") -// }) -// .with_child({ -// enum PaneContentTabDropTarget {} -// dragged_item_receiver::( -// self, -// 0, -// self.active_item_index + 1, -// !self.can_split, -// if self.can_split { Some(100.) } else { None }, -// cx, -// { -// let toolbar = self.toolbar.clone(); -// let toolbar_hidden = toolbar.read(cx).hidden(); -// move |_, cx| { -// Flex::column() -// .with_children( -// (!toolbar_hidden) -// .then(|| ChildView::new(&toolbar, cx).expanded()), -// ) -// .with_child( -// ChildView::new(active_item.as_any(), cx).flex(1., true), -// ) -// } -// }, -// ) -// .flex(1., true) -// }) -// .with_child(ChildView::new(&self.tab_context_menu, cx)) -// .into_any() -// } else { -// enum EmptyPane {} -// let theme = theme::current(cx).clone(); + // 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(), + // ) + // } -// dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { -// self.render_blank_pane(&theme, cx) -// }) -// .on_down(MouseButton::Left, |_, _, cx| { -// cx.focus_parent(); -// }) -// .into_any() -// } -// }) -// .on_down( -// MouseButton::Navigate(NavigationDirection::Back), -// move |_, pane, cx| { -// if let Some(workspace) = pane.workspace.upgrade(cx) { -// let pane = cx.weak_handle(); -// cx.window_context().defer(move |cx| { -// workspace.update(cx, |workspace, cx| { -// workspace.go_back(pane, cx).detach_and_log_err(cx) -// }) -// }) -// } -// }, -// ) -// .on_down(MouseButton::Navigate(NavigationDirection::Forward), { -// move |_, pane, cx| { -// if let Some(workspace) = pane.workspace.upgrade(cx) { -// let pane = cx.weak_handle(); -// cx.window_context().defer(move |cx| { -// workspace.update(cx, |workspace, cx| { -// workspace.go_forward(pane, cx).detach_and_log_err(cx) -// }) -// }) -// } -// } -// }) -// .into_any_named("pane") -// } + // stack.add_child(tab_row); + // stack + // .constrained() + // .with_height(theme.workspace.tab_bar.height) + // .flex(1., false) + // .into_any_named("tab bar") + // }) + // .with_child({ + // enum PaneContentTabDropTarget {} + // dragged_item_receiver::( + // self, + // 0, + // self.active_item_index + 1, + // !self.can_split, + // if self.can_split { Some(100.) } else { None }, + // cx, + // { + // let toolbar = self.toolbar.clone(); + // let toolbar_hidden = toolbar.read(cx).hidden(); + // move |_, cx| { + // Flex::column() + // .with_children( + // (!toolbar_hidden) + // .then(|| ChildView::new(&toolbar, cx).expanded()), + // ) + // .with_child( + // ChildView::new(active_item.as_any(), cx).flex(1., true), + // ) + // } + // }, + // ) + // .flex(1., true) + // }) + // .with_child(ChildView::new(&self.tab_context_menu, cx)) + // .into_any() + // } else { + // enum EmptyPane {} + // let theme = theme::current(cx).clone(); -// fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { -// if !self.has_focus { -// self.has_focus = true; -// cx.emit(Event::Focus); -// cx.notify(); -// } + // dragged_item_receiver::(self, 0, 0, false, None, cx, |_, cx| { + // self.render_blank_pane(&theme, cx) + // }) + // .on_down(MouseButton::Left, |_, _, cx| { + // cx.focus_parent(); + // }) + // .into_any() + // } + // }) + // .on_down( + // MouseButton::Navigate(NavigationDirection::Back), + // move |_, pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace.go_back(pane, cx).detach_and_log_err(cx) + // }) + // }) + // } + // }, + // ) + // .on_down(MouseButton::Navigate(NavigationDirection::Forward), { + // move |_, pane, cx| { + // if let Some(workspace) = pane.workspace.upgrade(cx) { + // let pane = cx.weak_handle(); + // cx.window_context().defer(move |cx| { + // workspace.update(cx, |workspace, cx| { + // workspace.go_forward(pane, cx).detach_and_log_err(cx) + // }) + // }) + // } + // } + // }) + // .into_any_named("pane") + } -// self.toolbar.update(cx, |toolbar, cx| { -// toolbar.focus_changed(true, cx); -// }); + // fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + // if !self.has_focus { + // self.has_focus = true; + // cx.emit(Event::Focus); + // cx.notify(); + // } -// 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()); -// } -// } + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.focus_changed(true, cx); + // }); -// 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()); -// } -// } -// } + // 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()); + // } + // } -// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { -// self.has_focus = false; -// self.toolbar.update(cx, |toolbar, cx| { -// toolbar.focus_changed(false, cx); -// }); -// cx.notify(); -// } + // 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 update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { -// Self::reset_to_default_keymap_context(keymap); -// } -// } + // fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + // self.has_focus = false; + // self.toolbar.update(cx, |toolbar, cx| { + // toolbar.focus_changed(false, cx); + // }); + // cx.notify(); + // } + + // fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { + // Self::reset_to_default_keymap_context(keymap); + // } +} impl ItemNavHistory { pub fn push(&mut self, data: Option, cx: &mut WindowContext) { @@ -2747,3 +2911,16 @@ fn dirty_message_for(buffer_path: Option) -> String { // }) // } // } + +#[derive(Clone, Debug)] +struct DraggedTab { + title: String, +} + +impl Render for DraggedTab { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + div().w_8().h_4().bg(gpui2::red()) + } +} diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 22bbb946a7..a0526fdcba 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -6,12 +6,13 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{point, size, AnyElement, AnyView, Bounds, Model, Pixels, Point, View, ViewContext}; +use gpui2::{ + point, size, AnyElement, AnyView, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext, +}; use parking_lot::Mutex; use project2::Project; use serde::Deserialize; use std::sync::Arc; -use theme2::ThemeVariant; use ui::prelude::*; const HANDLE_HITBOX_SIZE: f32 = 4.0; @@ -128,10 +129,10 @@ impl PaneGroup { follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, - zoomed: Option<&AnyView>, + zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, - ) -> AnyElement { + ) -> impl Component { self.root.render( project, 0, @@ -189,36 +190,38 @@ impl Member { follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, - zoomed: Option<&AnyView>, + zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, - ) -> AnyElement { + ) -> impl Component { match self { Member::Pane(pane) => { - let pane_element = if Some(&**pane) == zoomed { - None - } else { - Some(pane) - }; + // todo!() + // let pane_element = if Some(pane.into()) == zoomed { + // None + // } else { + // Some(pane) + // }; + + div().child(pane.clone()).render() // Stack::new() // .with_child(pane_element.contained().with_border(leader_border)) // .with_children(leader_status_box) // .into_any() - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .bg(cx.theme().colors().editor) - .children(); + // let el = div() + // .flex() + // .flex_1() + // .gap_px() + // .w_full() + // .h_full() + // .bg(cx.theme().colors().editor) + // .children(); } Member::Axis(axis) => axis.render( project, basis + 1, - theme, follower_states, active_call, active_pane, @@ -541,11 +544,10 @@ impl PaneAxis { &self, project: &Model, basis: usize, - theme: &ThemeVariant, follower_states: &HashMap, FollowerState>, active_call: Option<&Model>, active_pane: &View, - zoomed: Option<&AnyView>, + zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index f3516c0fa4..92d5c7c73f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -28,10 +28,11 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui2::{ - div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, - Component, Div, Element, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, - ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription, Task, View, - ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, + AsyncWindowContext, Bounds, Component, Div, EntityId, EventEmitter, GlobalPixels, + Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, Styled, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use language2::LanguageRegistry; @@ -544,7 +545,7 @@ pub enum Event { pub struct Workspace { weak_self: WeakView, // modal: Option, - // zoomed: Option, + zoomed: Option, // zoomed_position: Option, center: PaneGroup, left_dock: View, @@ -622,7 +623,7 @@ impl Workspace { } project2::Event::Closed => { - cx.remove_window(); + // cx.remove_window(); } project2::Event::DeletedEntry(entry_id) => { @@ -763,7 +764,7 @@ impl Workspace { Workspace { weak_self: weak_handle.clone(), // modal: None, - // zoomed: None, + zoomed: None, // zoomed_position: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], @@ -2698,7 +2699,8 @@ impl Workspace { .id("titlebar") .on_click(|workspace, event, cx| { if event.up.click_count == 2 { - cx.zoom_window(); + // todo!() + // cx.zoom_window(); } }) .child("Collab title bar Item") // self.titlebar_item @@ -3805,12 +3807,12 @@ impl Render for Workspace { .child( div().flex().flex_col().flex_1().h_full().child( div().flex().flex_1().child(self.center.render( - project, - follower_states, - active_call, - active_pane, - zoomed, - app_state, + &self.project, + &self.follower_states, + self.active_call(), + &self.active_pane, + self.zoomed.as_ref(), + &self.app_state, cx, )), ), // .children( From 3605afc163c19ba857de67c403ac3c9493b25da5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 14:40:34 -0400 Subject: [PATCH 62/66] Render active item in pane --- crates/workspace2/src/pane.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 0bc875ef2d..4fbef2e6c1 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -25,8 +25,8 @@ use std::{ Arc, }, }; +use ui::v_stack; use ui::{prelude::*, Icon, IconButton, IconColor, IconElement}; -use ui::{v_stack}; use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -1897,15 +1897,17 @@ impl Pane { impl Render for Pane { type Element = Div; - // fn ui_name() -> &'static str { - // "Pane" - // } fn render(&mut self, cx: &mut ViewContext) -> Self::Element { v_stack() .child(self.render_tab_bar(cx)) .child(div() /* toolbar */) - .child(div() /* active item */) + .child(if let Some(item) = self.active_item() { + item.to_any().render() + } else { + // todo!() + div().child("Empty Pane").render() + }) // enum MouseNavigationHandler {} From 9c8220d04ec40d74087774c275b4d025abf3fda6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 15:04:52 -0400 Subject: [PATCH 63/66] Remove commented-out label code --- crates/workspace2/src/pane.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 4fbef2e6c1..b30ec0b7f8 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1355,22 +1355,6 @@ impl Pane { cx: &mut ViewContext<'_, Pane>, ) -> impl Component { let label = item.tab_content(Some(detail), cx); - - // let label = match (self.git_status, is_deleted) { - // (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone()) - // .color(LabelColor::Hidden) - // .set_strikethrough(true), - // (GitStatus::None, false) => Label::new(self.title.clone()), - // (GitStatus::Created, false) => { - // Label::new(self.title.clone()).color(LabelColor::Created) - // } - // (GitStatus::Modified, false) => { - // Label::new(self.title.clone()).color(LabelColor::Modified) - // } - // (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent), - // (GitStatus::Conflict, false) => Label::new(self.title.clone()), - // }; - let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted); let (tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index { From 91f3e9707aeec7e27da6a21ad1fac5fbf33f5d70 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Nov 2023 13:18:20 -0600 Subject: [PATCH 64/66] Use correct color values in quad fragment shader Co-Authored-By: Julia Risley --- crates/gpui2/src/platform/mac/shaders.metal | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/platform/mac/shaders.metal b/crates/gpui2/src/platform/mac/shaders.metal index 444842d9b2..0a3a2b2129 100644 --- a/crates/gpui2/src/platform/mac/shaders.metal +++ b/crates/gpui2/src/platform/mac/shaders.metal @@ -98,10 +98,10 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]], input.border_color.a *= 1. - saturate(0.5 - inset_distance); // Alpha-blend the border and the background. - float output_alpha = - quad.border_color.a + quad.background.a * (1. - quad.border_color.a); + float output_alpha = input.border_color.a + + input.background_color.a * (1. - input.border_color.a); float3 premultiplied_border_rgb = - input.border_color.rgb * quad.border_color.a; + input.border_color.rgb * input.border_color.a; float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a; float3 premultiplied_output_rgb = From 8283909dfd22b1ec8e972c672793772783d2a6bd Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 13:28:58 -0600 Subject: [PATCH 65/66] Disable selective warnings to make cargo check happy --- crates/workspace2/src/pane_group.rs | 2 +- crates/workspace2/src/status_bar.rs | 4 ++-- crates/workspace2/src/toolbar.rs | 10 ++++++---- crates/workspace2/src/workspace2.rs | 5 ++++- crates/zed2/src/main.rs | 5 ++++- crates/zed2/src/zed2.rs | 4 +++- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index a0526fdcba..e521c51bda 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -7,7 +7,7 @@ use db2::sqlez::{ statement::Statement, }; use gpui2::{ - point, size, AnyElement, AnyView, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext, + point, size, AnyElement, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext, }; use parking_lot::Mutex; use project2::Project; diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index 52134683d8..c2f78d9ad6 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -2,8 +2,8 @@ use std::any::TypeId; use crate::{ItemHandle, Pane}; use gpui2::{ - div, AnyView, Component, Div, Element, ParentElement, Render, Styled, Subscription, View, - ViewContext, WindowContext, + div, AnyView, Component, Div, ParentElement, Render, Styled, Subscription, View, ViewContext, + WindowContext, }; use theme2::ActiveTheme; use util::ResultExt; diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index 55429af791..c3d1e520c7 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -1,5 +1,7 @@ use crate::ItemHandle; -use gpui2::{AnyView, AppContext, EventEmitter, Render, View, ViewContext, WindowContext}; +use gpui2::{ + AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext, +}; pub trait ToolbarItemView: Render + EventEmitter { fn set_active_pane_item( @@ -28,7 +30,7 @@ pub trait ToolbarItemView: Render + EventEmitter { } trait ToolbarItemViewHandle: Send { - fn id(&self) -> usize; + fn id(&self) -> EntityId; fn to_any(&self) -> AnyView; fn set_active_pane_item( &self, @@ -258,8 +260,8 @@ impl Toolbar { } impl ToolbarItemViewHandle for View { - fn id(&self) -> usize { - self.id() + fn id(&self) -> EntityId { + self.entity_id() } fn to_any(&self) -> AnyView { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 2486463392..6d29751073 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,3 +1,6 @@ +#![allow(unused_variables, dead_code, unused_mut)] +// todo!() this is to make transition easier. + pub mod dock; pub mod item; pub mod notifications; @@ -2999,7 +3002,7 @@ impl Workspace { } Some(()) - }); + })?; } Ok(()) } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 05ca4690b8..f8b77fe9df 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -1,3 +1,6 @@ +#![allow(unused_variables, dead_code, unused_mut)] +// todo!() this is to make transition easier. + // Allow binary to be called Zed for a nice application menu when running executable directly #![allow(non_snake_case)] @@ -24,7 +27,7 @@ use settings::{ default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, }; use simplelog::ConfigBuilder; -use smol::{future::FutureExt, process::Command}; +use smol::process::Command; use std::{ env, ffi::OsStr, diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index fe57d12752..04778f29dd 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -1,3 +1,6 @@ +#![allow(unused_variables, dead_code, unused_mut)] +// todo!() this is to make transition easier. + mod assets; pub mod languages; mod only_instance; @@ -5,7 +8,6 @@ mod open_listener; pub use assets::*; use collections::HashMap; -use client::{Client, UserStore}; use gpui::{ point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions, WeakView, WindowBounds, WindowKind, WindowOptions, From 54969877a4660ea4cb2691d9f2daecd52780b4bf Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 21:17:31 +0100 Subject: [PATCH 66/66] Make the Zed2 window movable (#3218) This PR makes the Zed2 window movable and fixes a crash related to a `todo!()` that wasn't necessary. Release Notes: - N/A --- crates/workspace2/src/dock.rs | 2 +- crates/workspace2/src/workspace2.rs | 6 +++--- crates/zed2/src/zed2.rs | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 20a06d1658..9da9123a2f 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -622,7 +622,7 @@ impl StatusItemView for PanelButtons { _active_pane_item: Option<&dyn crate::ItemHandle>, _cx: &mut ViewContext, ) { - todo!() + // todo!(This is empty in the old `workspace::dock`) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 6d29751073..3d9b86a051 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1043,9 +1043,9 @@ impl Workspace { // dock.update(cx, |dock, cx| dock.add_panel(panel, cx)); // } - // pub fn status_bar(&self) -> &View { - // &self.status_bar - // } + pub fn status_bar(&self) -> &View { + &self.status_bar + } pub fn app_state(&self) -> &Arc { &self.app_state diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 04778f29dd..4f28536085 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -242,7 +242,7 @@ pub fn build_window_options( focus: false, show: false, kind: WindowKind::Normal, - is_movable: false, + is_movable: true, display_id: display.map(|display| display.id()), } } @@ -317,16 +317,16 @@ pub fn initialize_workspace( // feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) // }); // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - // workspace.status_bar().update(cx, |status_bar, cx| { - // status_bar.add_left_item(diagnostic_summary, cx); - // status_bar.add_left_item(activity_indicator, cx); + workspace.status_bar().update(cx, |status_bar, cx| { + // status_bar.add_left_item(diagnostic_summary, cx); + // status_bar.add_left_item(activity_indicator, cx); - // status_bar.add_right_item(feedback_button, cx); - // status_bar.add_right_item(copilot, cx); - // status_bar.add_right_item(active_buffer_language, cx); - // status_bar.add_right_item(vim_mode_indicator, cx); - // status_bar.add_right_item(cursor_position, cx); - // }); + // status_bar.add_right_item(feedback_button, cx); + // status_bar.add_right_item(copilot, cx); + // status_bar.add_right_item(active_buffer_language, cx); + // status_bar.add_right_item(vim_mode_indicator, cx); + // status_bar.add_right_item(cursor_position, cx); + }); // auto_update::notify_of_any_new_update(cx.weak_handle(), cx);