From c8b5b085f4bc8a3c425725beaa49bc5b4dddb8ab Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Oct 2023 12:27:20 +0200 Subject: [PATCH 001/156] 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 002/156] 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 003/156] 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 adc426b66872e5ccecfda85fa49d02039f8bc300 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 26 Oct 2023 18:19:46 +0200 Subject: [PATCH 004/156] v1 --- Cargo.lock | 6 +- crates/call2/Cargo.toml | 4 +- crates/call2/src/room.rs | 2 +- crates/gpui/src/executor.rs | 8 +- crates/gpui2/src/app.rs | 2 +- crates/gpui2/src/app/entity_map.rs | 2 +- crates/gpui2/src/app/test_context.rs | 40 +- crates/gpui2/src/executor.rs | 13 +- crates/gpui2/src/platform/test/dispatcher.rs | 13 +- crates/prettier2/Cargo.toml | 4 +- crates/prettier2/src/prettier2.rs | 2 +- crates/project2/Cargo.toml | 4 +- crates/project2/src/project2.rs | 74 +- crates/project2/src/project_tests.rs | 8150 +++++++++--------- crates/project2/src/worktree.rs | 89 +- 15 files changed, 4231 insertions(+), 4182 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 021f3a1a72..3dd85395b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1135,7 +1135,7 @@ dependencies = [ "audio2", "client2", "collections", - "fs", + "fs2", "futures 0.3.28", "gpui2", "language2", @@ -5899,7 +5899,7 @@ dependencies = [ "anyhow", "client2", "collections", - "fs", + "fs2", "futures 0.3.28", "gpui2", "language2", @@ -6066,7 +6066,7 @@ dependencies = [ "ctor", "db2", "env_logger 0.9.3", - "fs", + "fs2", "fsevent", "futures 0.3.28", "fuzzy2", diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index efc7ab326e..f0e47832ed 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -25,7 +25,7 @@ collections = { path = "../collections" } gpui2 = { path = "../gpui2" } log.workspace = true live_kit_client = { path = "../live_kit_client" } -fs = { path = "../fs" } +fs2 = { path = "../fs2" } language2 = { path = "../language2" } media = { path = "../media" } project2 = { path = "../project2" } @@ -43,7 +43,7 @@ serde_derive.workspace = true [dev-dependencies] client2 = { path = "../client2", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } +fs2 = { path = "../fs2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] } diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index 091ff17029..27ffe68c50 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -10,7 +10,7 @@ use client2::{ Client, ParticipantIndex, TypedEnvelope, User, UserStore, }; use collections::{BTreeMap, HashMap, HashSet}; -use fs::Fs; +use fs2::Fs; use futures::{FutureExt, StreamExt}; use gpui2::{ AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 474ea8364f..a7d81e5a4d 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -84,7 +84,7 @@ struct DeterministicState { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ExecutorEvent { PollRunnable { id: usize }, - EnqueuRunnable { id: usize }, + EnqueueRunnable { id: usize }, } #[cfg(any(test, feature = "test-support"))] @@ -199,7 +199,7 @@ impl Deterministic { let unparker = self.parker.lock().unparker(); let (runnable, task) = async_task::spawn_local(future, move |runnable| { let mut state = state.lock(); - state.push_to_history(ExecutorEvent::EnqueuRunnable { id }); + state.push_to_history(ExecutorEvent::EnqueueRunnable { id }); state .scheduled_from_foreground .entry(cx_id) @@ -229,7 +229,7 @@ impl Deterministic { let mut state = state.lock(); state .poll_history - .push(ExecutorEvent::EnqueuRunnable { id }); + .push(ExecutorEvent::EnqueueRunnable { id }); state .scheduled_from_background .push(BackgroundRunnable { id, runnable }); @@ -616,7 +616,7 @@ impl ExecutorEvent { pub fn id(&self) -> usize { match self { ExecutorEvent::PollRunnable { id } => *id, - ExecutorEvent::EnqueuRunnable { id } => *id, + ExecutorEvent::EnqueueRunnable { id } => *id, } } } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 35efe84e48..c8b2a94275 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -362,7 +362,7 @@ impl AppContext { self.observers.remove(&entity_id); self.event_listeners.remove(&entity_id); for mut release_callback in self.release_listeners.remove(&entity_id) { - release_callback(&mut entity, self); + release_callback(entity.as_mut(), self); } } } diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 0420517590..e8f3606611 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -211,7 +211,7 @@ impl Drop for AnyHandle { let count = entity_map .counts .get(self.entity_id) - .expect("Detected over-release of a handle."); + .expect("detected over-release of a handle."); let prev_count = count.fetch_sub(1, SeqCst); assert_ne!(prev_count, 0, "Detected over-release of a handle."); if prev_count == 1 { diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 274121b692..16d7504b2d 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,7 +1,8 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread, - ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, + AnyWindowHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Handle, + MainThread, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; +use futures::SinkExt; use parking_lot::Mutex; use std::{any::Any, future::Future, sync::Arc}; @@ -149,4 +150,39 @@ impl TestAppContext { executor: self.executor.clone(), } } + + pub fn subscribe( + &mut self, + entity: &Handle, + ) -> futures::channel::mpsc::UnboundedReceiver + where + T::Event: 'static + Send + Clone, + { + let (mut tx, rx) = futures::channel::mpsc::unbounded(); + entity + .update(self, |_, cx: &mut ModelContext| { + cx.subscribe(&entity, move |_, _, event, _| { + let _ = tx.send(event.clone()); + }) + }) + .detach(); + rx + } } + +// pub fn subscribe( +// entity: &impl Handle, +// cx: &mut TestAppContext, +// ) -> Observation +// where +// T::Event: Clone, +// { +// let (tx, rx) = smol::channel::unbounded(); +// let _subscription = cx.update(|cx| { +// cx.subscribe(entity, move |_, event, _| { +// let _ = smol::block_on(tx.send(event.clone())); +// }) +// }); + +// Observation { rx, _subscription } +// } diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 28d4b6d117..8f128329a2 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -147,8 +147,10 @@ impl Executor { Poll::Pending => { if !self.dispatcher.poll() { #[cfg(any(test, feature = "test-support"))] - if let Some(_) = self.dispatcher.as_test() { - panic!("blocked with nothing left to run") + if let Some(test) = self.dispatcher.as_test() { + if !test.parking_allowed() { + panic!("blocked with nothing left to run") + } } parker.park(); } @@ -216,7 +218,7 @@ impl Executor { #[cfg(any(test, feature = "test-support"))] pub fn simulate_random_delay(&self) -> impl Future { - self.dispatcher.as_test().unwrap().simulate_random_delay() + self.spawn(self.dispatcher.as_test().unwrap().simulate_random_delay()) } #[cfg(any(test, feature = "test-support"))] @@ -229,6 +231,11 @@ impl Executor { self.dispatcher.as_test().unwrap().run_until_parked() } + #[cfg(any(test, feature = "test-support"))] + pub fn allow_parking(&self) { + self.dispatcher.as_test().unwrap().allow_parking(); + } + pub fn num_cpus(&self) -> usize { num_cpus::get() } diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 0ed5638a8b..7c4af964b0 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -28,6 +28,7 @@ struct TestDispatcherState { time: Duration, is_main_thread: bool, next_id: TestDispatcherId, + allow_parking: bool, } impl TestDispatcher { @@ -40,6 +41,7 @@ impl TestDispatcher { time: Duration::ZERO, is_main_thread: true, next_id: TestDispatcherId(1), + allow_parking: false, }; TestDispatcher { @@ -66,7 +68,7 @@ impl TestDispatcher { self.state.lock().time = new_now; } - pub fn simulate_random_delay(&self) -> impl Future { + pub fn simulate_random_delay(&self) -> impl 'static + Send + Future { pub struct YieldNow { count: usize, } @@ -75,6 +77,7 @@ impl TestDispatcher { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + eprintln!("self.count: {}", self.count); if self.count > 0 { self.count -= 1; cx.waker().wake_by_ref(); @@ -93,6 +96,14 @@ impl TestDispatcher { pub fn run_until_parked(&self) { while self.poll() {} } + + pub fn parking_allowed(&self) -> bool { + self.state.lock().allow_parking + } + + pub fn allow_parking(&self) { + self.state.lock().allow_parking = true + } } impl Clone for TestDispatcher { diff --git a/crates/prettier2/Cargo.toml b/crates/prettier2/Cargo.toml index 8defd40262..b98124f72c 100644 --- a/crates/prettier2/Cargo.toml +++ b/crates/prettier2/Cargo.toml @@ -16,7 +16,7 @@ client2 = { path = "../client2" } collections = { path = "../collections"} language2 = { path = "../language2" } gpui2 = { path = "../gpui2" } -fs = { path = "../fs" } +fs2 = { path = "../fs2" } lsp2 = { path = "../lsp2" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } @@ -32,4 +32,4 @@ parking_lot.workspace = true [dev-dependencies] language2 = { path = "../language2", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } +fs2 = { path = "../fs2", features = ["test-support"] } diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index ed22d23157..ed1c91b9c1 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/crates/prettier2/src/prettier2.rs @@ -1,6 +1,6 @@ use anyhow::Context; use collections::{HashMap, HashSet}; -use fs::Fs; +use fs2::Fs; use gpui2::{AsyncAppContext, Handle}; use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff}; use lsp2::{LanguageServer, LanguageServerId}; diff --git a/crates/project2/Cargo.toml b/crates/project2/Cargo.toml index 98bf9b62be..b135b5367c 100644 --- a/crates/project2/Cargo.toml +++ b/crates/project2/Cargo.toml @@ -25,7 +25,7 @@ client2 = { path = "../client2" } clock = { path = "../clock" } collections = { path = "../collections" } db2 = { path = "../db2" } -fs = { path = "../fs" } +fs2 = { path = "../fs2" } fsevent = { path = "../fsevent" } fuzzy2 = { path = "../fuzzy2" } git = { path = "../git" } @@ -71,7 +71,7 @@ pretty_assertions.workspace = true client2 = { path = "../client2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } db2 = { path = "../db2", features = ["test-support"] } -fs = { path = "../fs", 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"] } diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index b0fde1c486..fc9ff32e5d 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -89,7 +89,7 @@ use util::{ post_inc, ResultExt, TryFutureExt as _, }; -pub use fs::*; +pub use fs2::*; pub use worktree::*; pub trait Item { @@ -842,39 +842,39 @@ impl Project { } } - // #[cfg(any(test, feature = "test-support"))] - // pub async fn test( - // fs: Arc, - // root_paths: impl IntoIterator, - // cx: &mut gpui::TestAppContext, - // ) -> Handle { - // let mut languages = LanguageRegistry::test(); - // languages.set_executor(cx.background()); - // let http_client = util::http::FakeHttpClient::with_404_response(); - // let client = cx.update(|cx| client2::Client::new(http_client.clone(), cx)); - // let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - // let project = cx.update(|cx| { - // Project::local( - // client, - // node_runtime::FakeNodeRuntime::new(), - // user_store, - // Arc::new(languages), - // fs, - // cx, - // ) - // }); - // for path in root_paths { - // let (tree, _) = project - // .update(cx, |project, cx| { - // project.find_or_create_local_worktree(path, true, cx) - // }) - // .await - // .unwrap(); - // tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete()) - // .await; - // } - // project - // } + #[cfg(any(test, feature = "test-support"))] + pub async fn test( + fs: Arc, + root_paths: impl IntoIterator, + cx: &mut gpui2::TestAppContext, + ) -> Handle { + 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 user_store = cx.entity(|cx| UserStore::new(client.clone(), http_client, cx)); + let project = cx.update(|cx| { + Project::local( + client, + node_runtime::FakeNodeRuntime::new(), + user_store, + Arc::new(languages), + fs, + cx, + ) + }); + for path in root_paths { + let (tree, _) = project + .update(cx, |project, cx| { + project.find_or_create_local_worktree(path, true, cx) + }) + .await + .unwrap(); + tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete()) + .await; + } + project + } /// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes. /// Instead, if appends the suffix to every input, this suffix is returned by this method. @@ -5199,7 +5199,7 @@ impl Project { fs.create_file( &abs_path, op.options - .map(|options| fs::CreateOptions { + .map(|options| fs2::CreateOptions { overwrite: options.overwrite.unwrap_or(false), ignore_if_exists: options.ignore_if_exists.unwrap_or(false), }) @@ -5222,7 +5222,7 @@ impl Project { &source_abs_path, &target_abs_path, op.options - .map(|options| fs::RenameOptions { + .map(|options| fs2::RenameOptions { overwrite: options.overwrite.unwrap_or(false), ignore_if_exists: options.ignore_if_exists.unwrap_or(false), }) @@ -5238,7 +5238,7 @@ impl Project { .map_err(|_| anyhow!("can't convert URI to path"))?; let options = op .options - .map(|options| fs::RemoveOptions { + .map(|options| fs2::RemoveOptions { recursive: options.recursive.unwrap_or(false), ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false), }) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 4ca8bb0fa1..ec34c7875a 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -1,4077 +1,4073 @@ -// use crate::{search::PathMatcher, worktree::WorktreeModelHandle, Event, *}; -// use fs::{FakeFs, RealFs}; -// use futures::{future, StreamExt}; -// use gpui::{executor::Deterministic, test::subscribe, AppContext}; -// use language2::{ -// language_settings::{AllLanguageSettings, LanguageSettingsContent}, -// tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, -// LineEnding, OffsetRangeExt, Point, ToPoint, -// }; -// use lsp2::Url; -// use parking_lot::Mutex; -// use pretty_assertions::assert_eq; -// use serde_json::json; -// use std::{cell::RefCell, os::unix, rc::Rc, task::Poll}; -// use unindent::Unindent as _; -// use util::{assert_set_eq, test::temp_tree}; - -// #[cfg(test)] -// #[ctor::ctor] -// fn init_logger() { -// if std::env::var("RUST_LOG").is_ok() { -// env_logger::init(); -// } -// } - -// #[gpui::test] -// async fn test_symlinks(cx: &mut gpui::TestAppContext) { -// init_test(cx); -// cx.foreground().allow_parking(); - -// let dir = temp_tree(json!({ -// "root": { -// "apple": "", -// "banana": { -// "carrot": { -// "date": "", -// "endive": "", -// } -// }, -// "fennel": { -// "grape": "", -// } -// } -// })); - -// let root_link_path = dir.path().join("root_link"); -// unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); -// unix::fs::symlink( -// &dir.path().join("root/fennel"), -// &dir.path().join("root/finnochio"), -// ) -// .unwrap(); - -// let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; -// project.read_with(cx, |project, cx| { -// let tree = project.worktrees(cx).next().unwrap().read(cx); -// assert_eq!(tree.file_count(), 5); -// assert_eq!( -// tree.inode_for_path("fennel/grape"), -// tree.inode_for_path("finnochio/grape") -// ); -// }); -// } - -// #[gpui::test] -// async fn test_managing_project_specific_settings( -// deterministic: Arc, -// cx: &mut gpui::TestAppContext, -// ) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/the-root", -// json!({ -// ".zed": { -// "settings.json": r#"{ "tab_size": 8 }"# -// }, -// "a": { -// "a.rs": "fn a() {\n A\n}" -// }, -// "b": { -// ".zed": { -// "settings.json": r#"{ "tab_size": 2 }"# -// }, -// "b.rs": "fn b() {\n B\n}" -// } -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; -// let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()); - -// deterministic.run_until_parked(); -// cx.read(|cx| { -// let tree = worktree.read(cx); - -// let settings_a = language_settings( -// None, -// Some( -// &(File::for_entry( -// tree.entry_for_path("a/a.rs").unwrap().clone(), -// worktree.clone(), -// ) as _), -// ), -// cx, -// ); -// let settings_b = language_settings( -// None, -// Some( -// &(File::for_entry( -// tree.entry_for_path("b/b.rs").unwrap().clone(), -// worktree.clone(), -// ) as _), -// ), -// cx, -// ); - -// assert_eq!(settings_a.tab_size.get(), 8); -// assert_eq!(settings_b.tab_size.get(), 2); -// }); -// } - -// #[gpui::test] -// async fn test_managing_language_servers( -// deterministic: Arc, -// cx: &mut gpui::TestAppContext, -// ) { -// init_test(cx); - -// let mut rust_language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut json_language = Language::new( -// LanguageConfig { -// name: "JSON".into(), -// path_suffixes: vec!["json".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_rust_servers = rust_language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "the-rust-language-server", -// capabilities: lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![".".to_string(), "::".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// let mut fake_json_servers = json_language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "the-json-language-server", -// capabilities: lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![":".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/the-root", -// json!({ -// "test.rs": "const A: i32 = 1;", -// "test2.rs": "", -// "Cargo.toml": "a = 1", -// "package.json": "{\"a\": 1}", -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; - -// // Open a buffer without an associated language server. -// let toml_buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/the-root/Cargo.toml", cx) -// }) -// .await -// .unwrap(); - -// // Open a buffer with an associated language server before the language for it has been loaded. -// let rust_buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/the-root/test.rs", cx) -// }) -// .await -// .unwrap(); -// rust_buffer.read_with(cx, |buffer, _| { -// assert_eq!(buffer.language().map(|l| l.name()), None); -// }); - -// // Now we add the languages to the project, and ensure they get assigned to all -// // the relevant open buffers. -// project.update(cx, |project, _| { -// project.languages.add(Arc::new(json_language)); -// project.languages.add(Arc::new(rust_language)); -// }); -// deterministic.run_until_parked(); -// rust_buffer.read_with(cx, |buffer, _| { -// assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into())); -// }); - -// // A server is started up, and it is notified about Rust files. -// let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), -// version: 0, -// text: "const A: i32 = 1;".to_string(), -// language_id: Default::default() -// } -// ); - -// // The buffer is configured based on the language server's capabilities. -// rust_buffer.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer.completion_triggers(), -// &[".".to_string(), "::".to_string()] -// ); -// }); -// toml_buffer.read_with(cx, |buffer, _| { -// assert!(buffer.completion_triggers().is_empty()); -// }); - -// // Edit a buffer. The changes are reported to the language server. -// rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx)); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::VersionedTextDocumentIdentifier::new( -// lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), -// 1 -// ) -// ); - -// // Open a third buffer with a different associated language server. -// let json_buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/the-root/package.json", cx) -// }) -// .await -// .unwrap(); - -// // A json language server is started up and is only notified about the json buffer. -// let mut fake_json_server = fake_json_servers.next().await.unwrap(); -// assert_eq!( -// fake_json_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(), -// version: 0, -// text: "{\"a\": 1}".to_string(), -// language_id: Default::default() -// } -// ); - -// // This buffer is configured based on the second language server's -// // capabilities. -// json_buffer.read_with(cx, |buffer, _| { -// assert_eq!(buffer.completion_triggers(), &[":".to_string()]); -// }); - -// // When opening another buffer whose language server is already running, -// // it is also configured based on the existing language server's capabilities. -// let rust_buffer2 = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/the-root/test2.rs", cx) -// }) -// .await -// .unwrap(); -// rust_buffer2.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer.completion_triggers(), -// &[".".to_string(), "::".to_string()] -// ); -// }); - -// // Changes are reported only to servers matching the buffer's language. -// toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx)); -// rust_buffer2.update(cx, |buffer, cx| { -// buffer.edit([(0..0, "let x = 1;")], None, cx) -// }); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::VersionedTextDocumentIdentifier::new( -// lsp2::Url::from_file_path("/the-root/test2.rs").unwrap(), -// 1 -// ) -// ); - -// // Save notifications are reported to all servers. -// project -// .update(cx, |project, cx| project.save_buffer(toml_buffer, cx)) -// .await -// .unwrap(); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentIdentifier::new( -// lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap() -// ) -// ); -// assert_eq!( -// fake_json_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentIdentifier::new( -// lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap() -// ) -// ); - -// // Renames are reported only to servers matching the buffer's language. -// fs.rename( -// Path::new("/the-root/test2.rs"), -// Path::new("/the-root/test3.rs"), -// Default::default(), -// ) -// .await -// .unwrap(); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test2.rs").unwrap()), -// ); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(), -// version: 0, -// text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), -// language_id: Default::default() -// }, -// ); - -// rust_buffer2.update(cx, |buffer, cx| { -// buffer.update_diagnostics( -// LanguageServerId(0), -// DiagnosticSet::from_sorted_entries( -// vec![DiagnosticEntry { -// diagnostic: Default::default(), -// range: Anchor::MIN..Anchor::MAX, -// }], -// &buffer.snapshot(), -// ), -// cx, -// ); -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, usize>(0..buffer.len(), false) -// .count(), -// 1 -// ); -// }); - -// // When the rename changes the extension of the file, the buffer gets closed on the old -// // language server and gets opened on the new one. -// fs.rename( -// Path::new("/the-root/test3.rs"), -// Path::new("/the-root/test3.json"), -// Default::default(), -// ) -// .await -// .unwrap(); -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(),), -// ); -// assert_eq!( -// fake_json_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), -// version: 0, -// text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), -// language_id: Default::default() -// }, -// ); - -// // We clear the diagnostics, since the language has changed. -// rust_buffer2.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, usize>(0..buffer.len(), false) -// .count(), -// 0 -// ); -// }); - -// // The renamed file's version resets after changing language server. -// rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx)); -// assert_eq!( -// fake_json_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::VersionedTextDocumentIdentifier::new( -// lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), -// 1 -// ) -// ); - -// // Restart language servers -// project.update(cx, |project, cx| { -// project.restart_language_servers_for_buffers( -// vec![rust_buffer.clone(), json_buffer.clone()], -// cx, -// ); -// }); - -// let mut rust_shutdown_requests = fake_rust_server -// .handle_request::(|_, _| future::ready(Ok(()))); -// let mut json_shutdown_requests = fake_json_server -// .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(); -// let mut fake_json_server = fake_json_servers.next().await.unwrap(); - -// // Ensure rust document is reopened in new rust language server -// assert_eq!( -// fake_rust_server -// .receive_notification::() -// .await -// .text_document, -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), -// version: 0, -// text: rust_buffer.read_with(cx, |buffer, _| buffer.text()), -// language_id: Default::default() -// } -// ); - -// // Ensure json documents are reopened in new json language server -// assert_set_eq!( -// [ -// fake_json_server -// .receive_notification::() -// .await -// .text_document, -// fake_json_server -// .receive_notification::() -// .await -// .text_document, -// ], -// [ -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(), -// version: 0, -// text: json_buffer.read_with(cx, |buffer, _| buffer.text()), -// language_id: Default::default() -// }, -// lsp2::TextDocumentItem { -// uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), -// version: 0, -// text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), -// language_id: Default::default() -// } -// ] -// ); - -// // 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(), -// ), -// }; -// assert_eq!( -// fake_json_server -// .receive_notification::() -// .await, -// close_message, -// ); -// } - -// #[gpui::test] -// async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "the-language-server", -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/the-root", -// json!({ -// ".gitignore": "target\n", -// "src": { -// "a.rs": "", -// "b.rs": "", -// }, -// "target": { -// "x": { -// "out": { -// "x.rs": "" -// } -// }, -// "y": { -// "out": { -// "y.rs": "", -// } -// }, -// "z": { -// "out": { -// "z.rs": "" -// } -// } -// } -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; -// project.update(cx, |project, _| { -// project.languages.add(Arc::new(language)); -// }); -// cx.foreground().run_until_parked(); - -// // Start the language server by opening a buffer with a compatible file extension. -// let _buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/the-root/src/a.rs", cx) -// }) -// .await -// .unwrap(); - -// // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them. -// project.read_with(cx, |project, cx| { -// let worktree = project.worktrees(cx).next().unwrap(); -// assert_eq!( -// worktree -// .read(cx) -// .snapshot() -// .entries(true) -// .map(|entry| (entry.path.as_ref(), entry.is_ignored)) -// .collect::>(), -// &[ -// (Path::new(""), false), -// (Path::new(".gitignore"), false), -// (Path::new("src"), false), -// (Path::new("src/a.rs"), false), -// (Path::new("src/b.rs"), false), -// (Path::new("target"), true), -// ] -// ); -// }); - -// let prev_read_dir_count = fs.read_dir_call_count(); - -// // Keep track of the FS events reported to the language server. -// 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 { -// id: Default::default(), -// method: "workspace/didChangeWatchedFiles".to_string(), -// register_options: serde_json::to_value( -// lsp::DidChangeWatchedFilesRegistrationOptions { -// watchers: vec![ -// lsp2::FileSystemWatcher { -// glob_pattern: lsp2::GlobPattern::String( -// "/the-root/Cargo.toml".to_string(), -// ), -// kind: None, -// }, -// lsp2::FileSystemWatcher { -// glob_pattern: lsp2::GlobPattern::String( -// "/the-root/src/*.{rs,c}".to_string(), -// ), -// kind: None, -// }, -// lsp2::FileSystemWatcher { -// glob_pattern: lsp2::GlobPattern::String( -// "/the-root/target/y/**/*.rs".to_string(), -// ), -// kind: None, -// }, -// ], -// }, -// ) -// .ok(), -// }], -// }) -// .await -// .unwrap(); -// fake_server.handle_notification::({ -// let file_changes = file_changes.clone(); -// move |params, _| { -// let mut file_changes = file_changes.lock(); -// file_changes.extend(params.changes); -// file_changes.sort_by(|a, b| a.uri.cmp(&b.uri)); -// } -// }); - -// cx.foreground().run_until_parked(); -// assert_eq!(mem::take(&mut *file_changes.lock()), &[]); -// assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4); - -// // Now the language server has asked us to watch an ignored directory path, -// // so we recursively load it. -// project.read_with(cx, |project, cx| { -// let worktree = project.worktrees(cx).next().unwrap(); -// assert_eq!( -// worktree -// .read(cx) -// .snapshot() -// .entries(true) -// .map(|entry| (entry.path.as_ref(), entry.is_ignored)) -// .collect::>(), -// &[ -// (Path::new(""), false), -// (Path::new(".gitignore"), false), -// (Path::new("src"), false), -// (Path::new("src/a.rs"), false), -// (Path::new("src/b.rs"), false), -// (Path::new("target"), true), -// (Path::new("target/x"), true), -// (Path::new("target/y"), true), -// (Path::new("target/y/out"), true), -// (Path::new("target/y/out/y.rs"), true), -// (Path::new("target/z"), true), -// ] -// ); -// }); - -// // Perform some file system mutations, two of which match the watched patterns, -// // and one of which does not. -// fs.create_file("/the-root/src/c.rs".as_ref(), Default::default()) -// .await -// .unwrap(); -// fs.create_file("/the-root/src/d.txt".as_ref(), Default::default()) -// .await -// .unwrap(); -// fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default()) -// .await -// .unwrap(); -// fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default()) -// .await -// .unwrap(); -// fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default()) -// .await -// .unwrap(); - -// // The language server receives events for the FS mutations that match its watch patterns. -// cx.foreground().run_until_parked(); -// assert_eq!( -// &*file_changes.lock(), -// &[ -// lsp2::FileEvent { -// uri: lsp2::Url::from_file_path("/the-root/src/b.rs").unwrap(), -// typ: lsp2::FileChangeType::DELETED, -// }, -// lsp2::FileEvent { -// uri: lsp2::Url::from_file_path("/the-root/src/c.rs").unwrap(), -// typ: lsp2::FileChangeType::CREATED, -// }, -// lsp2::FileEvent { -// uri: lsp2::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(), -// typ: lsp2::FileChangeType::CREATED, -// }, -// ] -// ); -// } - -// #[gpui::test] -// async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": "let a = 1;", -// "b.rs": "let b = 2;" -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await; - -// let buffer_a = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); -// let buffer_b = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) -// .await -// .unwrap(); - -// project.update(cx, |project, cx| { -// project -// .update_diagnostics( -// LanguageServerId(0), -// 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), -// message: "error 1".to_string(), -// ..Default::default() -// }], -// }, -// &[], -// cx, -// ) -// .unwrap(); -// project -// .update_diagnostics( -// LanguageServerId(0), -// 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), -// message: "error 2".to_string(), -// ..Default::default() -// }], -// }, -// &[], -// cx, -// ) -// .unwrap(); -// }); - -// buffer_a.read_with(cx, |buffer, _| { -// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); -// assert_eq!( -// chunks -// .iter() -// .map(|(s, d)| (s.as_str(), *d)) -// .collect::>(), -// &[ -// ("let ", None), -// ("a", Some(DiagnosticSeverity::ERROR)), -// (" = 1;", None), -// ] -// ); -// }); -// buffer_b.read_with(cx, |buffer, _| { -// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); -// assert_eq!( -// chunks -// .iter() -// .map(|(s, d)| (s.as_str(), *d)) -// .collect::>(), -// &[ -// ("let ", None), -// ("b", Some(DiagnosticSeverity::WARNING)), -// (" = 2;", None), -// ] -// ); -// }); -// } - -// #[gpui::test] -// async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/root", -// json!({ -// "dir": { -// "a.rs": "let a = 1;", -// }, -// "other.rs": "let b = c;" -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/root/dir".as_ref()], cx).await; - -// let (worktree, _) = project -// .update(cx, |project, cx| { -// project.find_or_create_local_worktree("/root/other.rs", false, cx) -// }) -// .await -// .unwrap(); -// let worktree_id = worktree.read_with(cx, |tree, _| tree.id()); - -// project.update(cx, |project, cx| { -// project -// .update_diagnostics( -// LanguageServerId(0), -// 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), -// message: "unknown variable 'c'".to_string(), -// ..Default::default() -// }], -// }, -// &[], -// cx, -// ) -// .unwrap(); -// }); - -// let buffer = project -// .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx)) -// .await -// .unwrap(); -// buffer.read_with(cx, |buffer, _| { -// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); -// assert_eq!( -// chunks -// .iter() -// .map(|(s, d)| (s.as_str(), *d)) -// .collect::>(), -// &[ -// ("let b = ", None), -// ("c", Some(DiagnosticSeverity::ERROR)), -// (";", None), -// ] -// ); -// }); - -// project.read_with(cx, |project, cx| { -// assert_eq!(project.diagnostic_summaries(cx).next(), None); -// assert_eq!(project.diagnostic_summary(cx).error_count, 0); -// }); -// } - -// #[gpui::test] -// async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let progress_token = "the-progress-token"; -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// disk_based_diagnostics_progress_token: Some(progress_token.into()), -// disk_based_diagnostics_sources: vec!["disk".into()], -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": "fn a() { A }", -// "b.rs": "const y: i32 = 1", -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let worktree_id = project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id()); - -// // Cause worktree to start the fake language server -// let _buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) -// .await -// .unwrap(); - -// let mut events = subscribe(&project, cx); - -// let fake_server = fake_servers.next().await.unwrap(); -// assert_eq!( -// events.next().await.unwrap(), -// Event::LanguageServerAdded(LanguageServerId(0)), -// ); - -// fake_server -// .start_progress(format!("{}/0", progress_token)) -// .await; -// assert_eq!( -// events.next().await.unwrap(), -// Event::DiskBasedDiagnosticsStarted { -// language_server_id: LanguageServerId(0), -// } -// ); - -// fake_server.notify::(lsp2::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), -// message: "undefined variable 'A'".to_string(), -// ..Default::default() -// }], -// }); -// assert_eq!( -// events.next().await.unwrap(), -// Event::DiagnosticsUpdated { -// language_server_id: LanguageServerId(0), -// path: (worktree_id, Path::new("a.rs")).into() -// } -// ); - -// fake_server.end_progress(format!("{}/0", progress_token)); -// assert_eq!( -// events.next().await.unwrap(), -// Event::DiskBasedDiagnosticsFinished { -// language_server_id: LanguageServerId(0) -// } -// ); - -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// buffer.read_with(cx, |buffer, _| { -// let snapshot = buffer.snapshot(); -// let diagnostics = snapshot -// .diagnostics_in_range::<_, Point>(0..buffer.len(), false) -// .collect::>(); -// assert_eq!( -// diagnostics, -// &[DiagnosticEntry { -// range: Point::new(0, 9)..Point::new(0, 10), -// diagnostic: Diagnostic { -// severity: lsp2::DiagnosticSeverity::ERROR, -// message: "undefined variable 'A'".to_string(), -// group_id: 0, -// is_primary: true, -// ..Default::default() -// } -// }] -// ) -// }); - -// // Ensure publishing empty diagnostics twice only results in one update event. -// fake_server.notify::(lsp2::PublishDiagnosticsParams { -// uri: Url::from_file_path("/dir/a.rs").unwrap(), -// version: None, -// diagnostics: Default::default(), -// }); -// assert_eq!( -// events.next().await.unwrap(), -// Event::DiagnosticsUpdated { -// language_server_id: LanguageServerId(0), -// path: (worktree_id, Path::new("a.rs")).into() -// } -// ); - -// fake_server.notify::(lsp2::PublishDiagnosticsParams { -// uri: Url::from_file_path("/dir/a.rs").unwrap(), -// version: None, -// diagnostics: Default::default(), -// }); -// cx.foreground().run_until_parked(); -// assert_eq!(futures::poll!(events.next()), Poll::Pending); -// } - -// #[gpui::test] -// async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let progress_token = "the-progress-token"; -// let mut language = Language::new( -// LanguageConfig { -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// disk_based_diagnostics_sources: vec!["disk".into()], -// disk_based_diagnostics_progress_token: Some(progress_token.into()), -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": "" })).await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); - -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// // Simulate diagnostics starting to update. -// let fake_server = fake_servers.next().await.unwrap(); -// fake_server.start_progress(progress_token).await; - -// // Restart the server before the diagnostics finish updating. -// project.update(cx, |project, cx| { -// project.restart_language_servers_for_buffers([buffer], cx); -// }); -// let mut events = subscribe(&project, cx); - -// // Simulate the newly started server sending more diagnostics. -// let fake_server = fake_servers.next().await.unwrap(); -// assert_eq!( -// events.next().await.unwrap(), -// Event::LanguageServerAdded(LanguageServerId(1)) -// ); -// fake_server.start_progress(progress_token).await; -// assert_eq!( -// events.next().await.unwrap(), -// Event::DiskBasedDiagnosticsStarted { -// language_server_id: LanguageServerId(1) -// } -// ); -// project.read_with(cx, |project, _| { -// assert_eq!( -// project -// .language_servers_running_disk_based_diagnostics() -// .collect::>(), -// [LanguageServerId(1)] -// ); -// }); - -// // All diagnostics are considered done, despite the old server's diagnostic -// // task never completing. -// fake_server.end_progress(progress_token); -// assert_eq!( -// events.next().await.unwrap(), -// Event::DiskBasedDiagnosticsFinished { -// language_server_id: LanguageServerId(1) -// } -// ); -// project.read_with(cx, |project, _| { -// assert_eq!( -// project -// .language_servers_running_disk_based_diagnostics() -// .collect::>(), -// [LanguageServerId(0); 0] -// ); -// }); -// } - -// #[gpui::test] -// async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": "x" })).await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); - -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// // Publish diagnostics -// let fake_server = fake_servers.next().await.unwrap(); -// fake_server.notify::(lsp2::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), -// message: "the message".to_string(), -// ..Default::default() -// }], -// }); - -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, usize>(0..1, false) -// .map(|entry| entry.diagnostic.message.clone()) -// .collect::>(), -// ["the message".to_string()] -// ); -// }); -// project.read_with(cx, |project, cx| { -// assert_eq!( -// project.diagnostic_summary(cx), -// DiagnosticSummary { -// error_count: 1, -// warning_count: 0, -// } -// ); -// }); - -// project.update(cx, |project, cx| { -// project.restart_language_servers_for_buffers([buffer.clone()], cx); -// }); - -// // The diagnostics are cleared. -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, usize>(0..1, false) -// .map(|entry| entry.diagnostic.message.clone()) -// .collect::>(), -// Vec::::new(), -// ); -// }); -// project.read_with(cx, |project, cx| { -// assert_eq!( -// project.diagnostic_summary(cx), -// DiagnosticSummary { -// error_count: 0, -// warning_count: 0, -// } -// ); -// }); -// } - -// #[gpui::test] -// async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "the-lsp", -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": "" })).await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); - -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// // 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(), -// version: Some(10000), -// diagnostics: Vec::new(), -// }); -// cx.foreground().run_until_parked(); - -// project.update(cx, |project, cx| { -// project.restart_language_servers_for_buffers([buffer.clone()], cx); -// }); -// let mut fake_server = fake_servers.next().await.unwrap(); -// let notification = fake_server -// .receive_notification::() -// .await -// .text_document; -// assert_eq!(notification.version, 0); -// } - -// #[gpui::test] -// async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut rust = Language::new( -// LanguageConfig { -// name: Arc::from("Rust"), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_rust_servers = rust -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "rust-lsp", -// ..Default::default() -// })) -// .await; -// let mut js = Language::new( -// LanguageConfig { -// name: Arc::from("JavaScript"), -// path_suffixes: vec!["js".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_js_servers = js -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "js-lsp", -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| { -// project.languages.add(Arc::new(rust)); -// project.languages.add(Arc::new(js)); -// }); - -// let _rs_buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); -// let _js_buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx)) -// .await -// .unwrap(); - -// let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap(); -// assert_eq!( -// fake_rust_server_1 -// .receive_notification::() -// .await -// .text_document -// .uri -// .as_str(), -// "file:///dir/a.rs" -// ); - -// let mut fake_js_server = fake_js_servers.next().await.unwrap(); -// assert_eq!( -// fake_js_server -// .receive_notification::() -// .await -// .text_document -// .uri -// .as_str(), -// "file:///dir/b.js" -// ); - -// // Disable Rust language server, ensuring only that server gets stopped. -// cx.update(|cx| { -// cx.update_global(|settings: &mut SettingsStore, cx| { -// settings.update_user_settings::(cx, |settings| { -// settings.languages.insert( -// Arc::from("Rust"), -// LanguageSettingsContent { -// enable_language_server: Some(false), -// ..Default::default() -// }, -// ); -// }); -// }) -// }); -// fake_rust_server_1 -// .receive_notification::() -// .await; - -// // Enable Rust and disable JavaScript language servers, ensuring that the -// // former gets started again and that the latter stops. -// cx.update(|cx| { -// cx.update_global(|settings: &mut SettingsStore, cx| { -// settings.update_user_settings::(cx, |settings| { -// settings.languages.insert( -// Arc::from("Rust"), -// LanguageSettingsContent { -// enable_language_server: Some(true), -// ..Default::default() -// }, -// ); -// settings.languages.insert( -// Arc::from("JavaScript"), -// LanguageSettingsContent { -// enable_language_server: Some(false), -// ..Default::default() -// }, -// ); -// }); -// }) -// }); -// let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); -// assert_eq!( -// fake_rust_server_2 -// .receive_notification::() -// .await -// .text_document -// .uri -// .as_str(), -// "file:///dir/a.rs" -// ); -// fake_js_server -// .receive_notification::() -// .await; -// } - -// #[gpui::test(iterations = 3)] -// async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// disk_based_diagnostics_sources: vec!["disk".into()], -// ..Default::default() -// })) -// .await; - -// let text = " -// fn a() { A } -// fn b() { BB } -// fn c() { CCC } -// " -// .unindent(); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": text })).await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); - -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// let mut fake_server = fake_servers.next().await.unwrap(); -// let open_notification = fake_server -// .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::() -// .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(), -// 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)), -// 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)), -// 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)), -// severity: Some(DiagnosticSeverity::ERROR), -// source: Some("disk".to_string()), -// message: "undefined variable 'CCC'".to_string(), -// ..Default::default() -// }, -// ], -// }); - -// // The diagnostics have moved down since they were created. -// buffer.next_notification(cx).await; -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false) -// .collect::>(), -// &[ -// DiagnosticEntry { -// range: Point::new(3, 9)..Point::new(3, 11), -// diagnostic: Diagnostic { -// source: Some("disk".into()), -// severity: DiagnosticSeverity::ERROR, -// message: "undefined variable 'BB'".to_string(), -// is_disk_based: true, -// group_id: 1, -// is_primary: true, -// ..Default::default() -// }, -// }, -// DiagnosticEntry { -// range: Point::new(4, 9)..Point::new(4, 12), -// diagnostic: Diagnostic { -// source: Some("disk".into()), -// severity: DiagnosticSeverity::ERROR, -// message: "undefined variable 'CCC'".to_string(), -// is_disk_based: true, -// group_id: 2, -// is_primary: true, -// ..Default::default() -// } -// } -// ] -// ); -// assert_eq!( -// chunks_with_diagnostics(buffer, 0..buffer.len()), -// [ -// ("\n\nfn a() { ".to_string(), None), -// ("A".to_string(), Some(DiagnosticSeverity::ERROR)), -// (" }\nfn b() { ".to_string(), None), -// ("BB".to_string(), Some(DiagnosticSeverity::ERROR)), -// (" }\nfn c() { ".to_string(), None), -// ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)), -// (" }\n".to_string(), None), -// ] -// ); -// assert_eq!( -// chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)), -// [ -// ("B".to_string(), Some(DiagnosticSeverity::ERROR)), -// (" }\nfn c() { ".to_string(), None), -// ("CC".to_string(), Some(DiagnosticSeverity::ERROR)), -// ] -// ); -// }); - -// // Ensure overlapping diagnostics are highlighted correctly. -// fake_server.notify::(lsp2::PublishDiagnosticsParams { -// uri: lsp2::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)), -// 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)), -// severity: Some(DiagnosticSeverity::WARNING), -// message: "unreachable statement".to_string(), -// source: Some("disk".to_string()), -// ..Default::default() -// }, -// ], -// }); - -// buffer.next_notification(cx).await; -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false) -// .collect::>(), -// &[ -// DiagnosticEntry { -// range: Point::new(2, 9)..Point::new(2, 12), -// diagnostic: Diagnostic { -// source: Some("disk".into()), -// severity: DiagnosticSeverity::WARNING, -// message: "unreachable statement".to_string(), -// is_disk_based: true, -// group_id: 4, -// is_primary: true, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(2, 9)..Point::new(2, 10), -// diagnostic: Diagnostic { -// source: Some("disk".into()), -// severity: DiagnosticSeverity::ERROR, -// message: "undefined variable 'A'".to_string(), -// is_disk_based: true, -// group_id: 3, -// is_primary: true, -// ..Default::default() -// }, -// } -// ] -// ); -// assert_eq!( -// chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)), -// [ -// ("fn a() { ".to_string(), None), -// ("A".to_string(), Some(DiagnosticSeverity::ERROR)), -// (" }".to_string(), Some(DiagnosticSeverity::WARNING)), -// ("\n".to_string(), None), -// ] -// ); -// assert_eq!( -// chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)), -// [ -// (" }".to_string(), Some(DiagnosticSeverity::WARNING)), -// ("\n".to_string(), None), -// ] -// ); -// }); - -// // Keep editing the buffer and ensure disk-based diagnostics get translated according to the -// // changes since the last save. -// buffer.update(cx, |buffer, cx| { -// buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx); -// buffer.edit( -// [(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], -// None, -// cx, -// ); -// buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx); -// }); -// let change_notification_2 = fake_server -// .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(), -// 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)), -// 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)), -// severity: Some(DiagnosticSeverity::WARNING), -// message: "undefined variable 'A'".to_string(), -// source: Some("disk".to_string()), -// ..Default::default() -// }, -// ], -// }); - -// buffer.next_notification(cx).await; -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, _| { -// assert_eq!( -// buffer -// .snapshot() -// .diagnostics_in_range::<_, Point>(0..buffer.len(), false) -// .collect::>(), -// &[ -// DiagnosticEntry { -// range: Point::new(2, 21)..Point::new(2, 22), -// diagnostic: Diagnostic { -// source: Some("disk".into()), -// severity: DiagnosticSeverity::WARNING, -// message: "undefined variable 'A'".to_string(), -// is_disk_based: true, -// group_id: 6, -// is_primary: true, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(3, 9)..Point::new(3, 14), -// diagnostic: Diagnostic { -// source: Some("disk".into()), -// severity: DiagnosticSeverity::ERROR, -// message: "undefined variable 'BB'".to_string(), -// is_disk_based: true, -// group_id: 5, -// is_primary: true, -// ..Default::default() -// }, -// } -// ] -// ); -// }); -// } - -// #[gpui::test] -// async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let text = concat!( -// "let one = ;\n", // -// "let two = \n", -// "let three = 3;\n", -// ); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": text })).await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// project.update(cx, |project, cx| { -// project -// .update_buffer_diagnostics( -// &buffer, -// LanguageServerId(0), -// None, -// vec![ -// DiagnosticEntry { -// range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::ERROR, -// message: "syntax error 1".to_string(), -// ..Default::default() -// }, -// }, -// DiagnosticEntry { -// range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::ERROR, -// message: "syntax error 2".to_string(), -// ..Default::default() -// }, -// }, -// ], -// cx, -// ) -// .unwrap(); -// }); - -// // An empty range is extended forward to include the following character. -// // At the end of a line, an empty range is extended backward to include -// // the preceding character. -// buffer.read_with(cx, |buffer, _| { -// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); -// assert_eq!( -// chunks -// .iter() -// .map(|(s, d)| (s.as_str(), *d)) -// .collect::>(), -// &[ -// ("let one = ", None), -// (";", Some(DiagnosticSeverity::ERROR)), -// ("\nlet two =", None), -// (" ", Some(DiagnosticSeverity::ERROR)), -// ("\nlet three = 3;\n", None) -// ] -// ); -// }); -// } - -// #[gpui::test] -// async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({ "a.rs": "one two three" })) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; - -// project.update(cx, |project, cx| { -// project -// .update_diagnostic_entries( -// LanguageServerId(0), -// Path::new("/dir/a.rs").to_owned(), -// None, -// vec![DiagnosticEntry { -// range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::ERROR, -// is_primary: true, -// message: "syntax error a1".to_string(), -// ..Default::default() -// }, -// }], -// cx, -// ) -// .unwrap(); -// project -// .update_diagnostic_entries( -// LanguageServerId(1), -// Path::new("/dir/a.rs").to_owned(), -// None, -// vec![DiagnosticEntry { -// range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::ERROR, -// is_primary: true, -// message: "syntax error b1".to_string(), -// ..Default::default() -// }, -// }], -// cx, -// ) -// .unwrap(); - -// assert_eq!( -// project.diagnostic_summary(cx), -// DiagnosticSummary { -// error_count: 2, -// warning_count: 0, -// } -// ); -// }); -// } - -// #[gpui::test] -// async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await; - -// let text = " -// fn a() { -// f1(); -// } -// fn b() { -// f2(); -// } -// fn c() { -// f3(); -// } -// " -// .unindent(); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": text.clone(), -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// let mut fake_server = fake_servers.next().await.unwrap(); -// let lsp_document_version = fake_server -// .receive_notification::() -// .await -// .text_document -// .version; - -// // Simulate editing the buffer after the language server computes some edits. -// buffer.update(cx, |buffer, cx| { -// buffer.edit( -// [( -// Point::new(0, 0)..Point::new(0, 0), -// "// above first function\n", -// )], -// None, -// cx, -// ); -// buffer.edit( -// [( -// Point::new(2, 0)..Point::new(2, 0), -// " // inside first function\n", -// )], -// None, -// cx, -// ); -// buffer.edit( -// [( -// Point::new(6, 4)..Point::new(6, 4), -// "// inside second function ", -// )], -// None, -// cx, -// ); - -// assert_eq!( -// buffer.text(), -// " -// // above first function -// fn a() { -// // inside first function -// f1(); -// } -// fn b() { -// // inside second function f2(); -// } -// fn c() { -// f3(); -// } -// " -// .unindent() -// ); -// }); - -// let edits = project -// .update(cx, |project, cx| { -// project.edits_from_lsp( -// &buffer, -// vec![ -// // replace body of first function -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(0, 0), -// lsp2::Position::new(3, 0), -// ), -// new_text: " -// fn a() { -// f10(); -// } -// " -// .unindent(), -// }, -// // edit inside second function -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(4, 6), -// lsp2::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), -// ), -// new_text: "4000".into(), -// }, -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(7, 5), -// lsp2::Position::new(7, 6), -// ), -// new_text: "".into(), -// }, -// ], -// LanguageServerId(0), -// Some(lsp_document_version), -// cx, -// ) -// }) -// .await -// .unwrap(); - -// buffer.update(cx, |buffer, cx| { -// for (range, new_text) in edits { -// buffer.edit([(range, new_text)], None, cx); -// } -// assert_eq!( -// buffer.text(), -// " -// // above first function -// fn a() { -// // inside first function -// f10(); -// } -// fn b() { -// // inside second function f200(); -// } -// fn c() { -// f4000(); -// } -// " -// .unindent() -// ); -// }); -// } - -// #[gpui::test] -// async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let text = " -// use a::b; -// use a::c; - -// fn f() { -// b(); -// c(); -// } -// " -// .unindent(); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": text.clone(), -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// // Simulate the language server sending us a small edit in the form of a very large diff. -// // Rust-analyzer does this when performing a merge-imports code action. -// let edits = project -// .update(cx, |project, cx| { -// project.edits_from_lsp( -// &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), -// ), -// 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), -// ), -// new_text: "\n\n".into(), -// }, -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(0, 9), -// lsp2::Position::new(0, 9), -// ), -// new_text: " -// fn f() { -// b(); -// c(); -// }" -// .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), -// ), -// new_text: "".into(), -// }, -// ], -// LanguageServerId(0), -// None, -// cx, -// ) -// }) -// .await -// .unwrap(); - -// buffer.update(cx, |buffer, cx| { -// let edits = edits -// .into_iter() -// .map(|(range, text)| { -// ( -// range.start.to_point(buffer)..range.end.to_point(buffer), -// text, -// ) -// }) -// .collect::>(); - -// assert_eq!( -// edits, -// [ -// (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), -// (Point::new(1, 0)..Point::new(2, 0), "".into()) -// ] -// ); - -// for (range, new_text) in edits { -// buffer.edit([(range, new_text)], None, cx); -// } -// assert_eq!( -// buffer.text(), -// " -// use a::{b, c}; - -// fn f() { -// b(); -// c(); -// } -// " -// .unindent() -// ); -// }); -// } - -// #[gpui::test] -// async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let text = " -// use a::b; -// use a::c; - -// fn f() { -// b(); -// c(); -// } -// " -// .unindent(); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": text.clone(), -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); - -// // Simulate the language server sending us edits in a non-ordered fashion, -// // with ranges sometimes being inverted or pointing to invalid locations. -// let edits = project -// .update(cx, |project, cx| { -// project.edits_from_lsp( -// &buffer, -// [ -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(0, 9), -// lsp2::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), -// ), -// new_text: "a::{b, c}".into(), -// }, -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(1, 0), -// lsp2::Position::new(99, 0), -// ), -// new_text: "".into(), -// }, -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(0, 9), -// lsp2::Position::new(0, 9), -// ), -// new_text: " -// fn f() { -// b(); -// c(); -// }" -// .unindent(), -// }, -// ], -// LanguageServerId(0), -// None, -// cx, -// ) -// }) -// .await -// .unwrap(); - -// buffer.update(cx, |buffer, cx| { -// let edits = edits -// .into_iter() -// .map(|(range, text)| { -// ( -// range.start.to_point(buffer)..range.end.to_point(buffer), -// text, -// ) -// }) -// .collect::>(); - -// assert_eq!( -// edits, -// [ -// (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), -// (Point::new(1, 0)..Point::new(2, 0), "".into()) -// ] -// ); - -// for (range, new_text) in edits { -// buffer.edit([(range, new_text)], None, cx); -// } -// assert_eq!( -// buffer.text(), -// " -// use a::{b, c}; - -// fn f() { -// b(); -// c(); -// } -// " -// .unindent() -// ); -// }); -// } - -// fn chunks_with_diagnostics( -// buffer: &Buffer, -// range: Range, -// ) -> Vec<(String, Option)> { -// let mut chunks: Vec<(String, Option)> = Vec::new(); -// for chunk in buffer.snapshot().chunks(range, true) { -// if chunks.last().map_or(false, |prev_chunk| { -// prev_chunk.1 == chunk.diagnostic_severity -// }) { -// chunks.last_mut().unwrap().0.push_str(chunk.text); -// } else { -// chunks.push((chunk.text.to_string(), chunk.diagnostic_severity)); -// } -// } -// chunks -// } - -// #[gpui::test(iterations = 10)] -// async fn test_definition(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": "const fn a() { A }", -// "b.rs": "const y: i32 = crate::a()", -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); - -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) -// .await -// .unwrap(); - -// let fake_server = fake_servers.next().await.unwrap(); -// 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)); - -// 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)), -// ), -// ))) -// }); - -// let mut definitions = project -// .update(cx, |project, cx| project.definition(&buffer, 22, cx)) -// .await -// .unwrap(); - -// // Assert no new language server started -// cx.foreground().run_until_parked(); -// assert!(fake_servers.try_next().is_err()); - -// assert_eq!(definitions.len(), 1); -// let definition = definitions.pop().unwrap(); -// cx.update(|cx| { -// let target_buffer = definition.target.buffer.read(cx); -// assert_eq!( -// target_buffer -// .file() -// .unwrap() -// .as_local() -// .unwrap() -// .abs_path(cx), -// Path::new("/dir/a.rs"), -// ); -// assert_eq!(definition.target.range.to_offset(target_buffer), 9..10); -// assert_eq!( -// list_worktrees(&project, cx), -// [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)] -// ); - -// drop(definition); -// }); -// cx.read(|cx| { -// assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]); -// }); - -// fn list_worktrees<'a>( -// project: &'a ModelHandle, -// cx: &'a AppContext, -// ) -> Vec<(&'a Path, bool)> { -// project -// .read(cx) -// .worktrees(cx) -// .map(|worktree| { -// let worktree = worktree.read(cx); -// ( -// worktree.as_local().unwrap().abs_path().as_ref(), -// worktree.is_visible(), -// ) -// }) -// .collect::>() -// } -// } - -// #[gpui::test] -// async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "TypeScript".into(), -// path_suffixes: vec!["ts".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_typescript()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![":".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.ts": "", -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) -// .await -// .unwrap(); - -// let fake_server = fake_language_servers.next().await.unwrap(); - -// let text = "let a = b.fqn"; -// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); -// let completions = project.update(cx, |project, cx| { -// project.completions(&buffer, text.len(), cx) -// }); - -// fake_server -// .handle_request::(|_, _| async move { -// Ok(Some(lsp2::CompletionResponse::Array(vec![ -// lsp2::CompletionItem { -// label: "fullyQualifiedName?".into(), -// insert_text: Some("fullyQualifiedName".into()), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await; -// let completions = completions.await.unwrap(); -// let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); -// assert_eq!(completions.len(), 1); -// assert_eq!(completions[0].new_text, "fullyQualifiedName"); -// assert_eq!( -// completions[0].old_range.to_offset(&snapshot), -// text.len() - 3..text.len() -// ); - -// let text = "let a = \"atoms/cmp\""; -// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); -// let completions = project.update(cx, |project, cx| { -// project.completions(&buffer, text.len() - 1, cx) -// }); - -// fake_server -// .handle_request::(|_, _| async move { -// Ok(Some(lsp2::CompletionResponse::Array(vec![ -// lsp2::CompletionItem { -// label: "component".into(), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await; -// let completions = completions.await.unwrap(); -// let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); -// assert_eq!(completions.len(), 1); -// assert_eq!(completions[0].new_text, "component"); -// assert_eq!( -// completions[0].old_range.to_offset(&snapshot), -// text.len() - 4..text.len() - 1 -// ); -// } - -// #[gpui::test] -// async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "TypeScript".into(), -// path_suffixes: vec!["ts".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_typescript()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![":".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.ts": "", -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) -// .await -// .unwrap(); - -// let fake_server = fake_language_servers.next().await.unwrap(); - -// let text = "let a = b.fqn"; -// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); -// let completions = project.update(cx, |project, cx| { -// project.completions(&buffer, text.len(), cx) -// }); - -// fake_server -// .handle_request::(|_, _| async move { -// Ok(Some(lsp2::CompletionResponse::Array(vec![ -// lsp2::CompletionItem { -// label: "fullyQualifiedName?".into(), -// insert_text: Some("fully\rQualified\r\nName".into()), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await; -// let completions = completions.await.unwrap(); -// assert_eq!(completions.len(), 1); -// assert_eq!(completions[0].new_text, "fully\nQualified\nName"); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "TypeScript".into(), -// path_suffixes: vec!["ts".to_string()], -// ..Default::default() -// }, -// None, -// ); -// let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.ts": "a", -// }), -// ) -// .await; - -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) -// .await -// .unwrap(); - -// let fake_server = fake_language_servers.next().await.unwrap(); - -// // 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 { -// Ok(Some(vec![ -// lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction { -// title: "The code action".into(), -// 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 { -// title: "two".into(), -// ..Default::default() -// }), -// ])) -// }) -// .next() -// .await; - -// let action = actions.await.unwrap()[0].clone(); -// let apply = project.update(cx, |project, cx| { -// project.apply_code_action(buffer.clone(), action, true, cx) -// }); - -// // Resolving the code action does not populate its edits. In absence of -// // edits, we must execute the given command. -// 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::({ -// let fake = fake_server.clone(); -// move |params, _| { -// assert_eq!(params.command, "_the/command"); -// let fake = fake.clone(); -// async move { -// fake.server -// .request::( -// lsp2::ApplyWorkspaceEditParams { -// label: None, -// 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), -// ), -// new_text: "X".into(), -// }], -// )] -// .into_iter() -// .collect(), -// ), -// ..Default::default() -// }, -// }, -// ) -// .await -// .unwrap(); -// Ok(Some(json!(null))) -// } -// } -// }) -// .next() -// .await; - -// // Applying the code action returns a project transaction containing the edits -// // sent by the language server in its `workspaceEdit` request. -// let transaction = apply.await.unwrap(); -// assert!(transaction.0.contains_key(&buffer)); -// buffer.update(cx, |buffer, cx| { -// assert_eq!(buffer.text(), "Xa"); -// buffer.undo(cx); -// assert_eq!(buffer.text(), "a"); -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_save_file(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "file1": "the old contents", -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) -// .await -// .unwrap(); -// buffer.update(cx, |buffer, cx| { -// assert_eq!(buffer.text(), "the old contents"); -// buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); -// }); - -// project -// .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) -// .await -// .unwrap(); - -// let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); -// assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); -// } - -// #[gpui::test] -// async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "file1": "the old contents", -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await; -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) -// .await -// .unwrap(); -// buffer.update(cx, |buffer, cx| { -// buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); -// }); - -// project -// .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) -// .await -// .unwrap(); - -// let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); -// assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); -// } - -// #[gpui::test] -// async fn test_save_as(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree("/dir", json!({})).await; - -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - -// let languages = project.read_with(cx, |project, _| project.languages().clone()); -// languages.register( -// "/some/path", -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".into()], -// ..Default::default() -// }, -// tree_sitter_rust::language(), -// vec![], -// |_| Default::default(), -// ); - -// let buffer = project.update(cx, |project, cx| { -// project.create_buffer("", None, cx).unwrap() -// }); -// buffer.update(cx, |buffer, cx| { -// buffer.edit([(0..0, "abc")], None, cx); -// assert!(buffer.is_dirty()); -// assert!(!buffer.has_conflict()); -// assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); -// }); -// project -// .update(cx, |project, cx| { -// project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) -// }) -// .await -// .unwrap(); -// assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); - -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, cx| { -// assert_eq!( -// buffer.file().unwrap().full_path(cx), -// Path::new("dir/file1.rs") -// ); -// assert!(!buffer.is_dirty()); -// assert!(!buffer.has_conflict()); -// assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); -// }); - -// let opened_buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/dir/file1.rs", cx) -// }) -// .await -// .unwrap(); -// assert_eq!(opened_buffer, buffer); -// } - -// #[gpui::test(retries = 5)] -// async fn test_rescan_and_remote_updates( -// deterministic: Arc, -// cx: &mut gpui::TestAppContext, -// ) { -// init_test(cx); -// cx.foreground().allow_parking(); - -// let dir = temp_tree(json!({ -// "a": { -// "file1": "", -// "file2": "", -// "file3": "", -// }, -// "b": { -// "c": { -// "file4": "", -// "file5": "", -// } -// } -// })); - -// let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; -// let rpc = project.read_with(cx, |p, _| p.client.clone()); - -// let buffer_for_path = |path: &'static str, cx: &mut gpui2::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: &gpui2::TestAppContext| { -// project.read_with(cx, |project, cx| { -// let tree = project.worktrees(cx).next().unwrap(); -// tree.read(cx) -// .entry_for_path(path) -// .unwrap_or_else(|| panic!("no entry for path {}", path)) -// .id -// }) -// }; - -// let buffer2 = buffer_for_path("a/file2", cx).await; -// let buffer3 = buffer_for_path("a/file3", cx).await; -// let buffer4 = buffer_for_path("b/c/file4", cx).await; -// let buffer5 = buffer_for_path("b/c/file5", cx).await; - -// let file2_id = id_for_path("a/file2", cx); -// let file3_id = id_for_path("a/file3", cx); -// let file4_id = id_for_path("b/c/file4", cx); - -// // Create a remote copy of this worktree. -// let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()); - -// let metadata = tree.read_with(cx, |tree, _| tree.as_local().unwrap().metadata_proto()); - -// let updates = Arc::new(Mutex::new(Vec::new())); -// tree.update(cx, |tree, cx| { -// let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, { -// let updates = updates.clone(); -// move |update| { -// updates.lock().push(update); -// async { true } -// } -// }); -// }); - -// let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); -// deterministic.run_until_parked(); - -// cx.read(|cx| { -// assert!(!buffer2.read(cx).is_dirty()); -// assert!(!buffer3.read(cx).is_dirty()); -// assert!(!buffer4.read(cx).is_dirty()); -// assert!(!buffer5.read(cx).is_dirty()); -// }); - -// // Rename and delete files and directories. -// tree.flush_fs_events(cx).await; -// std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); -// std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); -// std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); -// std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); -// tree.flush_fs_events(cx).await; - -// let expected_paths = vec![ -// "a", -// "a/file1", -// "a/file2.new", -// "b", -// "d", -// "d/file3", -// "d/file4", -// ]; - -// cx.read(|app| { -// assert_eq!( -// tree.read(app) -// .paths() -// .map(|p| p.to_str().unwrap()) -// .collect::>(), -// expected_paths -// ); - -// assert_eq!(id_for_path("a/file2.new", cx), file2_id); -// assert_eq!(id_for_path("d/file3", cx), file3_id); -// assert_eq!(id_for_path("d/file4", cx), file4_id); - -// assert_eq!( -// buffer2.read(app).file().unwrap().path().as_ref(), -// Path::new("a/file2.new") -// ); -// assert_eq!( -// buffer3.read(app).file().unwrap().path().as_ref(), -// Path::new("d/file3") -// ); -// assert_eq!( -// buffer4.read(app).file().unwrap().path().as_ref(), -// Path::new("d/file4") -// ); -// assert_eq!( -// buffer5.read(app).file().unwrap().path().as_ref(), -// Path::new("b/c/file5") -// ); - -// assert!(!buffer2.read(app).file().unwrap().is_deleted()); -// assert!(!buffer3.read(app).file().unwrap().is_deleted()); -// assert!(!buffer4.read(app).file().unwrap().is_deleted()); -// assert!(buffer5.read(app).file().unwrap().is_deleted()); -// }); - -// // Update the remote worktree. Check that it becomes consistent with the -// // local worktree. -// deterministic.run_until_parked(); -// remote.update(cx, |remote, _| { -// for update in updates.lock().drain(..) { -// remote.as_remote_mut().unwrap().update_from_remote(update); -// } -// }); -// deterministic.run_until_parked(); -// remote.read_with(cx, |remote, _| { -// assert_eq!( -// remote -// .paths() -// .map(|p| p.to_str().unwrap()) -// .collect::>(), -// expected_paths -// ); -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_buffer_identity_across_renames( -// deterministic: Arc, -// cx: &mut gpui::TestAppContext, -// ) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a": { -// "file1": "", -// } -// }), -// ) -// .await; - -// let project = Project::test(fs, [Path::new("/dir")], cx).await; -// let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()); -// let tree_id = tree.read_with(cx, |tree, _| tree.id()); - -// let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| { -// project.read_with(cx, |project, cx| { -// let tree = project.worktrees(cx).next().unwrap(); -// tree.read(cx) -// .entry_for_path(path) -// .unwrap_or_else(|| panic!("no entry for path {}", path)) -// .id -// }) -// }; - -// let dir_id = id_for_path("a", cx); -// let file_id = id_for_path("a/file1", cx); -// let buffer = project -// .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx)) -// .await -// .unwrap(); -// buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty())); - -// project -// .update(cx, |project, cx| { -// project.rename_entry(dir_id, Path::new("b"), cx) -// }) -// .unwrap() -// .await -// .unwrap(); -// deterministic.run_until_parked(); -// assert_eq!(id_for_path("b", cx), dir_id); -// assert_eq!(id_for_path("b/file1", cx), file_id); -// buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty())); -// } - -// #[gpui2::test] -// async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.txt": "a-contents", -// "b.txt": "b-contents", -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - -// // Spawn multiple tasks to open paths, repeating some paths. -// let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| { -// ( -// p.open_local_buffer("/dir/a.txt", cx), -// p.open_local_buffer("/dir/b.txt", cx), -// p.open_local_buffer("/dir/a.txt", cx), -// ) -// }); - -// let buffer_a_1 = buffer_a_1.await.unwrap(); -// let buffer_a_2 = buffer_a_2.await.unwrap(); -// let buffer_b = buffer_b.await.unwrap(); -// assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents"); -// assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents"); - -// // There is only one buffer per path. -// let buffer_a_id = buffer_a_1.id(); -// assert_eq!(buffer_a_2.id(), buffer_a_id); - -// // Open the same path again while it is still open. -// drop(buffer_a_1); -// let buffer_a_3 = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx)) -// .await -// .unwrap(); - -// // There's still only one buffer per path. -// assert_eq!(buffer_a_3.id(), buffer_a_id); -// } - -// #[gpui2::test] -// async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "file1": "abc", -// "file2": "def", -// "file3": "ghi", -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - -// let buffer1 = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) -// .await -// .unwrap(); -// let events = Rc::new(RefCell::new(Vec::new())); - -// // initially, the buffer isn't dirty. -// buffer1.update(cx, |buffer, cx| { -// cx.subscribe(&buffer1, { -// let events = events.clone(); -// move |_, _, event, _| match event { -// BufferEvent::Operation(_) => {} -// _ => events.borrow_mut().push(event.clone()), -// } -// }) -// .detach(); - -// assert!(!buffer.is_dirty()); -// assert!(events.borrow().is_empty()); - -// buffer.edit([(1..2, "")], None, cx); -// }); - -// // after the first edit, the buffer is dirty, and emits a dirtied event. -// buffer1.update(cx, |buffer, cx| { -// assert!(buffer.text() == "ac"); -// assert!(buffer.is_dirty()); -// assert_eq!( -// *events.borrow(), -// &[language2::Event::Edited, language2::Event::DirtyChanged] -// ); -// events.borrow_mut().clear(); -// buffer.did_save( -// buffer.version(), -// buffer.as_rope().fingerprint(), -// buffer.file().unwrap().mtime(), -// cx, -// ); -// }); - -// // after saving, the buffer is not dirty, and emits a saved event. -// buffer1.update(cx, |buffer, cx| { -// assert!(!buffer.is_dirty()); -// assert_eq!(*events.borrow(), &[language2::Event::Saved]); -// events.borrow_mut().clear(); - -// buffer.edit([(1..1, "B")], None, cx); -// buffer.edit([(2..2, "D")], None, cx); -// }); - -// // after editing again, the buffer is dirty, and emits another dirty event. -// buffer1.update(cx, |buffer, cx| { -// assert!(buffer.text() == "aBDc"); -// assert!(buffer.is_dirty()); -// assert_eq!( -// *events.borrow(), -// &[ -// language2::Event::Edited, -// language2::Event::DirtyChanged, -// language2::Event::Edited, -// ], -// ); -// events.borrow_mut().clear(); - -// // After restoring the buffer to its previously-saved state, -// // the buffer is not considered dirty anymore. -// buffer.edit([(1..3, "")], None, cx); -// assert!(buffer.text() == "ac"); -// assert!(!buffer.is_dirty()); -// }); - -// assert_eq!( -// *events.borrow(), -// &[language2::Event::Edited, language2::Event::DirtyChanged] -// ); - -// // When a file is deleted, the buffer is considered dirty. -// let events = Rc::new(RefCell::new(Vec::new())); -// let buffer2 = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) -// .await -// .unwrap(); -// buffer2.update(cx, |_, cx| { -// cx.subscribe(&buffer2, { -// let events = events.clone(); -// move |_, _, event, _| events.borrow_mut().push(event.clone()) -// }) -// .detach(); -// }); - -// fs.remove_file("/dir/file2".as_ref(), Default::default()) -// .await -// .unwrap(); -// cx.foreground().run_until_parked(); -// buffer2.read_with(cx, |buffer, _| assert!(buffer.is_dirty())); -// assert_eq!( -// *events.borrow(), -// &[ -// language2::Event::DirtyChanged, -// language2::Event::FileHandleChanged -// ] -// ); - -// // When a file is already dirty when deleted, we don't emit a Dirtied event. -// let events = Rc::new(RefCell::new(Vec::new())); -// let buffer3 = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx)) -// .await -// .unwrap(); -// buffer3.update(cx, |_, cx| { -// cx.subscribe(&buffer3, { -// let events = events.clone(); -// move |_, _, event, _| events.borrow_mut().push(event.clone()) -// }) -// .detach(); -// }); - -// buffer3.update(cx, |buffer, cx| { -// buffer.edit([(0..0, "x")], None, cx); -// }); -// events.borrow_mut().clear(); -// fs.remove_file("/dir/file3".as_ref(), Default::default()) -// .await -// .unwrap(); -// cx.foreground().run_until_parked(); -// assert_eq!(*events.borrow(), &[language2::Event::FileHandleChanged]); -// cx.read(|cx| assert!(buffer3.read(cx).is_dirty())); -// } - -// #[gpui::test] -// async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let initial_contents = "aaa\nbbbbb\nc\n"; -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "the-file": initial_contents, -// }), -// ) -// .await; -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx)) -// .await -// .unwrap(); - -// let anchors = (0..3) -// .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1)))) -// .collect::>(); - -// // Change the file on disk, adding two new lines of text, and removing -// // one line. -// buffer.read_with(cx, |buffer, _| { -// assert!(!buffer.is_dirty()); -// assert!(!buffer.has_conflict()); -// }); -// let new_contents = "AAAA\naaa\nBB\nbbbbb\n"; -// fs.save( -// "/dir/the-file".as_ref(), -// &new_contents.into(), -// LineEnding::Unix, -// ) -// .await -// .unwrap(); - -// // Because the buffer was not modified, it is reloaded from disk. Its -// // contents are edited according to the diff between the old and new -// // file contents. -// cx.foreground().run_until_parked(); -// buffer.update(cx, |buffer, _| { -// assert_eq!(buffer.text(), new_contents); -// assert!(!buffer.is_dirty()); -// assert!(!buffer.has_conflict()); - -// let anchor_positions = anchors -// .iter() -// .map(|anchor| anchor.to_point(&*buffer)) -// .collect::>(); -// assert_eq!( -// anchor_positions, -// [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)] -// ); -// }); - -// // Modify the buffer -// buffer.update(cx, |buffer, cx| { -// buffer.edit([(0..0, " ")], None, cx); -// assert!(buffer.is_dirty()); -// assert!(!buffer.has_conflict()); -// }); - -// // Change the file on disk again, adding blank lines to the beginning. -// fs.save( -// "/dir/the-file".as_ref(), -// &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(), -// LineEnding::Unix, -// ) -// .await -// .unwrap(); - -// // Because the buffer is modified, it doesn't reload from disk, but is -// // marked as having a conflict. -// cx.foreground().run_until_parked(); -// buffer.read_with(cx, |buffer, _| { -// assert!(buffer.has_conflict()); -// }); -// } - -// #[gpui::test] -// async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "file1": "a\nb\nc\n", -// "file2": "one\r\ntwo\r\nthree\r\n", -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; -// let buffer1 = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) -// .await -// .unwrap(); -// let buffer2 = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) -// .await -// .unwrap(); - -// buffer1.read_with(cx, |buffer, _| { -// assert_eq!(buffer.text(), "a\nb\nc\n"); -// assert_eq!(buffer.line_ending(), LineEnding::Unix); -// }); -// buffer2.read_with(cx, |buffer, _| { -// assert_eq!(buffer.text(), "one\ntwo\nthree\n"); -// assert_eq!(buffer.line_ending(), LineEnding::Windows); -// }); - -// // Change a file's line endings on disk from unix to windows. The buffer's -// // state updates correctly. -// fs.save( -// "/dir/file1".as_ref(), -// &"aaa\nb\nc\n".into(), -// LineEnding::Windows, -// ) -// .await -// .unwrap(); -// cx.foreground().run_until_parked(); -// buffer1.read_with(cx, |buffer, _| { -// assert_eq!(buffer.text(), "aaa\nb\nc\n"); -// assert_eq!(buffer.line_ending(), LineEnding::Windows); -// }); - -// // Save a file with windows line endings. The file is written correctly. -// buffer2.update(cx, |buffer, cx| { -// buffer.set_text("one\ntwo\nthree\nfour\n", cx); -// }); -// project -// .update(cx, |project, cx| project.save_buffer(buffer2, cx)) -// .await -// .unwrap(); -// assert_eq!( -// fs.load("/dir/file2".as_ref()).await.unwrap(), -// "one\r\ntwo\r\nthree\r\nfour\r\n", -// ); -// } - -// #[gpui::test] -// async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/the-dir", -// json!({ -// "a.rs": " -// fn foo(mut v: Vec) { -// for x in &v { -// v.push(1); -// } -// } -// " -// .unindent(), -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx)) -// .await -// .unwrap(); - -// let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap(); -// 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)), -// severity: Some(DiagnosticSeverity::WARNING), -// message: "error 1".to_string(), -// 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), -// ), -// }, -// 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)), -// severity: Some(DiagnosticSeverity::HINT), -// message: "error 1 hint 1".to_string(), -// 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), -// ), -// }, -// message: "original diagnostic".to_string(), -// }]), -// ..Default::default() -// }, -// lsp2::Diagnostic { -// range: lsp2::Range::new(lsp2::Position::new(2, 8), lsp2::Position::new(2, 17)), -// severity: Some(DiagnosticSeverity::ERROR), -// message: "error 2".to_string(), -// related_information: Some(vec![ -// lsp::DiagnosticRelatedInformation { -// location: lsp::Location { -// uri: buffer_uri.clone(), -// range: lsp2::Range::new( -// lsp2::Position::new(1, 13), -// lsp2::Position::new(1, 15), -// ), -// }, -// message: "error 2 hint 1".to_string(), -// }, -// lsp::DiagnosticRelatedInformation { -// location: lsp::Location { -// uri: buffer_uri.clone(), -// range: lsp2::Range::new( -// lsp2::Position::new(1, 13), -// lsp2::Position::new(1, 15), -// ), -// }, -// message: "error 2 hint 2".to_string(), -// }, -// ]), -// ..Default::default() -// }, -// lsp2::Diagnostic { -// range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)), -// severity: Some(DiagnosticSeverity::HINT), -// message: "error 2 hint 1".to_string(), -// 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), -// ), -// }, -// message: "original diagnostic".to_string(), -// }]), -// ..Default::default() -// }, -// lsp2::Diagnostic { -// range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)), -// severity: Some(DiagnosticSeverity::HINT), -// message: "error 2 hint 2".to_string(), -// 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), -// ), -// }, -// message: "original diagnostic".to_string(), -// }]), -// ..Default::default() -// }, -// ], -// version: None, -// }; - -// project -// .update(cx, |p, cx| { -// p.update_diagnostics(LanguageServerId(0), message, &[], cx) -// }) -// .unwrap(); -// let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - -// assert_eq!( -// buffer -// .diagnostics_in_range::<_, Point>(0..buffer.len(), false) -// .collect::>(), -// &[ -// DiagnosticEntry { -// range: Point::new(1, 8)..Point::new(1, 9), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::WARNING, -// message: "error 1".to_string(), -// group_id: 1, -// is_primary: true, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(1, 8)..Point::new(1, 9), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::HINT, -// message: "error 1 hint 1".to_string(), -// group_id: 1, -// is_primary: false, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(1, 13)..Point::new(1, 15), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::HINT, -// message: "error 2 hint 1".to_string(), -// group_id: 0, -// is_primary: false, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(1, 13)..Point::new(1, 15), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::HINT, -// message: "error 2 hint 2".to_string(), -// group_id: 0, -// is_primary: false, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(2, 8)..Point::new(2, 17), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::ERROR, -// message: "error 2".to_string(), -// group_id: 0, -// is_primary: true, -// ..Default::default() -// } -// } -// ] -// ); - -// assert_eq!( -// buffer.diagnostic_group::(0).collect::>(), -// &[ -// DiagnosticEntry { -// range: Point::new(1, 13)..Point::new(1, 15), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::HINT, -// message: "error 2 hint 1".to_string(), -// group_id: 0, -// is_primary: false, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(1, 13)..Point::new(1, 15), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::HINT, -// message: "error 2 hint 2".to_string(), -// group_id: 0, -// is_primary: false, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(2, 8)..Point::new(2, 17), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::ERROR, -// message: "error 2".to_string(), -// group_id: 0, -// is_primary: true, -// ..Default::default() -// } -// } -// ] -// ); - -// assert_eq!( -// buffer.diagnostic_group::(1).collect::>(), -// &[ -// DiagnosticEntry { -// range: Point::new(1, 8)..Point::new(1, 9), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::WARNING, -// message: "error 1".to_string(), -// group_id: 1, -// is_primary: true, -// ..Default::default() -// } -// }, -// DiagnosticEntry { -// range: Point::new(1, 8)..Point::new(1, 9), -// diagnostic: Diagnostic { -// severity: DiagnosticSeverity::HINT, -// message: "error 1 hint 1".to_string(), -// group_id: 1, -// is_primary: false, -// ..Default::default() -// } -// }, -// ] -// ); -// } - -// #[gpui::test] -// async fn test_rename(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp2::ServerCapabilities { -// rename_provider: Some(lsp2::OneOf::Right(lsp2::RenameOptions { -// prepare_provider: Some(true), -// work_done_progress_options: Default::default(), -// })), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "one.rs": "const ONE: usize = 1;", -// "two.rs": "const TWO: usize = one::ONE + one::ONE;" -// }), -// ) -// .await; - -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/dir/one.rs", cx) -// }) -// .await -// .unwrap(); - -// let fake_server = fake_servers.next().await.unwrap(); - -// let response = project.update(cx, |project, cx| { -// project.prepare_rename(buffer.clone(), 7, cx) -// }); -// fake_server -// .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), -// )))) -// }) -// .next() -// .await -// .unwrap(); -// let range = response.await.unwrap().unwrap(); -// let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer)); -// assert_eq!(range, 6..9); - -// let response = project.update(cx, |project, cx| { -// project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx) -// }); -// fake_server -// .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) -// ); -// assert_eq!(params.new_name, "THREE"); -// 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), -// ), -// "THREE".to_string(), -// )], -// ), -// ( -// lsp2::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), -// ), -// "THREE".to_string(), -// ), -// lsp2::TextEdit::new( -// lsp2::Range::new( -// lsp2::Position::new(0, 35), -// lsp2::Position::new(0, 38), -// ), -// "THREE".to_string(), -// ), -// ], -// ), -// ] -// .into_iter() -// .collect(), -// ), -// ..Default::default() -// })) -// }) -// .next() -// .await -// .unwrap(); -// let mut transaction = response.await.unwrap().0; -// assert_eq!(transaction.len(), 2); -// assert_eq!( -// transaction -// .remove_entry(&buffer) -// .unwrap() -// .0 -// .read_with(cx, |buffer, _| buffer.text()), -// "const THREE: usize = 1;" -// ); -// assert_eq!( -// transaction -// .into_keys() -// .next() -// .unwrap() -// .read_with(cx, |buffer, _| buffer.text()), -// "const TWO: usize = one::THREE + one::THREE;" -// ); -// } - -// #[gpui::test] -// async fn test_search(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "one.rs": "const ONE: usize = 1;", -// "two.rs": "const TWO: usize = one::ONE + one::ONE;", -// "three.rs": "const THREE: usize = one::ONE + two::TWO;", -// "four.rs": "const FOUR: usize = one::ONE + three::THREE;", -// }), -// ) -// .await; -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; -// assert_eq!( -// search( -// &project, -// SearchQuery::text("TWO", false, true, Vec::new(), Vec::new()).unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("two.rs".to_string(), vec![6..9]), -// ("three.rs".to_string(), vec![37..40]) -// ]) -// ); - -// let buffer_4 = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/dir/four.rs", cx) -// }) -// .await -// .unwrap(); -// buffer_4.update(cx, |buffer, cx| { -// let text = "two::TWO"; -// buffer.edit([(20..28, text), (31..43, text)], None, cx); -// }); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text("TWO", false, true, Vec::new(), Vec::new()).unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("two.rs".to_string(), vec![6..9]), -// ("three.rs".to_string(), vec![37..40]), -// ("four.rs".to_string(), vec![25..28, 36..39]) -// ]) -// ); -// } - -// #[gpui::test] -// async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let search_query = "file"; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "one.rs": r#"// Rust file one"#, -// "one.ts": r#"// TypeScript file one"#, -// "two.rs": r#"// Rust file two"#, -// "two.ts": r#"// TypeScript file two"#, -// }), -// ) -// .await; -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - -// assert!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![PathMatcher::new("*.odd").unwrap()], -// Vec::new() -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap() -// .is_empty(), -// "If no inclusions match, no files should be returned" -// ); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![PathMatcher::new("*.rs").unwrap()], -// Vec::new() -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.rs".to_string(), vec![8..12]), -// ("two.rs".to_string(), vec![8..12]), -// ]), -// "Rust only search should give only Rust files" -// ); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![ -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap(), -// ], -// Vec::new() -// ).unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.ts".to_string(), vec![14..18]), -// ("two.ts".to_string(), vec![14..18]), -// ]), -// "TypeScript only search should give only TypeScript files, even if other inclusions don't match anything" -// ); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![ -// PathMatcher::new("*.rs").unwrap(), -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap(), -// ], -// Vec::new() -// ).unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.rs".to_string(), vec![8..12]), -// ("one.ts".to_string(), vec![14..18]), -// ("two.rs".to_string(), vec![8..12]), -// ("two.ts".to_string(), vec![14..18]), -// ]), -// "Rust and typescript search should give both Rust and TypeScript files, even if other inclusions don't match anything" -// ); -// } - -// #[gpui::test] -// async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let search_query = "file"; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "one.rs": r#"// Rust file one"#, -// "one.ts": r#"// TypeScript file one"#, -// "two.rs": r#"// Rust file two"#, -// "two.ts": r#"// TypeScript file two"#, -// }), -// ) -// .await; -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// Vec::new(), -// vec![PathMatcher::new("*.odd").unwrap()], -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.rs".to_string(), vec![8..12]), -// ("one.ts".to_string(), vec![14..18]), -// ("two.rs".to_string(), vec![8..12]), -// ("two.ts".to_string(), vec![14..18]), -// ]), -// "If no exclusions match, all files should be returned" -// ); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// Vec::new(), -// vec![PathMatcher::new("*.rs").unwrap()], -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.ts".to_string(), vec![14..18]), -// ("two.ts".to_string(), vec![14..18]), -// ]), -// "Rust exclusion search should give only TypeScript files" -// ); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// Vec::new(), -// vec![ -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap(), -// ], -// ).unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.rs".to_string(), vec![8..12]), -// ("two.rs".to_string(), vec![8..12]), -// ]), -// "TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything" -// ); - -// assert!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// Vec::new(), -// vec![ -// PathMatcher::new("*.rs").unwrap(), -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap(), -// ], -// ).unwrap(), -// cx -// ) -// .await -// .unwrap().is_empty(), -// "Rust and typescript exclusion should give no files, even if other exclusions don't match anything" -// ); -// } - -// #[gpui::test] -// async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) { -// init_test(cx); - -// let search_query = "file"; - -// let fs = FakeFs::new(cx.background()); -// fs.insert_tree( -// "/dir", -// json!({ -// "one.rs": r#"// Rust file one"#, -// "one.ts": r#"// TypeScript file one"#, -// "two.rs": r#"// Rust file two"#, -// "two.ts": r#"// TypeScript file two"#, -// }), -// ) -// .await; -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - -// assert!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![PathMatcher::new("*.odd").unwrap()], -// vec![PathMatcher::new("*.odd").unwrap()], -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap() -// .is_empty(), -// "If both no exclusions and inclusions match, exclusions should win and return nothing" -// ); - -// assert!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![PathMatcher::new("*.ts").unwrap()], -// vec![PathMatcher::new("*.ts").unwrap()], -// ).unwrap(), -// cx -// ) -// .await -// .unwrap() -// .is_empty(), -// "If both TypeScript exclusions and inclusions match, exclusions should win and return nothing files." -// ); - -// assert!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![ -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap() -// ], -// vec![ -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap() -// ], -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap() -// .is_empty(), -// "Non-matching inclusions and exclusions should not change that." -// ); - -// assert_eq!( -// search( -// &project, -// SearchQuery::text( -// search_query, -// false, -// true, -// vec![ -// PathMatcher::new("*.ts").unwrap(), -// PathMatcher::new("*.odd").unwrap() -// ], -// vec![ -// PathMatcher::new("*.rs").unwrap(), -// PathMatcher::new("*.odd").unwrap() -// ], -// ) -// .unwrap(), -// cx -// ) -// .await -// .unwrap(), -// HashMap::from_iter([ -// ("one.ts".to_string(), vec![14..18]), -// ("two.ts".to_string(), vec![14..18]), -// ]), -// "Non-intersecting TypeScript inclusions and Rust exclusions should return TypeScript files" -// ); -// } - -// #[test] -// fn test_glob_literal_prefix() { -// assert_eq!(glob_literal_prefix("**/*.js"), ""); -// assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules"); -// assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo"); -// assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js"); -// } - -// async fn search( -// project: &ModelHandle, -// query: SearchQuery, -// cx: &mut gpui::TestAppContext, -// ) -> Result>>> { -// let mut search_rx = project.update(cx, |project, cx| project.search(query, cx)); -// let mut result = HashMap::default(); -// while let Some((buffer, range)) = search_rx.next().await { -// result.entry(buffer).or_insert(range); -// } -// Ok(result -// .into_iter() -// .map(|(buffer, ranges)| { -// buffer.read_with(cx, |buffer, _| { -// let path = buffer.file().unwrap().path().to_string_lossy().to_string(); -// let ranges = ranges -// .into_iter() -// .map(|range| range.to_offset(buffer)) -// .collect::>(); -// (path, ranges) -// }) -// }) -// .collect()) -// } - -// fn init_test(cx: &mut gpui::TestAppContext) { -// cx.foreground().forbid_parking(); - -// cx.update(|cx| { -// cx.set_global(SettingsStore::test(cx)); -// language2::init(cx); -// Project::init_settings(cx); -// }); -// } +use crate::{search::PathMatcher, worktree::WorktreeModelHandle, Event, *}; +use fs2::{FakeFs, RealFs}; +use futures::{future, StreamExt}; +use gpui2::AppContext; +use language2::{ + language_settings::{AllLanguageSettings, LanguageSettingsContent}, + tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, + LineEnding, OffsetRangeExt, Point, ToPoint, +}; +use lsp2::Url; +use parking_lot::Mutex; +use pretty_assertions::assert_eq; +use serde_json::json; +use std::{os::unix, task::Poll}; +use unindent::Unindent as _; +use util::{assert_set_eq, test::temp_tree}; + +#[gpui2::test] +async fn test_symlinks(cx: &mut gpui2::TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + + let dir = temp_tree(json!({ + "root": { + "apple": "", + "banana": { + "carrot": { + "date": "", + "endive": "", + } + }, + "fennel": { + "grape": "", + } + } + })); + + let root_link_path = dir.path().join("root_link"); + unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); + unix::fs::symlink( + &dir.path().join("root/fennel"), + &dir.path().join("root/finnochio"), + ) + .unwrap(); + + let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; + project.update(cx, |project, cx| { + let tree = project.worktrees().next().unwrap().read(cx); + assert_eq!(tree.file_count(), 5); + assert_eq!( + tree.inode_for_path("fennel/grape"), + tree.inode_for_path("finnochio/grape") + ); + }); +} + +#[gpui2::test] +async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/the-root", + json!({ + ".zed": { + "settings.json": r#"{ "tab_size": 8 }"# + }, + "a": { + "a.rs": "fn a() {\n A\n}" + }, + "b": { + ".zed": { + "settings.json": r#"{ "tab_size": 2 }"# + }, + "b.rs": "fn b() {\n B\n}" + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; + let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap()); + + cx.executor().run_until_parked(); + cx.update(|cx| { + let tree = worktree.read(cx); + + let settings_a = language_settings( + None, + Some( + &(File::for_entry( + tree.entry_for_path("a/a.rs").unwrap().clone(), + worktree.clone(), + ) as _), + ), + cx, + ); + let settings_b = language_settings( + None, + Some( + &(File::for_entry( + tree.entry_for_path("b/b.rs").unwrap().clone(), + worktree.clone(), + ) as _), + ), + cx, + ); + + assert_eq!(settings_a.tab_size.get(), 8); + assert_eq!(settings_b.tab_size.get(), 2); + }); +} + +#[gpui2::test] +async fn test_managing_language_servers(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut rust_language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut json_language = Language::new( + LanguageConfig { + name: "JSON".into(), + path_suffixes: vec!["json".to_string()], + ..Default::default() + }, + None, + ); + 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 { + trigger_characters: Some(vec![".".to_string(), "::".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + 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 { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/the-root", + json!({ + "test.rs": "const A: i32 = 1;", + "test2.rs": "", + "Cargo.toml": "a = 1", + "package.json": "{\"a\": 1}", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; + + // Open a buffer without an associated language server. + let toml_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/Cargo.toml", cx) + }) + .await + .unwrap(); + + // Open a buffer with an associated language server before the language for it has been loaded. + let rust_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/test.rs", cx) + }) + .await + .unwrap(); + rust_buffer.update(cx, |buffer, _| { + assert_eq!(buffer.language().map(|l| l.name()), None); + }); + + // Now we add the languages to the project, and ensure they get assigned to all + // the relevant open buffers. + project.update(cx, |project, _| { + project.languages.add(Arc::new(json_language)); + project.languages.add(Arc::new(rust_language)); + }); + cx.executor().run_until_parked(); + rust_buffer.update(cx, |buffer, _| { + assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into())); + }); + + // A server is started up, and it is notified about Rust files. + let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentItem { + uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), + version: 0, + text: "const A: i32 = 1;".to_string(), + language_id: Default::default() + } + ); + + // The buffer is configured based on the language server's capabilities. + rust_buffer.update(cx, |buffer, _| { + assert_eq!( + buffer.completion_triggers(), + &[".".to_string(), "::".to_string()] + ); + }); + toml_buffer.update(cx, |buffer, _| { + assert!(buffer.completion_triggers().is_empty()); + }); + + // Edit a buffer. The changes are reported to the language server. + rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx)); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::VersionedTextDocumentIdentifier::new( + lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), + 1 + ) + ); + + // Open a third buffer with a different associated language server. + let json_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/package.json", cx) + }) + .await + .unwrap(); + + // A json language server is started up and is only notified about the json buffer. + let mut fake_json_server = fake_json_servers.next().await.unwrap(); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentItem { + uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(), + version: 0, + text: "{\"a\": 1}".to_string(), + language_id: Default::default() + } + ); + + // This buffer is configured based on the second language server's + // capabilities. + json_buffer.update(cx, |buffer, _| { + assert_eq!(buffer.completion_triggers(), &[":".to_string()]); + }); + + // When opening another buffer whose language server is already running, + // it is also configured based on the existing language server's capabilities. + let rust_buffer2 = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/test2.rs", cx) + }) + .await + .unwrap(); + rust_buffer2.update(cx, |buffer, _| { + assert_eq!( + buffer.completion_triggers(), + &[".".to_string(), "::".to_string()] + ); + }); + + // Changes are reported only to servers matching the buffer's language. + toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx)); + rust_buffer2.update(cx, |buffer, cx| { + buffer.edit([(0..0, "let x = 1;")], None, cx) + }); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::VersionedTextDocumentIdentifier::new( + lsp2::Url::from_file_path("/the-root/test2.rs").unwrap(), + 1 + ) + ); + + // Save notifications are reported to all servers. + project + .update(cx, |project, cx| project.save_buffer(toml_buffer, cx)) + .await + .unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentIdentifier::new( + lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap() + ) + ); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentIdentifier::new( + lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap() + ) + ); + + // Renames are reported only to servers matching the buffer's language. + fs.rename( + Path::new("/the-root/test2.rs"), + Path::new("/the-root/test3.rs"), + Default::default(), + ) + .await + .unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test2.rs").unwrap()), + ); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentItem { + uri: lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(), + version: 0, + text: rust_buffer2.update(cx, |buffer, _| buffer.text()), + language_id: Default::default() + }, + ); + + rust_buffer2.update(cx, |buffer, cx| { + buffer.update_diagnostics( + LanguageServerId(0), + DiagnosticSet::from_sorted_entries( + vec![DiagnosticEntry { + diagnostic: Default::default(), + range: Anchor::MIN..Anchor::MAX, + }], + &buffer.snapshot(), + ), + cx, + ); + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..buffer.len(), false) + .count(), + 1 + ); + }); + + // When the rename changes the extension of the file, the buffer gets closed on the old + // language server and gets opened on the new one. + fs.rename( + Path::new("/the-root/test3.rs"), + Path::new("/the-root/test3.json"), + Default::default(), + ) + .await + .unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(),), + ); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentItem { + uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), + version: 0, + text: rust_buffer2.update(cx, |buffer, _| buffer.text()), + language_id: Default::default() + }, + ); + + // We clear the diagnostics, since the language has changed. + rust_buffer2.update(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..buffer.len(), false) + .count(), + 0 + ); + }); + + // The renamed file's version resets after changing language server. + rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx)); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp2::VersionedTextDocumentIdentifier::new( + lsp2::Url::from_file_path("/the-root/test3.json").unwrap(), + 1 + ) + ); + + // Restart language servers + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers( + vec![rust_buffer.clone(), json_buffer.clone()], + cx, + ); + }); + + let mut rust_shutdown_requests = fake_rust_server + .handle_request::(|_, _| future::ready(Ok(()))); + let mut json_shutdown_requests = fake_json_server + .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(); + let mut fake_json_server = fake_json_servers.next().await.unwrap(); + + // Ensure rust document is reopened in new rust language server + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp2::TextDocumentItem { + uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(), + version: 0, + text: rust_buffer.update(cx, |buffer, _| buffer.text()), + language_id: Default::default() + } + ); + + // Ensure json documents are reopened in new json language server + assert_set_eq!( + [ + fake_json_server + .receive_notification::() + .await + .text_document, + fake_json_server + .receive_notification::() + .await + .text_document, + ], + [ + lsp2::TextDocumentItem { + uri: lsp2::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(), + version: 0, + text: rust_buffer2.update(cx, |buffer, _| buffer.text()), + language_id: Default::default() + } + ] + ); + + // 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(), + ), + }; + assert_eq!( + fake_json_server + .receive_notification::() + .await, + close_message, + ); +} + +#[gpui2::test] +async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-language-server", + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/the-root", + json!({ + ".gitignore": "target\n", + "src": { + "a.rs": "", + "b.rs": "", + }, + "target": { + "x": { + "out": { + "x.rs": "" + } + }, + "y": { + "out": { + "y.rs": "", + } + }, + "z": { + "out": { + "z.rs": "" + } + } + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; + project.update(cx, |project, _| { + project.languages.add(Arc::new(language)); + }); + cx.executor().run_until_parked(); + + // Start the language server by opening a buffer with a compatible file extension. + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/src/a.rs", cx) + }) + .await + .unwrap(); + + // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them. + project.update(cx, |project, cx| { + let worktree = project.worktrees().next().unwrap(); + assert_eq!( + worktree + .read(cx) + .snapshot() + .entries(true) + .map(|entry| (entry.path.as_ref(), entry.is_ignored)) + .collect::>(), + &[ + (Path::new(""), false), + (Path::new(".gitignore"), false), + (Path::new("src"), false), + (Path::new("src/a.rs"), false), + (Path::new("src/b.rs"), false), + (Path::new("target"), true), + ] + ); + }); + + let prev_read_dir_count = fs.read_dir_call_count(); + + // Keep track of the FS events reported to the language server. + 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 { + id: Default::default(), + method: "workspace/didChangeWatchedFiles".to_string(), + register_options: serde_json::to_value( + lsp2::DidChangeWatchedFilesRegistrationOptions { + watchers: vec![ + lsp2::FileSystemWatcher { + glob_pattern: lsp2::GlobPattern::String( + "/the-root/Cargo.toml".to_string(), + ), + kind: None, + }, + lsp2::FileSystemWatcher { + glob_pattern: lsp2::GlobPattern::String( + "/the-root/src/*.{rs,c}".to_string(), + ), + kind: None, + }, + lsp2::FileSystemWatcher { + glob_pattern: lsp2::GlobPattern::String( + "/the-root/target/y/**/*.rs".to_string(), + ), + kind: None, + }, + ], + }, + ) + .ok(), + }], + }) + .await + .unwrap(); + fake_server.handle_notification::({ + let file_changes = file_changes.clone(); + move |params, _| { + let mut file_changes = file_changes.lock(); + file_changes.extend(params.changes); + file_changes.sort_by(|a, b| a.uri.cmp(&b.uri)); + } + }); + + cx.executor().run_until_parked(); + assert_eq!(mem::take(&mut *file_changes.lock()), &[]); + assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4); + + // Now the language server has asked us to watch an ignored directory path, + // so we recursively load it. + project.update(cx, |project, cx| { + let worktree = project.worktrees().next().unwrap(); + assert_eq!( + worktree + .read(cx) + .snapshot() + .entries(true) + .map(|entry| (entry.path.as_ref(), entry.is_ignored)) + .collect::>(), + &[ + (Path::new(""), false), + (Path::new(".gitignore"), false), + (Path::new("src"), false), + (Path::new("src/a.rs"), false), + (Path::new("src/b.rs"), false), + (Path::new("target"), true), + (Path::new("target/x"), true), + (Path::new("target/y"), true), + (Path::new("target/y/out"), true), + (Path::new("target/y/out/y.rs"), true), + (Path::new("target/z"), true), + ] + ); + }); + + // Perform some file system mutations, two of which match the watched patterns, + // and one of which does not. + fs.create_file("/the-root/src/c.rs".as_ref(), Default::default()) + .await + .unwrap(); + fs.create_file("/the-root/src/d.txt".as_ref(), Default::default()) + .await + .unwrap(); + fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default()) + .await + .unwrap(); + fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default()) + .await + .unwrap(); + fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default()) + .await + .unwrap(); + + // The language server receives events for the FS mutations that match its watch patterns. + cx.executor().run_until_parked(); + assert_eq!( + &*file_changes.lock(), + &[ + lsp2::FileEvent { + uri: lsp2::Url::from_file_path("/the-root/src/b.rs").unwrap(), + typ: lsp2::FileChangeType::DELETED, + }, + lsp2::FileEvent { + uri: lsp2::Url::from_file_path("/the-root/src/c.rs").unwrap(), + typ: lsp2::FileChangeType::CREATED, + }, + lsp2::FileEvent { + uri: lsp2::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(), + typ: lsp2::FileChangeType::CREATED, + }, + ] + ); +} + +#[gpui2::test] +async fn test_single_file_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "let a = 1;", + "b.rs": "let b = 2;" + }), + ) + .await; + + let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await; + + let buffer_a = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + let buffer_b = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) + .await + .unwrap(); + + project.update(cx, |project, cx| { + project + .update_diagnostics( + LanguageServerId(0), + lsp2::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), + message: "error 1".to_string(), + ..Default::default() + }], + }, + &[], + cx, + ) + .unwrap(); + project + .update_diagnostics( + LanguageServerId(0), + lsp2::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), + message: "error 2".to_string(), + ..Default::default() + }], + }, + &[], + cx, + ) + .unwrap(); + }); + + buffer_a.update(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let ", None), + ("a", Some(DiagnosticSeverity::ERROR)), + (" = 1;", None), + ] + ); + }); + buffer_b.update(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let ", None), + ("b", Some(DiagnosticSeverity::WARNING)), + (" = 2;", None), + ] + ); + }); +} + +#[gpui2::test] +async fn test_hidden_worktrees_diagnostics(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "dir": { + "a.rs": "let a = 1;", + }, + "other.rs": "let b = c;" + }), + ) + .await; + + let project = Project::test(fs, ["/root/dir".as_ref()], cx).await; + + let (worktree, _) = project + .update(cx, |project, cx| { + project.find_or_create_local_worktree("/root/other.rs", false, cx) + }) + .await + .unwrap(); + let worktree_id = worktree.update(cx, |tree, _| tree.id()); + + project.update(cx, |project, cx| { + project + .update_diagnostics( + LanguageServerId(0), + lsp2::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), + message: "unknown variable 'c'".to_string(), + ..Default::default() + }], + }, + &[], + cx, + ) + .unwrap(); + }); + + let buffer = project + .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx)) + .await + .unwrap(); + buffer.update(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let b = ", None), + ("c", Some(DiagnosticSeverity::ERROR)), + (";", None), + ] + ); + }); + + project.update(cx, |project, cx| { + assert_eq!(project.diagnostic_summaries(cx).next(), None); + assert_eq!(project.diagnostic_summary(cx).error_count, 0); + }); +} + +#[gpui2::test] +async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let progress_token = "the-progress-token"; + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_progress_token: Some(progress_token.into()), + disk_based_diagnostics_sources: vec!["disk".into()], + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "fn a() { A }", + "b.rs": "const y: i32 = 1", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id()); + + // Cause worktree to start the fake language server + let _buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) + .await + .unwrap(); + + let mut events = cx.subscribe(&project); + + let fake_server = fake_servers.next().await.unwrap(); + assert_eq!( + events.next().await.unwrap(), + Event::LanguageServerAdded(LanguageServerId(0)), + ); + + fake_server + .start_progress(format!("{}/0", progress_token)) + .await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsStarted { + language_server_id: LanguageServerId(0), + } + ); + + fake_server.notify::(lsp2::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), + message: "undefined variable 'A'".to_string(), + ..Default::default() + }], + }); + assert_eq!( + events.next().await.unwrap(), + Event::DiagnosticsUpdated { + language_server_id: LanguageServerId(0), + path: (worktree_id, Path::new("a.rs")).into() + } + ); + + fake_server.end_progress(format!("{}/0", progress_token)); + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsFinished { + language_server_id: LanguageServerId(0) + } + ); + + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + buffer.update(cx, |buffer, _| { + let snapshot = buffer.snapshot(); + let diagnostics = snapshot + .diagnostics_in_range::<_, Point>(0..buffer.len(), false) + .collect::>(); + assert_eq!( + diagnostics, + &[DiagnosticEntry { + range: Point::new(0, 9)..Point::new(0, 10), + diagnostic: Diagnostic { + severity: lsp2::DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + }] + ) + }); + + // Ensure publishing empty diagnostics twice only results in one update event. + fake_server.notify::(lsp2::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: Default::default(), + }); + assert_eq!( + events.next().await.unwrap(), + Event::DiagnosticsUpdated { + language_server_id: LanguageServerId(0), + path: (worktree_id, Path::new("a.rs")).into() + } + ); + + fake_server.notify::(lsp2::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: Default::default(), + }); + cx.executor().run_until_parked(); + assert_eq!(futures::poll!(events.next()), Poll::Pending); +} + +#[gpui2::test] +async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let progress_token = "the-progress-token"; + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + disk_based_diagnostics_progress_token: Some(progress_token.into()), + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": "" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Simulate diagnostics starting to update. + let fake_server = fake_servers.next().await.unwrap(); + fake_server.start_progress(progress_token).await; + + // Restart the server before the diagnostics finish updating. + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer], cx); + }); + let mut events = cx.subscribe(&project); + + // Simulate the newly started server sending more diagnostics. + let fake_server = fake_servers.next().await.unwrap(); + assert_eq!( + events.next().await.unwrap(), + Event::LanguageServerAdded(LanguageServerId(1)) + ); + fake_server.start_progress(progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsStarted { + language_server_id: LanguageServerId(1) + } + ); + project.update(cx, |project, _| { + assert_eq!( + project + .language_servers_running_disk_based_diagnostics() + .collect::>(), + [LanguageServerId(1)] + ); + }); + + // All diagnostics are considered done, despite the old server's diagnostic + // task never completing. + fake_server.end_progress(progress_token); + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsFinished { + language_server_id: LanguageServerId(1) + } + ); + project.update(cx, |project, _| { + assert_eq!( + project + .language_servers_running_disk_based_diagnostics() + .collect::>(), + [LanguageServerId(0); 0] + ); + }); +} + +#[gpui2::test] +async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": "x" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Publish diagnostics + let fake_server = fake_servers.next().await.unwrap(); + fake_server.notify::(lsp2::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), + message: "the message".to_string(), + ..Default::default() + }], + }); + + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..1, false) + .map(|entry| entry.diagnostic.message.clone()) + .collect::>(), + ["the message".to_string()] + ); + }); + project.update(cx, |project, cx| { + assert_eq!( + project.diagnostic_summary(cx), + DiagnosticSummary { + error_count: 1, + warning_count: 0, + } + ); + }); + + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer.clone()], cx); + }); + + // The diagnostics are cleared. + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..1, false) + .map(|entry| entry.diagnostic.message.clone()) + .collect::>(), + Vec::::new(), + ); + }); + project.update(cx, |project, cx| { + assert_eq!( + project.diagnostic_summary(cx), + DiagnosticSummary { + error_count: 0, + warning_count: 0, + } + ); + }); +} + +#[gpui2::test] +async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-lsp", + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": "" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // 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(), + version: Some(10000), + diagnostics: Vec::new(), + }); + cx.executor().run_until_parked(); + + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer.clone()], cx); + }); + let mut fake_server = fake_servers.next().await.unwrap(); + let notification = fake_server + .receive_notification::() + .await + .text_document; + assert_eq!(notification.version, 0); +} + +#[gpui2::test] +async fn test_toggling_enable_language_server(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut rust = Language::new( + LanguageConfig { + name: Arc::from("Rust"), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_rust_servers = rust + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "rust-lsp", + ..Default::default() + })) + .await; + let mut js = Language::new( + LanguageConfig { + name: Arc::from("JavaScript"), + path_suffixes: vec!["js".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_js_servers = js + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "js-lsp", + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| { + project.languages.add(Arc::new(rust)); + project.languages.add(Arc::new(js)); + }); + + let _rs_buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + let _js_buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx)) + .await + .unwrap(); + + let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server_1 + .receive_notification::() + .await + .text_document + .uri + .as_str(), + "file:///dir/a.rs" + ); + + let mut fake_js_server = fake_js_servers.next().await.unwrap(); + assert_eq!( + fake_js_server + .receive_notification::() + .await + .text_document + .uri + .as_str(), + "file:///dir/b.js" + ); + + // Disable Rust language server, ensuring only that server gets stopped. + cx.update(|cx| { + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.languages.insert( + Arc::from("Rust"), + LanguageSettingsContent { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }); + }) + }); + fake_rust_server_1 + .receive_notification::() + .await; + + // Enable Rust and disable JavaScript language servers, ensuring that the + // former gets started again and that the latter stops. + cx.update(|cx| { + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.languages.insert( + Arc::from("Rust"), + LanguageSettingsContent { + enable_language_server: Some(true), + ..Default::default() + }, + ); + settings.languages.insert( + Arc::from("JavaScript"), + LanguageSettingsContent { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }); + }) + }); + let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server_2 + .receive_notification::() + .await + .text_document + .uri + .as_str(), + "file:///dir/a.rs" + ); + fake_js_server + .receive_notification::() + .await; +} + +#[gpui2::test(iterations = 3)] +async fn test_transforming_diagnostics(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + ..Default::default() + })) + .await; + + let text = " + fn a() { A } + fn b() { BB } + fn c() { CCC } + " + .unindent(); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": text })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + let mut fake_server = fake_servers.next().await.unwrap(); + let open_notification = fake_server + .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::() + .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(), + 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)), + 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)), + 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)), + severity: Some(DiagnosticSeverity::ERROR), + source: Some("disk".to_string()), + message: "undefined variable 'CCC'".to_string(), + ..Default::default() + }, + ], + }); + + // The diagnostics have moved down since they were created. + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(3, 9)..Point::new(3, 11), + diagnostic: Diagnostic { + source: Some("disk".into()), + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'BB'".to_string(), + is_disk_based: true, + group_id: 1, + is_primary: true, + ..Default::default() + }, + }, + DiagnosticEntry { + range: Point::new(4, 9)..Point::new(4, 12), + diagnostic: Diagnostic { + source: Some("disk".into()), + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'CCC'".to_string(), + is_disk_based: true, + group_id: 2, + is_primary: true, + ..Default::default() + } + } + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, 0..buffer.len()), + [ + ("\n\nfn a() { ".to_string(), None), + ("A".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\nfn b() { ".to_string(), None), + ("BB".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\nfn c() { ".to_string(), None), + ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\n".to_string(), None), + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)), + [ + ("B".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\nfn c() { ".to_string(), None), + ("CC".to_string(), Some(DiagnosticSeverity::ERROR)), + ] + ); + }); + + // Ensure overlapping diagnostics are highlighted correctly. + fake_server.notify::(lsp2::PublishDiagnosticsParams { + uri: lsp2::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)), + 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)), + severity: Some(DiagnosticSeverity::WARNING), + message: "unreachable statement".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + ], + }); + + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(2, 9)..Point::new(2, 12), + diagnostic: Diagnostic { + source: Some("disk".into()), + severity: DiagnosticSeverity::WARNING, + message: "unreachable statement".to_string(), + is_disk_based: true, + group_id: 4, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(2, 9)..Point::new(2, 10), + diagnostic: Diagnostic { + source: Some("disk".into()), + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + is_disk_based: true, + group_id: 3, + is_primary: true, + ..Default::default() + }, + } + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)), + [ + ("fn a() { ".to_string(), None), + ("A".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }".to_string(), Some(DiagnosticSeverity::WARNING)), + ("\n".to_string(), None), + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)), + [ + (" }".to_string(), Some(DiagnosticSeverity::WARNING)), + ("\n".to_string(), None), + ] + ); + }); + + // Keep editing the buffer and ensure disk-based diagnostics get translated according to the + // changes since the last save. + buffer.update(cx, |buffer, cx| { + buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx); + buffer.edit( + [(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], + None, + cx, + ); + buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx); + }); + let change_notification_2 = fake_server + .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(), + 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)), + 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)), + severity: Some(DiagnosticSeverity::WARNING), + message: "undefined variable 'A'".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + ], + }); + + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, Point>(0..buffer.len(), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(2, 21)..Point::new(2, 22), + diagnostic: Diagnostic { + source: Some("disk".into()), + severity: DiagnosticSeverity::WARNING, + message: "undefined variable 'A'".to_string(), + is_disk_based: true, + group_id: 6, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(3, 9)..Point::new(3, 14), + diagnostic: Diagnostic { + source: Some("disk".into()), + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'BB'".to_string(), + is_disk_based: true, + group_id: 5, + is_primary: true, + ..Default::default() + }, + } + ] + ); + }); +} + +#[gpui2::test] +async fn test_empty_diagnostic_ranges(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let text = concat!( + "let one = ;\n", // + "let two = \n", + "let three = 3;\n", + ); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": text })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + project.update(cx, |project, cx| { + project + .update_buffer_diagnostics( + &buffer, + LanguageServerId(0), + None, + vec![ + DiagnosticEntry { + range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "syntax error 1".to_string(), + ..Default::default() + }, + }, + DiagnosticEntry { + range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "syntax error 2".to_string(), + ..Default::default() + }, + }, + ], + cx, + ) + .unwrap(); + }); + + // An empty range is extended forward to include the following character. + // At the end of a line, an empty range is extended backward to include + // the preceding character. + buffer.update(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let one = ", None), + (";", Some(DiagnosticSeverity::ERROR)), + ("\nlet two =", None), + (" ", Some(DiagnosticSeverity::ERROR)), + ("\nlet three = 3;\n", None) + ] + ); + }); +} + +#[gpui2::test] +async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({ "a.rs": "one two three" })) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + + project.update(cx, |project, cx| { + project + .update_diagnostic_entries( + LanguageServerId(0), + Path::new("/dir/a.rs").to_owned(), + None, + vec![DiagnosticEntry { + range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + is_primary: true, + message: "syntax error a1".to_string(), + ..Default::default() + }, + }], + cx, + ) + .unwrap(); + project + .update_diagnostic_entries( + LanguageServerId(1), + Path::new("/dir/a.rs").to_owned(), + None, + vec![DiagnosticEntry { + range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + is_primary: true, + message: "syntax error b1".to_string(), + ..Default::default() + }, + }], + cx, + ) + .unwrap(); + + assert_eq!( + project.diagnostic_summary(cx), + DiagnosticSummary { + error_count: 2, + warning_count: 0, + } + ); + }); +} + +#[gpui2::test] +async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await; + + let text = " + fn a() { + f1(); + } + fn b() { + f2(); + } + fn c() { + f3(); + } + " + .unindent(); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + let mut fake_server = fake_servers.next().await.unwrap(); + let lsp_document_version = fake_server + .receive_notification::() + .await + .text_document + .version; + + // Simulate editing the buffer after the language server computes some edits. + buffer.update(cx, |buffer, cx| { + buffer.edit( + [( + Point::new(0, 0)..Point::new(0, 0), + "// above first function\n", + )], + None, + cx, + ); + buffer.edit( + [( + Point::new(2, 0)..Point::new(2, 0), + " // inside first function\n", + )], + None, + cx, + ); + buffer.edit( + [( + Point::new(6, 4)..Point::new(6, 4), + "// inside second function ", + )], + None, + cx, + ); + + assert_eq!( + buffer.text(), + " + // above first function + fn a() { + // inside first function + f1(); + } + fn b() { + // inside second function f2(); + } + fn c() { + f3(); + } + " + .unindent() + ); + }); + + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &buffer, + vec![ + // replace body of first function + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(0, 0), + lsp2::Position::new(3, 0), + ), + new_text: " + fn a() { + f10(); + } + " + .unindent(), + }, + // edit inside second function + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(4, 6), + lsp2::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), + ), + new_text: "4000".into(), + }, + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(7, 5), + lsp2::Position::new(7, 6), + ), + new_text: "".into(), + }, + ], + LanguageServerId(0), + Some(lsp_document_version), + cx, + ) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + for (range, new_text) in edits { + buffer.edit([(range, new_text)], None, cx); + } + assert_eq!( + buffer.text(), + " + // above first function + fn a() { + // inside first function + f10(); + } + fn b() { + // inside second function f200(); + } + fn c() { + f4000(); + } + " + .unindent() + ); + }); +} + +#[gpui2::test] +async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let text = " + use a::b; + use a::c; + + fn f() { + b(); + c(); + } + " + .unindent(); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Simulate the language server sending us a small edit in the form of a very large diff. + // Rust-analyzer does this when performing a merge-imports code action. + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &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), + ), + 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), + ), + new_text: "\n\n".into(), + }, + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(0, 9), + lsp2::Position::new(0, 9), + ), + new_text: " + fn f() { + b(); + c(); + }" + .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), + ), + new_text: "".into(), + }, + ], + LanguageServerId(0), + None, + cx, + ) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + let edits = edits + .into_iter() + .map(|(range, text)| { + ( + range.start.to_point(buffer)..range.end.to_point(buffer), + text, + ) + }) + .collect::>(); + + assert_eq!( + edits, + [ + (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), + (Point::new(1, 0)..Point::new(2, 0), "".into()) + ] + ); + + for (range, new_text) in edits { + buffer.edit([(range, new_text)], None, cx); + } + assert_eq!( + buffer.text(), + " + use a::{b, c}; + + fn f() { + b(); + c(); + } + " + .unindent() + ); + }); +} + +#[gpui2::test] +async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let text = " + use a::b; + use a::c; + + fn f() { + b(); + c(); + } + " + .unindent(); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Simulate the language server sending us edits in a non-ordered fashion, + // with ranges sometimes being inverted or pointing to invalid locations. + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &buffer, + [ + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(0, 9), + lsp2::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), + ), + new_text: "a::{b, c}".into(), + }, + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(1, 0), + lsp2::Position::new(99, 0), + ), + new_text: "".into(), + }, + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(0, 9), + lsp2::Position::new(0, 9), + ), + new_text: " + fn f() { + b(); + c(); + }" + .unindent(), + }, + ], + LanguageServerId(0), + None, + cx, + ) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + let edits = edits + .into_iter() + .map(|(range, text)| { + ( + range.start.to_point(buffer)..range.end.to_point(buffer), + text, + ) + }) + .collect::>(); + + assert_eq!( + edits, + [ + (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), + (Point::new(1, 0)..Point::new(2, 0), "".into()) + ] + ); + + for (range, new_text) in edits { + buffer.edit([(range, new_text)], None, cx); + } + assert_eq!( + buffer.text(), + " + use a::{b, c}; + + fn f() { + b(); + c(); + } + " + .unindent() + ); + }); +} + +fn chunks_with_diagnostics( + buffer: &Buffer, + range: Range, +) -> Vec<(String, Option)> { + let mut chunks: Vec<(String, Option)> = Vec::new(); + for chunk in buffer.snapshot().chunks(range, true) { + if chunks.last().map_or(false, |prev_chunk| { + prev_chunk.1 == chunk.diagnostic_severity + }) { + chunks.last_mut().unwrap().0.push_str(chunk.text); + } else { + chunks.push((chunk.text.to_string(), chunk.diagnostic_severity)); + } + } + chunks +} + +#[gpui2::test(iterations = 10)] +async fn test_definition(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "const fn a() { A }", + "b.rs": "const y: i32 = crate::a()", + }), + ) + .await; + + let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) + .await + .unwrap(); + + let fake_server = fake_servers.next().await.unwrap(); + 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)); + + 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)), + ), + ))) + }); + + let mut definitions = project + .update(cx, |project, cx| project.definition(&buffer, 22, cx)) + .await + .unwrap(); + + // Assert no new language server started + cx.executor().run_until_parked(); + assert!(fake_servers.try_next().is_err()); + + assert_eq!(definitions.len(), 1); + let definition = definitions.pop().unwrap(); + cx.update(|cx| { + let target_buffer = definition.target.buffer.read(cx); + assert_eq!( + target_buffer + .file() + .unwrap() + .as_local() + .unwrap() + .abs_path(cx), + Path::new("/dir/a.rs"), + ); + assert_eq!(definition.target.range.to_offset(target_buffer), 9..10); + assert_eq!( + list_worktrees(&project, cx), + [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)] + ); + + drop(definition); + }); + cx.update(|cx| { + assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]); + }); + + fn list_worktrees<'a>( + project: &'a Handle, + cx: &'a AppContext, + ) -> Vec<(&'a Path, bool)> { + project + .read(cx) + .worktrees() + .map(|worktree| { + let worktree = worktree.read(cx); + ( + worktree.as_local().unwrap().abs_path().as_ref(), + worktree.is_visible(), + ) + }) + .collect::>() + } +} + +#[gpui2::test] +async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + Some(tree_sitter_typescript::language_typescript()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp2::ServerCapabilities { + completion_provider: Some(lsp2::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); + + let fake_server = fake_language_servers.next().await.unwrap(); + + let text = "let a = b.fqn"; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len(), cx) + }); + + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp2::CompletionResponse::Array(vec![ + lsp2::CompletionItem { + label: "fullyQualifiedName?".into(), + insert_text: Some("fullyQualifiedName".into()), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].new_text, "fullyQualifiedName"); + assert_eq!( + completions[0].old_range.to_offset(&snapshot), + text.len() - 3..text.len() + ); + + let text = "let a = \"atoms/cmp\""; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len() - 1, cx) + }); + + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp2::CompletionResponse::Array(vec![ + lsp2::CompletionItem { + label: "component".into(), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].new_text, "component"); + assert_eq!( + completions[0].old_range.to_offset(&snapshot), + text.len() - 4..text.len() - 1 + ); +} + +#[gpui2::test] +async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + Some(tree_sitter_typescript::language_typescript()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp2::ServerCapabilities { + completion_provider: Some(lsp2::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); + + let fake_server = fake_language_servers.next().await.unwrap(); + + let text = "let a = b.fqn"; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len(), cx) + }); + + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp2::CompletionResponse::Array(vec![ + lsp2::CompletionItem { + label: "fullyQualifiedName?".into(), + insert_text: Some("fully\rQualified\r\nName".into()), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + assert_eq!(completions.len(), 1); + 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) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "a", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); + + let fake_server = fake_language_servers.next().await.unwrap(); + + // 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 { + Ok(Some(vec![ + lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction { + title: "The code action".into(), + command: Some(lsp2::Command { + title: "The command".into(), + command: "_the/command".into(), + arguments: Some(vec![json!("the-argument")]), + }), + ..Default::default() + }), + lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction { + title: "two".into(), + ..Default::default() + }), + ])) + }) + .next() + .await; + + let action = actions.await.unwrap()[0].clone(); + let apply = project.update(cx, |project, cx| { + project.apply_code_action(buffer.clone(), action, true, cx) + }); + + // Resolving the code action does not populate its edits. In absence of + // edits, we must execute the given command. + 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::({ + let fake = fake_server.clone(); + move |params, _| { + assert_eq!(params.command, "_the/command"); + let fake = fake.clone(); + async move { + fake.server + .request::( + lsp2::ApplyWorkspaceEditParams { + label: None, + edit: lsp2::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), + ), + new_text: "X".into(), + }], + )] + .into_iter() + .collect(), + ), + ..Default::default() + }, + }, + ) + .await + .unwrap(); + Ok(Some(json!(null))) + } + } + }) + .next() + .await; + + // Applying the code action returns a project transaction containing the edits + // sent by the language server in its `workspaceEdit` request. + let transaction = apply.await.unwrap(); + assert!(transaction.0.contains_key(&buffer)); + buffer.update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "Xa"); + buffer.undo(cx); + assert_eq!(buffer.text(), "a"); + }); +} + +#[gpui2::test(iterations = 10)] +async fn test_save_file(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "file1": "the old contents", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + buffer.update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "the old contents"); + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); + }); + + project + .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) + .await + .unwrap(); + + let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); + assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text())); +} + +#[gpui2::test] +async fn test_save_in_single_file_worktree(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "file1": "the old contents", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); + }); + + project + .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) + .await + .unwrap(); + + let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); + assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text())); +} + +#[gpui2::test] +async fn test_save_as(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({})).await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + let languages = project.update(cx, |project, _| project.languages().clone()); + languages.register( + "/some/path", + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".into()], + ..Default::default() + }, + tree_sitter_rust::language(), + vec![], + |_| Default::default(), + ); + + let buffer = project.update(cx, |project, cx| { + project.create_buffer("", None, cx).unwrap() + }); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "abc")], None, cx); + assert!(buffer.is_dirty()); + assert!(!buffer.has_conflict()); + assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); + }); + project + .update(cx, |project, cx| { + project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) + }) + .await + .unwrap(); + assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); + + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, cx| { + assert_eq!( + buffer.file().unwrap().full_path(cx), + Path::new("dir/file1.rs") + ); + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); + }); + + let opened_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/file1.rs", cx) + }) + .await + .unwrap(); + assert_eq!(opened_buffer, buffer); +} + +#[gpui2::test(retries = 5)] +async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + + let dir = temp_tree(json!({ + "a": { + "file1": "", + "file2": "", + "file3": "", + }, + "b": { + "c": { + "file4": "", + "file5": "", + } + } + })); + + 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 = 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| { + project.update(cx, |project, cx| { + let tree = project.worktrees().next().unwrap(); + tree.read(cx) + .entry_for_path(path) + .unwrap_or_else(|| panic!("no entry for path {}", path)) + .id + }) + }; + + let buffer2 = buffer_for_path("a/file2", cx).await; + let buffer3 = buffer_for_path("a/file3", cx).await; + let buffer4 = buffer_for_path("b/c/file4", cx).await; + let buffer5 = buffer_for_path("b/c/file5", cx).await; + + let file2_id = id_for_path("a/file2", cx); + let file3_id = id_for_path("a/file3", cx); + let file4_id = id_for_path("b/c/file4", cx); + + // Create a remote copy of this worktree. + let tree = project.update(cx, |project, _| project.worktrees().next().unwrap()); + + let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto()); + + let updates = Arc::new(Mutex::new(Vec::new())); + tree.update(cx, |tree, cx| { + let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, { + let updates = updates.clone(); + move |update| { + updates.lock().push(update); + async { true } + } + }); + }); + + let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); + cx.executor().run_until_parked(); + + cx.update(|cx| { + assert!(!buffer2.read(cx).is_dirty()); + assert!(!buffer3.read(cx).is_dirty()); + assert!(!buffer4.read(cx).is_dirty()); + assert!(!buffer5.read(cx).is_dirty()); + }); + + // Rename and delete files and directories. + tree.flush_fs_events(cx).await; + std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); + std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); + std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); + std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); + tree.flush_fs_events(cx).await; + + let expected_paths = vec![ + "a", + "a/file1", + "a/file2.new", + "b", + "d", + "d/file3", + "d/file4", + ]; + + cx.update(|app| { + assert_eq!( + tree.read(app) + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + expected_paths + ); + }); + + assert_eq!(id_for_path("a/file2.new", cx), file2_id); + assert_eq!(id_for_path("d/file3", cx), file3_id); + assert_eq!(id_for_path("d/file4", cx), file4_id); + + cx.update(|cx| { + assert_eq!( + buffer2.read(cx).file().unwrap().path().as_ref(), + Path::new("a/file2.new") + ); + assert_eq!( + buffer3.read(cx).file().unwrap().path().as_ref(), + Path::new("d/file3") + ); + assert_eq!( + buffer4.read(cx).file().unwrap().path().as_ref(), + Path::new("d/file4") + ); + assert_eq!( + buffer5.read(cx).file().unwrap().path().as_ref(), + Path::new("b/c/file5") + ); + + assert!(!buffer2.read(cx).file().unwrap().is_deleted()); + assert!(!buffer3.read(cx).file().unwrap().is_deleted()); + assert!(!buffer4.read(cx).file().unwrap().is_deleted()); + assert!(buffer5.read(cx).file().unwrap().is_deleted()); + }); + + // Update the remote worktree. Check that it becomes consistent with the + // local worktree. + cx.executor().run_until_parked(); + + remote.update(cx, |remote, _| { + for update in updates.lock().drain(..) { + remote.as_remote_mut().unwrap().update_from_remote(update); + } + }); + cx.executor().run_until_parked(); + remote.update(cx, |remote, _| { + assert_eq!( + remote + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + expected_paths + ); + }); +} + +#[gpui2::test(iterations = 10)] +async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a": { + "file1": "", + } + }), + ) + .await; + + let project = Project::test(fs, [Path::new("/dir")], cx).await; + 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| { + project.update(cx, |project, cx| { + let tree = project.worktrees().next().unwrap(); + tree.read(cx) + .entry_for_path(path) + .unwrap_or_else(|| panic!("no entry for path {}", path)) + .id + }) + }; + + let dir_id = id_for_path("a", cx); + let file_id = id_for_path("a/file1", cx); + let buffer = project + .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx)) + .await + .unwrap(); + buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty())); + + project + .update(cx, |project, cx| { + project.rename_entry(dir_id, Path::new("b"), cx) + }) + .unwrap() + .await + .unwrap(); + cx.executor().run_until_parked(); + + assert_eq!(id_for_path("b", cx), dir_id); + assert_eq!(id_for_path("b/file1", cx), file_id); + buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty())); +} + +#[gpui2::test] +async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.txt": "a-contents", + "b.txt": "b-contents", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + // Spawn multiple tasks to open paths, repeating some paths. + let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| { + ( + p.open_local_buffer("/dir/a.txt", cx), + p.open_local_buffer("/dir/b.txt", cx), + p.open_local_buffer("/dir/a.txt", cx), + ) + }); + + let buffer_a_1 = buffer_a_1.await.unwrap(); + let buffer_a_2 = buffer_a_2.await.unwrap(); + let buffer_b = buffer_b.await.unwrap(); + assert_eq!(buffer_a_1.update(cx, |b, _| b.text()), "a-contents"); + assert_eq!(buffer_b.update(cx, |b, _| b.text()), "b-contents"); + + // There is only one buffer per path. + let buffer_a_id = buffer_a_1.entity_id(); + assert_eq!(buffer_a_2.entity_id(), buffer_a_id); + + // Open the same path again while it is still open. + drop(buffer_a_1); + let buffer_a_3 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx)) + .await + .unwrap(); + + // There's still only one buffer per path. + assert_eq!(buffer_a_3.entity_id(), buffer_a_id); +} + +#[gpui2::test] +async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { + init_test(cx); + dbg!("GAH"); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "file1": "abc", + "file2": "def", + "file3": "ghi", + }), + ) + .await; + dbg!("NOOP"); + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + let buffer1 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + let events = Arc::new(Mutex::new(Vec::new())); + + dbg!("BOOP"); + + // initially, the buffer isn't dirty. + buffer1.update(cx, |buffer, cx| { + cx.subscribe(&buffer1, { + let events = events.clone(); + move |_, _, event, _| match event { + BufferEvent::Operation(_) => {} + _ => events.lock().push(event.clone()), + } + }) + .detach(); + + assert!(!buffer.is_dirty()); + assert!(events.lock().is_empty()); + + buffer.edit([(1..2, "")], None, cx); + }); + dbg!("ADSASD"); + + // after the first edit, the buffer is dirty, and emits a dirtied event. + buffer1.update(cx, |buffer, cx| { + assert!(buffer.text() == "ac"); + assert!(buffer.is_dirty()); + assert_eq!( + *events.lock(), + &[language2::Event::Edited, language2::Event::DirtyChanged] + ); + events.lock().clear(); + buffer.did_save( + buffer.version(), + buffer.as_rope().fingerprint(), + buffer.file().unwrap().mtime(), + cx, + ); + }); + dbg!("1111"); + + // 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]); + events.lock().clear(); + + buffer.edit([(1..1, "B")], None, cx); + buffer.edit([(2..2, "D")], None, cx); + }); + + dbg!("5555555"); + + // after editing again, the buffer is dirty, and emits another dirty event. + buffer1.update(cx, |buffer, cx| { + assert!(buffer.text() == "aBDc"); + assert!(buffer.is_dirty()); + assert_eq!( + *events.lock(), + &[ + language2::Event::Edited, + language2::Event::DirtyChanged, + language2::Event::Edited, + ], + ); + events.lock().clear(); + + // After restoring the buffer to its previously-saved state, + // the buffer is not considered dirty anymore. + buffer.edit([(1..3, "")], None, cx); + assert!(buffer.text() == "ac"); + assert!(!buffer.is_dirty()); + }); + + dbg!("666666"); + assert_eq!( + *events.lock(), + &[language2::Event::Edited, language2::Event::DirtyChanged] + ); + + // When a file is deleted, the buffer is considered dirty. + let events = Arc::new(Mutex::new(Vec::new())); + let buffer2 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) + .await + .unwrap(); + buffer2.update(cx, |_, cx| { + cx.subscribe(&buffer2, { + let events = events.clone(); + move |_, _, event, _| events.lock().push(event.clone()) + }) + .detach(); + }); + + dbg!("0000000"); + + fs.remove_file("/dir/file2".as_ref(), Default::default()) + .await + .unwrap(); + cx.executor().run_until_parked(); + buffer2.update(cx, |buffer, _| assert!(buffer.is_dirty())); + assert_eq!( + *events.lock(), + &[ + language2::Event::DirtyChanged, + language2::Event::FileHandleChanged + ] + ); + + // When a file is already dirty when deleted, we don't emit a Dirtied event. + let events = Arc::new(Mutex::new(Vec::new())); + let buffer3 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx)) + .await + .unwrap(); + buffer3.update(cx, |_, cx| { + cx.subscribe(&buffer3, { + let events = events.clone(); + move |_, _, event, _| events.lock().push(event.clone()) + }) + .detach(); + }); + + dbg!(";;;;;;"); + buffer3.update(cx, |buffer, cx| { + buffer.edit([(0..0, "x")], None, cx); + }); + events.lock().clear(); + fs.remove_file("/dir/file3".as_ref(), Default::default()) + .await + .unwrap(); + cx.executor().run_until_parked(); + assert_eq!(*events.lock(), &[language2::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) { + init_test(cx); + + let initial_contents = "aaa\nbbbbb\nc\n"; + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "the-file": initial_contents, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx)) + .await + .unwrap(); + + let anchors = (0..3) + .map(|row| buffer.update(cx, |b, _| b.anchor_before(Point::new(row, 1)))) + .collect::>(); + + // Change the file on disk, adding two new lines of text, and removing + // one line. + buffer.update(cx, |buffer, _| { + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); + let new_contents = "AAAA\naaa\nBB\nbbbbb\n"; + fs.save( + "/dir/the-file".as_ref(), + &new_contents.into(), + LineEnding::Unix, + ) + .await + .unwrap(); + + // Because the buffer was not modified, it is reloaded from disk. Its + // contents are edited according to the diff between the old and new + // file contents. + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert_eq!(buffer.text(), new_contents); + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + + let anchor_positions = anchors + .iter() + .map(|anchor| anchor.to_point(&*buffer)) + .collect::>(); + assert_eq!( + anchor_positions, + [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)] + ); + }); + + // Modify the buffer + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, " ")], None, cx); + assert!(buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); + + // Change the file on disk again, adding blank lines to the beginning. + fs.save( + "/dir/the-file".as_ref(), + &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(), + LineEnding::Unix, + ) + .await + .unwrap(); + + // Because the buffer is modified, it doesn't reload from disk, but is + // marked as having a conflict. + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, _| { + assert!(buffer.has_conflict()); + }); +} + +#[gpui2::test] +async fn test_buffer_line_endings(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "file1": "a\nb\nc\n", + "file2": "one\r\ntwo\r\nthree\r\n", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer1 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + let buffer2 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) + .await + .unwrap(); + + buffer1.update(cx, |buffer, _| { + assert_eq!(buffer.text(), "a\nb\nc\n"); + assert_eq!(buffer.line_ending(), LineEnding::Unix); + }); + buffer2.update(cx, |buffer, _| { + assert_eq!(buffer.text(), "one\ntwo\nthree\n"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + }); + + // Change a file's line endings on disk from unix to windows. The buffer's + // state updates correctly. + fs.save( + "/dir/file1".as_ref(), + &"aaa\nb\nc\n".into(), + LineEnding::Windows, + ) + .await + .unwrap(); + cx.executor().run_until_parked(); + buffer1.update(cx, |buffer, _| { + assert_eq!(buffer.text(), "aaa\nb\nc\n"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + }); + + // Save a file with windows line endings. The file is written correctly. + buffer2.update(cx, |buffer, cx| { + buffer.set_text("one\ntwo\nthree\nfour\n", cx); + }); + project + .update(cx, |project, cx| project.save_buffer(buffer2, cx)) + .await + .unwrap(); + assert_eq!( + fs.load("/dir/file2".as_ref()).await.unwrap(), + "one\r\ntwo\r\nthree\r\nfour\r\n", + ); +} + +#[gpui2::test] +async fn test_grouped_diagnostics(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/the-dir", + json!({ + "a.rs": " + fn foo(mut v: Vec) { + for x in &v { + v.push(1); + } + } + " + .unindent(), + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx)) + .await + .unwrap(); + + let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap(); + let message = lsp2::PublishDiagnosticsParams { + uri: buffer_uri.clone(), + diagnostics: vec![ + lsp2::Diagnostic { + range: lsp2::Range::new(lsp2::Position::new(1, 8), lsp2::Position::new(1, 9)), + severity: Some(DiagnosticSeverity::WARNING), + message: "error 1".to_string(), + related_information: Some(vec![lsp2::DiagnosticRelatedInformation { + location: lsp2::Location { + uri: buffer_uri.clone(), + range: lsp2::Range::new( + lsp2::Position::new(1, 8), + lsp2::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)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 1 hint 1".to_string(), + related_information: Some(vec![lsp2::DiagnosticRelatedInformation { + location: lsp2::Location { + uri: buffer_uri.clone(), + range: lsp2::Range::new( + lsp2::Position::new(1, 8), + lsp2::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)), + severity: Some(DiagnosticSeverity::ERROR), + message: "error 2".to_string(), + related_information: Some(vec![ + lsp2::DiagnosticRelatedInformation { + location: lsp2::Location { + uri: buffer_uri.clone(), + range: lsp2::Range::new( + lsp2::Position::new(1, 13), + lsp2::Position::new(1, 15), + ), + }, + message: "error 2 hint 1".to_string(), + }, + lsp2::DiagnosticRelatedInformation { + location: lsp2::Location { + uri: buffer_uri.clone(), + range: lsp2::Range::new( + lsp2::Position::new(1, 13), + lsp2::Position::new(1, 15), + ), + }, + message: "error 2 hint 2".to_string(), + }, + ]), + ..Default::default() + }, + lsp2::Diagnostic { + range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 2 hint 1".to_string(), + related_information: Some(vec![lsp2::DiagnosticRelatedInformation { + location: lsp2::Location { + uri: buffer_uri.clone(), + range: lsp2::Range::new( + lsp2::Position::new(2, 8), + lsp2::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)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 2 hint 2".to_string(), + related_information: Some(vec![lsp2::DiagnosticRelatedInformation { + location: lsp2::Location { + uri: buffer_uri, + range: lsp2::Range::new( + lsp2::Position::new(2, 8), + lsp2::Position::new(2, 17), + ), + }, + message: "original diagnostic".to_string(), + }]), + ..Default::default() + }, + ], + version: None, + }; + + project + .update(cx, |p, cx| { + p.update_diagnostics(LanguageServerId(0), message, &[], cx) + }) + .unwrap(); + let buffer = buffer.update(cx, |buffer, _| buffer.snapshot()); + + assert_eq!( + buffer + .diagnostics_in_range::<_, Point>(0..buffer.len(), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 1, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(2, 8)..Point::new(2, 17), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + } + ] + ); + + assert_eq!( + buffer.diagnostic_group::(0).collect::>(), + &[ + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(2, 8)..Point::new(2, 17), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + } + ] + ); + + assert_eq!( + buffer.diagnostic_group::(1).collect::>(), + &[ + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 1, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() + } + }, + ] + ); +} + +#[gpui2::test] +async fn test_rename(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp2::ServerCapabilities { + rename_provider: Some(lsp2::OneOf::Right(lsp2::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: Default::default(), + })), + ..Default::default() + }, + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;" + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/one.rs", cx) + }) + .await + .unwrap(); + + let fake_server = fake_servers.next().await.unwrap(); + + let response = project.update(cx, |project, cx| { + project.prepare_rename(buffer.clone(), 7, cx) + }); + fake_server + .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), + )))) + }) + .next() + .await + .unwrap(); + let range = response.await.unwrap().unwrap(); + let range = buffer.update(cx, |buffer, _| range.to_offset(buffer)); + assert_eq!(range, 6..9); + + let response = project.update(cx, |project, cx| { + project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx) + }); + fake_server + .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) + ); + assert_eq!(params.new_name, "THREE"); + Ok(Some(lsp2::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), + ), + "THREE".to_string(), + )], + ), + ( + lsp2::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), + ), + "THREE".to_string(), + ), + lsp2::TextEdit::new( + lsp2::Range::new( + lsp2::Position::new(0, 35), + lsp2::Position::new(0, 38), + ), + "THREE".to_string(), + ), + ], + ), + ] + .into_iter() + .collect(), + ), + ..Default::default() + })) + }) + .next() + .await + .unwrap(); + let mut transaction = response.await.unwrap().0; + assert_eq!(transaction.len(), 2); + assert_eq!( + transaction + .remove_entry(&buffer) + .unwrap() + .0 + .update(cx, |buffer, _| buffer.text()), + "const THREE: usize = 1;" + ); + assert_eq!( + transaction + .into_keys() + .next() + .unwrap() + .update(cx, |buffer, _| buffer.text()), + "const TWO: usize = one::THREE + one::THREE;" + ); +} + +#[gpui2::test] +async fn test_search(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;", + "three.rs": "const THREE: usize = one::ONE + two::TWO;", + "four.rs": "const FOUR: usize = one::ONE + three::THREE;", + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + assert_eq!( + search( + &project, + SearchQuery::text("TWO", false, true, Vec::new(), Vec::new()).unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("two.rs".to_string(), vec![6..9]), + ("three.rs".to_string(), vec![37..40]) + ]) + ); + + let buffer_4 = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/four.rs", cx) + }) + .await + .unwrap(); + buffer_4.update(cx, |buffer, cx| { + let text = "two::TWO"; + buffer.edit([(20..28, text), (31..43, text)], None, cx); + }); + + assert_eq!( + search( + &project, + SearchQuery::text("TWO", false, true, Vec::new(), Vec::new()).unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("two.rs".to_string(), vec![6..9]), + ("three.rs".to_string(), vec![37..40]), + ("four.rs".to_string(), vec![25..28, 36..39]) + ]) + ); +} + +#[gpui2::test] +async fn test_search_with_inclusions(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let search_query = "file"; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": r#"// Rust file one"#, + "one.ts": r#"// TypeScript file one"#, + "two.rs": r#"// Rust file two"#, + "two.ts": r#"// TypeScript file two"#, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![PathMatcher::new("*.odd").unwrap()], + Vec::new() + ) + .unwrap(), + cx + ) + .await + .unwrap() + .is_empty(), + "If no inclusions match, no files should be returned" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![PathMatcher::new("*.rs").unwrap()], + Vec::new() + ) + .unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("two.rs".to_string(), vec![8..12]), + ]), + "Rust only search should give only Rust files" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap(), + ], + Vec::new() + ).unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.ts".to_string(), vec![14..18]), + ("two.ts".to_string(), vec![14..18]), + ]), + "TypeScript only search should give only TypeScript files, even if other inclusions don't match anything" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + PathMatcher::new("*.rs").unwrap(), + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap(), + ], + Vec::new() + ).unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("one.ts".to_string(), vec![14..18]), + ("two.rs".to_string(), vec![8..12]), + ("two.ts".to_string(), vec![14..18]), + ]), + "Rust and typescript search should give both Rust and TypeScript files, even if other inclusions don't match anything" + ); +} + +#[gpui2::test] +async fn test_search_with_exclusions(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let search_query = "file"; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": r#"// Rust file one"#, + "one.ts": r#"// TypeScript file one"#, + "two.rs": r#"// Rust file two"#, + "two.ts": r#"// TypeScript file two"#, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![PathMatcher::new("*.odd").unwrap()], + ) + .unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("one.ts".to_string(), vec![14..18]), + ("two.rs".to_string(), vec![8..12]), + ("two.ts".to_string(), vec![14..18]), + ]), + "If no exclusions match, all files should be returned" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![PathMatcher::new("*.rs").unwrap()], + ) + .unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.ts".to_string(), vec![14..18]), + ("two.ts".to_string(), vec![14..18]), + ]), + "Rust exclusion search should give only TypeScript files" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![ + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap(), + ], + ).unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("two.rs".to_string(), vec![8..12]), + ]), + "TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything" + ); + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![ + PathMatcher::new("*.rs").unwrap(), + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap(), + ], + ).unwrap(), + cx + ) + .await + .unwrap().is_empty(), + "Rust and typescript exclusion should give no files, even if other exclusions don't match anything" + ); +} + +#[gpui2::test] +async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui2::TestAppContext) { + init_test(cx); + + let search_query = "file"; + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": r#"// Rust file one"#, + "one.ts": r#"// TypeScript file one"#, + "two.rs": r#"// Rust file two"#, + "two.ts": r#"// TypeScript file two"#, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![PathMatcher::new("*.odd").unwrap()], + vec![PathMatcher::new("*.odd").unwrap()], + ) + .unwrap(), + cx + ) + .await + .unwrap() + .is_empty(), + "If both no exclusions and inclusions match, exclusions should win and return nothing" + ); + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![PathMatcher::new("*.ts").unwrap()], + vec![PathMatcher::new("*.ts").unwrap()], + ).unwrap(), + cx + ) + .await + .unwrap() + .is_empty(), + "If both TypeScript exclusions and inclusions match, exclusions should win and return nothing files." + ); + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap() + ], + vec![ + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap() + ], + ) + .unwrap(), + cx + ) + .await + .unwrap() + .is_empty(), + "Non-matching inclusions and exclusions should not change that." + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + PathMatcher::new("*.ts").unwrap(), + PathMatcher::new("*.odd").unwrap() + ], + vec![ + PathMatcher::new("*.rs").unwrap(), + PathMatcher::new("*.odd").unwrap() + ], + ) + .unwrap(), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.ts".to_string(), vec![14..18]), + ("two.ts".to_string(), vec![14..18]), + ]), + "Non-intersecting TypeScript inclusions and Rust exclusions should return TypeScript files" + ); +} + +#[test] +fn test_glob_literal_prefix() { + assert_eq!(glob_literal_prefix("**/*.js"), ""); + assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules"); + assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo"); + assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js"); +} + +async fn search( + project: &Handle, + query: SearchQuery, + cx: &mut gpui2::TestAppContext, +) -> Result>>> { + let mut search_rx = project.update(cx, |project, cx| project.search(query, cx)); + let mut result = HashMap::default(); + while let Some((buffer, range)) = search_rx.next().await { + result.entry(buffer).or_insert(range); + } + Ok(result + .into_iter() + .map(|(buffer, ranges)| { + buffer.update(cx, |buffer, _| { + let path = buffer.file().unwrap().path().to_string_lossy().to_string(); + let ranges = ranges + .into_iter() + .map(|range| range.to_offset(buffer)) + .collect::>(); + (path, ranges) + }) + }) + .collect()) +} + +fn init_test(cx: &mut gpui2::TestAppContext) { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } + + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + language2::init(cx); + Project::init_settings(cx); + }); +} diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index c1b762640b..6a3a3fbe78 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Context as _, Result}; use client2::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, HashSet, VecDeque}; -use fs::{ +use fs2::{ repository::{GitFileStatus, GitRepository, RepoPath}, Fs, }; @@ -2815,7 +2815,7 @@ pub type UpdatedGitRepositoriesSet = Arc<[(Arc, GitRepositoryChange)]>; impl Entry { fn new( path: Arc, - metadata: &fs::Metadata, + metadata: &fs2::Metadata, next_entry_id: &AtomicUsize, root_char_bag: CharBag, ) -> Self { @@ -4030,53 +4030,52 @@ struct UpdateIgnoreStatusJob { scan_queue: Sender, } -// todo!("re-enable when we have tests") -// pub trait WorktreeModelHandle { -// #[cfg(any(test, feature = "test-support"))] -// fn flush_fs_events<'a>( -// &self, -// cx: &'a gpui::TestAppContext, -// ) -> futures::future::LocalBoxFuture<'a, ()>; -// } +pub trait WorktreeModelHandle { + #[cfg(any(test, feature = "test-support"))] + fn flush_fs_events<'a>( + &self, + cx: &'a mut gpui2::TestAppContext, + ) -> futures::future::LocalBoxFuture<'a, ()>; +} -// impl WorktreeModelHandle for Handle { -// // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that -// // occurred before the worktree was constructed. These events can cause the worktree to perform -// // extra directory scans, and emit extra scan-state notifications. -// // -// // This function mutates the worktree's directory and waits for those mutations to be picked up, -// // to ensure that all redundant FS events have already been processed. -// #[cfg(any(test, feature = "test-support"))] -// fn flush_fs_events<'a>( -// &self, -// cx: &'a gpui::TestAppContext, -// ) -> futures::future::LocalBoxFuture<'a, ()> { -// let filename = "fs-event-sentinel"; -// let tree = self.clone(); -// let (fs, root_path) = self.read_with(cx, |tree, _| { -// let tree = tree.as_local().unwrap(); -// (tree.fs.clone(), tree.abs_path().clone()) -// }); +impl WorktreeModelHandle for Handle { + // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that + // occurred before the worktree was constructed. These events can cause the worktree to perform + // extra directory scans, and emit extra scan-state notifications. + // + // This function mutates the worktree's directory and waits for those mutations to be picked up, + // to ensure that all redundant FS events have already been processed. + #[cfg(any(test, feature = "test-support"))] + fn flush_fs_events<'a>( + &self, + cx: &'a mut gpui2::TestAppContext, + ) -> futures::future::LocalBoxFuture<'a, ()> { + let filename = "fs-event-sentinel"; + let tree = self.clone(); + let (fs, root_path) = self.update(cx, |tree, _| { + let tree = tree.as_local().unwrap(); + (tree.fs.clone(), tree.abs_path().clone()) + }); -// async move { -// fs.create_file(&root_path.join(filename), Default::default()) -// .await -// .unwrap(); -// tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some()) -// .await; + async move { + fs.create_file(&root_path.join(filename), Default::default()) + .await + .unwrap(); + cx.executor().run_until_parked(); + assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some())); -// fs.remove_file(&root_path.join(filename), Default::default()) -// .await -// .unwrap(); -// tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none()) -// .await; + fs.remove_file(&root_path.join(filename), Default::default()) + .await + .unwrap(); + cx.executor().run_until_parked(); + assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none())); -// cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) -// .await; -// } -// .boxed_local() -// } -// } + cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + } + .boxed_local() + } +} #[derive(Clone, Debug)] struct TraversalProgress<'a> { From e2ee9a28bfb3907447aecf85b8862dbee737e331 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 14:50:50 +0100 Subject: [PATCH 005/156] 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 006/156] 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 007/156] 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 008/156] 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 08e9b2e8481bb87f3ae76843d1f24f4d5a29f0bc Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 30 Oct 2023 13:32:47 -0400 Subject: [PATCH 009/156] added parsing support for <|S| |E|> spans --- crates/assistant/src/codegen.rs | 110 +++++++++++++++++++++++++------- crates/assistant/src/prompts.rs | 18 +++--- 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index b6ef6b5cfa..b26b1713b2 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -117,7 +117,7 @@ impl Codegen { let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1); let diff = cx.background().spawn(async move { - let chunks = strip_markdown_codeblock(response.await?); + let chunks = strip_invalid_spans_from_codeblock(response.await?); futures::pin_mut!(chunks); let mut diff = StreamingDiff::new(selected_text.to_string()); @@ -278,12 +278,13 @@ impl Codegen { } } -fn strip_markdown_codeblock( +fn strip_invalid_spans_from_codeblock( stream: impl Stream>, ) -> impl Stream> { let mut first_line = true; let mut buffer = String::new(); - let mut starts_with_fenced_code_block = false; + let mut starts_with_markdown_codeblock = false; + let mut includes_start_or_end_span = false; stream.filter_map(move |chunk| { let chunk = match chunk { Ok(chunk) => chunk, @@ -291,11 +292,31 @@ fn strip_markdown_codeblock( }; buffer.push_str(&chunk); + if buffer.len() > "<|S|".len() && buffer.starts_with("<|S|") { + includes_start_or_end_span = true; + + buffer = buffer + .strip_prefix("<|S|>") + .or_else(|| buffer.strip_prefix("<|S|")) + .unwrap_or(&buffer) + .to_string(); + } else if buffer.ends_with("|E|>") { + includes_start_or_end_span = true; + } else if buffer.starts_with("<|") + || buffer.starts_with("<|S") + || buffer.starts_with("<|S|") + || buffer.ends_with("|") + || buffer.ends_with("|E") + || buffer.ends_with("|E|") + { + return future::ready(None); + } + if first_line { if buffer == "" || buffer == "`" || buffer == "``" { return future::ready(None); } else if buffer.starts_with("```") { - starts_with_fenced_code_block = true; + starts_with_markdown_codeblock = true; if let Some(newline_ix) = buffer.find('\n') { buffer.replace_range(..newline_ix + 1, ""); first_line = false; @@ -305,16 +326,26 @@ fn strip_markdown_codeblock( } } - let text = if starts_with_fenced_code_block { - buffer + let mut text = buffer.to_string(); + if starts_with_markdown_codeblock { + text = text .strip_suffix("\n```\n") - .or_else(|| buffer.strip_suffix("\n```")) - .or_else(|| buffer.strip_suffix("\n``")) - .or_else(|| buffer.strip_suffix("\n`")) - .or_else(|| buffer.strip_suffix('\n')) - .unwrap_or(&buffer) - } else { - &buffer + .or_else(|| text.strip_suffix("\n```")) + .or_else(|| text.strip_suffix("\n``")) + .or_else(|| text.strip_suffix("\n`")) + .or_else(|| text.strip_suffix('\n')) + .unwrap_or(&text) + .to_string(); + } + + if includes_start_or_end_span { + text = text + .strip_suffix("|E|>") + .or_else(|| text.strip_suffix("E|>")) + .or_else(|| text.strip_prefix("|>")) + .or_else(|| text.strip_prefix(">")) + .unwrap_or(&text) + .to_string(); }; if text.contains('\n') { @@ -327,6 +358,7 @@ fn strip_markdown_codeblock( } else { Some(Ok(buffer.clone())) }; + buffer = remainder; future::ready(result) }) @@ -537,50 +569,82 @@ mod tests { } #[gpui::test] - async fn test_strip_markdown_codeblock() { + async fn test_strip_invalid_spans_from_codeblock() { assert_eq!( - strip_markdown_codeblock(chunks("Lorem ipsum dolor", 2)) + strip_invalid_spans_from_codeblock(chunks("Lorem ipsum dolor", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```\nLorem ipsum dolor", 2)) + strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```", 2)) + strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2)) + strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```html\n```js\nLorem ipsum dolor\n```\n```", 2)) - .map(|chunk| chunk.unwrap()) - .collect::() - .await, + strip_invalid_spans_from_codeblock(chunks( + "```html\n```js\nLorem ipsum dolor\n```\n```", + 2 + )) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, "```js\nLorem ipsum dolor\n```" ); assert_eq!( - strip_markdown_codeblock(chunks("``\nLorem ipsum dolor\n```", 2)) + strip_invalid_spans_from_codeblock(chunks("``\nLorem ipsum dolor\n```", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "``\nLorem ipsum dolor\n```" ); + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("<|S|Lorem ipsum|E|>", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("<|S|>Lorem ipsum", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); + + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("```\n<|S|>Lorem ipsum\n```", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("```\n<|S|Lorem ipsum|E|>\n```", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); fn chunks(text: &str, size: usize) -> impl Stream> { stream::iter( text.chars() diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index dffcbc2923..66425d31f7 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -79,12 +79,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> S if !flushed_selection { // The collapsed node ends after the selection starts, so we'll flush the selection first. summary.extend(buffer.text_for_range(offset..selected_range.start)); - summary.push_str("<|START|"); + summary.push_str("<|S|"); if selected_range.end == selected_range.start { summary.push_str(">"); } else { summary.extend(buffer.text_for_range(selected_range.clone())); - summary.push_str("|END|>"); + summary.push_str("|E|>"); } offset = selected_range.end; flushed_selection = true; @@ -106,12 +106,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> S // Flush selection if we haven't already done so. if !flushed_selection && offset <= selected_range.start { summary.extend(buffer.text_for_range(offset..selected_range.start)); - summary.push_str("<|START|"); + summary.push_str("<|S|"); if selected_range.end == selected_range.start { summary.push_str(">"); } else { summary.extend(buffer.text_for_range(selected_range.clone())); - summary.push_str("|END|>"); + summary.push_str("|E|>"); } offset = selected_range.end; } @@ -259,7 +259,7 @@ pub(crate) mod tests { summarize(&snapshot, Point::new(1, 4)..Point::new(1, 4)), indoc! {" struct X { - <|START|>a: usize, + <|S|>a: usize, b: usize, } @@ -285,7 +285,7 @@ pub(crate) mod tests { impl X { fn new() -> Self { - let <|START|a |END|>= 1; + let <|S|a |E|>= 1; let b = 2; Self { a, b } } @@ -306,7 +306,7 @@ pub(crate) mod tests { } impl X { - <|START|> + <|S|> fn new() -> Self {} pub fn a(&self, param: bool) -> usize {} @@ -332,7 +332,7 @@ pub(crate) mod tests { pub fn b(&self) -> usize {} } - <|START|>"} + <|S|>"} ); // Ensure nested functions get collapsed properly. @@ -368,7 +368,7 @@ pub(crate) mod tests { assert_eq!( summarize(&snapshot, Point::new(0, 0)..Point::new(0, 0)), indoc! {" - <|START|>struct X { + <|S|>struct X { a: usize, b: usize, } From 9688937468008db42e147edc7932b3f1c7a6729d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Oct 2023 19:33:23 +0100 Subject: [PATCH 010/156] 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 011/156] 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 012/156] 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 013/156] 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 014/156] 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 015/156] 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 016/156] 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 3e5379526ebdfbc769a83cef60f53d406bbdcbb4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 31 Oct 2023 13:09:35 +0000 Subject: [PATCH 017/156] Fix entity map drop behavior The entity map needs to be able to distinguish between the case when the entity_id is waiting to be dropped, and when it is completely gone. Before 8bc207141, it assumed that entity_ids in dropped_entity_ids could be re-used. This caused `take_dropped` to error because the slot had been overwritten. The fix there caused weak handles to allow upgrading a reference count from 0, which could resurrect items in `dropped_entity_ids` which caused them to be dropped twice. We could allow weak items to upgrade from 0, and delete from dropped_entity_ids, but that seemed more complicated than necessary. --- crates/gpui2/src/app/entity_map.rs | 78 +++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index e8f3606611..fbf838b74a 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -106,7 +106,12 @@ impl EntityMap { dropped_entity_ids .into_iter() .map(|entity_id| { - ref_counts.counts.remove(entity_id); + let count = ref_counts.counts.remove(entity_id).unwrap(); + debug_assert_eq!( + count.load(SeqCst), + 0, + "dropped an entity that was referenced" + ); (entity_id, self.entities.remove(entity_id).unwrap()) }) .collect() @@ -356,11 +361,15 @@ impl AnyWeakHandle { pub fn upgrade(&self) -> Option { let entity_map = self.entity_ref_counts.upgrade()?; - entity_map - .read() - .counts - .get(self.entity_id)? - .fetch_add(1, SeqCst); + let entity_map = entity_map.read(); + let ref_count = entity_map.counts.get(self.entity_id)?; + + // entity_id is in dropped_entity_ids + if ref_count.load(SeqCst) == 0 { + return None; + } + ref_count.fetch_add(1, SeqCst); + Some(AnyHandle { entity_id: self.entity_id, entity_type: self.entity_type, @@ -460,3 +469,60 @@ impl PartialEq> for WeakHandle { self.entity_id() == other.entity_id() } } + +#[cfg(test)] +mod test { + use crate::EntityMap; + + struct TestEntity { + pub i: i32, + } + + #[test] + fn test_entity_map_slot_assignment_before_cleanup() { + // Tests that slots are not re-used before take_dropped. + let mut entity_map = EntityMap::new(); + + let slot = entity_map.reserve::(); + entity_map.insert(slot, TestEntity { i: 1 }); + + let slot = entity_map.reserve::(); + entity_map.insert(slot, TestEntity { i: 2 }); + + let dropped = entity_map.take_dropped(); + assert_eq!(dropped.len(), 2); + + assert_eq!( + dropped + .into_iter() + .map(|(_, entity)| entity.downcast::().unwrap().i) + .collect::>(), + vec![1, 2], + ); + } + + #[test] + fn test_entity_map_weak_upgrade_before_cleanup() { + // Tests that weak handles are not upgraded before take_dropped + let mut entity_map = EntityMap::new(); + + let slot = entity_map.reserve::(); + let handle = entity_map.insert(slot, TestEntity { i: 1 }); + let weak = handle.downgrade(); + drop(handle); + + let strong = weak.upgrade(); + assert_eq!(strong, None); + + let dropped = entity_map.take_dropped(); + assert_eq!(dropped.len(), 1); + + assert_eq!( + dropped + .into_iter() + .map(|(_, entity)| entity.downcast::().unwrap().i) + .collect::>(), + vec![1], + ); + } +} From 8d0905e4793e14530c7cda5fae943292bd676335 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 09:25:36 -0400 Subject: [PATCH 018/156] 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 019/156] 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 020/156] 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 b8e007c6ec4ce3813f7a9c3058a1d5e0a356867e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 31 Oct 2023 14:24:12 +0000 Subject: [PATCH 021/156] Call flush_effects in test context update() In gpui1 we used to do this even outside of top-level contexts, but not sure we should make tests work that differently to the main app. --- crates/gpui2/src/app/test_context.rs | 4 ++-- crates/gpui2/src/platform/test/dispatcher.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 16d7504b2d..f590d05c6c 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -64,8 +64,8 @@ impl TestAppContext { } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { - let mut lock = self.app.lock(); - f(&mut *lock) + let mut cx = self.app.lock(); + cx.update(f) } pub fn read_window( diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 7c4af964b0..db70e7e4c1 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -77,7 +77,6 @@ impl TestDispatcher { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - eprintln!("self.count: {}", self.count); if self.count > 0 { self.count -= 1; cx.waker().wake_by_ref(); From 663e8aed8a18a10e69ead77f7172172978e56f2e Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 10:49:51 -0400 Subject: [PATCH 022/156] 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 023/156] 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 024/156] 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 025/156] 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 81f8e81e48494bffc8073cdab7bd0750d3346ea3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 31 Oct 2023 15:57:01 +0000 Subject: [PATCH 026/156] Fix block to allow for sync progress --- crates/gpui2/src/executor.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 8f128329a2..ca43d23a03 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -2,11 +2,15 @@ use crate::{AppContext, PlatformDispatcher}; use futures::{channel::mpsc, pin_mut}; use smol::prelude::*; use std::{ + borrow::BorrowMut, fmt::Debug, marker::PhantomData, mem, pin::Pin, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, + }, task::{Context, Poll}, time::Duration, }; @@ -136,7 +140,11 @@ impl Executor { pub fn block(&self, future: impl Future) -> R { pin_mut!(future); let (parker, unparker) = parking::pair(); + let awoken = Arc::new(AtomicBool::new(false)); + let awoken2 = awoken.clone(); + let waker = waker_fn(move || { + awoken2.store(true, SeqCst); unparker.unpark(); }); let mut cx = std::task::Context::from_waker(&waker); @@ -146,6 +154,10 @@ impl Executor { Poll::Ready(result) => return result, Poll::Pending => { if !self.dispatcher.poll() { + if awoken.swap(false, SeqCst) { + continue; + } + #[cfg(any(test, feature = "test-support"))] if let Some(test) = self.dispatcher.as_test() { if !test.parking_allowed() { From fed391fe6b9870c36f71ac5e293abbd75bb4dca0 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 31 Oct 2023 12:00:45 -0400 Subject: [PATCH 027/156] 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 0e9a82711cc8947ad6539a507f4d893772da982b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 31 Oct 2023 16:04:33 +0000 Subject: [PATCH 028/156] Actually deliver test events to subscribers --- crates/gpui2/src/app/test_context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index f590d05c6c..00712eba50 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -161,8 +161,8 @@ impl TestAppContext { let (mut tx, rx) = futures::channel::mpsc::unbounded(); entity .update(self, |_, cx: &mut ModelContext| { - cx.subscribe(&entity, move |_, _, event, _| { - let _ = tx.send(event.clone()); + cx.subscribe(&entity, move |_, _, event, cx| { + cx.executor().block(tx.send(event.clone())).unwrap(); }) }) .detach(); From 8db6b78fddb062ccb6b097792c28da375f949555 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 31 Oct 2023 16:14:10 +0000 Subject: [PATCH 029/156] Implement start/finish waiting for gpui2 I'm not sure these are strictly necessary, but it will make porting tests easier to have them. --- crates/gpui2/src/executor.rs | 11 ++++++++--- crates/gpui2/src/platform/test/dispatcher.rs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index ca43d23a03..5e9ac74a0c 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -161,7 +161,12 @@ impl Executor { #[cfg(any(test, feature = "test-support"))] if let Some(test) = self.dispatcher.as_test() { if !test.parking_allowed() { - panic!("blocked with nothing left to run") + let mut backtrace_message = String::new(); + if let Some(backtrace) = test.waiting_backtrace() { + backtrace_message = + format!("\nbacktrace of waiting future:\n{:?}", backtrace); + } + panic!("parked with nothing left to run\n{:?}", backtrace_message) } } parker.park(); @@ -220,12 +225,12 @@ impl Executor { #[cfg(any(test, feature = "test-support"))] pub fn start_waiting(&self) { - todo!("start_waiting") + self.dispatcher.as_test().unwrap().start_waiting(); } #[cfg(any(test, feature = "test-support"))] pub fn finish_waiting(&self) { - todo!("finish_waiting") + self.dispatcher.as_test().unwrap().finish_waiting(); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index db70e7e4c1..52a25d352c 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -1,5 +1,6 @@ use crate::PlatformDispatcher; use async_task::Runnable; +use backtrace::Backtrace; use collections::{HashMap, VecDeque}; use parking_lot::Mutex; use rand::prelude::*; @@ -29,6 +30,7 @@ struct TestDispatcherState { is_main_thread: bool, next_id: TestDispatcherId, allow_parking: bool, + waiting_backtrace: Option, } impl TestDispatcher { @@ -42,6 +44,7 @@ impl TestDispatcher { is_main_thread: true, next_id: TestDispatcherId(1), allow_parking: false, + waiting_backtrace: None, }; TestDispatcher { @@ -103,6 +106,21 @@ impl TestDispatcher { pub fn allow_parking(&self) { self.state.lock().allow_parking = true } + + pub fn start_waiting(&self) { + self.state.lock().waiting_backtrace = Some(Backtrace::new_unresolved()); + } + + pub fn finish_waiting(&self) { + self.state.lock().waiting_backtrace.take(); + } + + pub fn waiting_backtrace(&self) -> Option { + self.state.lock().waiting_backtrace.take().map(|mut b| { + b.resolve(); + b + }) + } } impl Clone for TestDispatcher { From 0a2fde8707a0ccd050bf02203432942641aafe77 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 11:03:01 -0600 Subject: [PATCH 030/156] 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 031/156] 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 032/156] 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 033/156] 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 034/156] 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 244e8ce101cedb56d8c073c5acff99b0cf344b70 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 31 Oct 2023 14:04:03 -0700 Subject: [PATCH 035/156] WIP - make livekit work in GPUI2 --- Cargo.lock | 35 +- crates/call2/Cargo.toml | 6 +- crates/call2/src/participant.rs | 4 +- crates/call2/src/room.rs | 225 +++-- .../LiveKitBridge/Package.resolved | 4 +- crates/live_kit_client2/.cargo/config.toml | 2 + crates/live_kit_client2/Cargo.toml | 71 ++ .../LiveKitBridge/Package.resolved | 52 + .../LiveKitBridge/Package.swift | 27 + .../live_kit_client2/LiveKitBridge/README.md | 3 + .../Sources/LiveKitBridge/LiveKitBridge.swift | 327 ++++++ crates/live_kit_client2/build.rs | 172 ++++ crates/live_kit_client2/examples/test_app.rs | 175 ++++ .../live_kit_client2/src/live_kit_client2.rs | 11 + crates/live_kit_client2/src/prod.rs | 943 ++++++++++++++++++ crates/live_kit_client2/src/test.rs | 647 ++++++++++++ 16 files changed, 2586 insertions(+), 118 deletions(-) create mode 100644 crates/live_kit_client2/.cargo/config.toml create mode 100644 crates/live_kit_client2/Cargo.toml create mode 100644 crates/live_kit_client2/LiveKitBridge/Package.resolved create mode 100644 crates/live_kit_client2/LiveKitBridge/Package.swift create mode 100644 crates/live_kit_client2/LiveKitBridge/README.md create mode 100644 crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift create mode 100644 crates/live_kit_client2/build.rs create mode 100644 crates/live_kit_client2/examples/test_app.rs create mode 100644 crates/live_kit_client2/src/live_kit_client2.rs create mode 100644 crates/live_kit_client2/src/prod.rs create mode 100644 crates/live_kit_client2/src/test.rs diff --git a/Cargo.lock b/Cargo.lock index 3aca27106c..bb72f5d6ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,7 +1169,7 @@ dependencies = [ "futures 0.3.28", "gpui2", "language2", - "live_kit_client", + "live_kit_client2", "log", "media", "postage", @@ -4589,6 +4589,39 @@ dependencies = [ "simplelog", ] +[[package]] +name = "live_kit_client2" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-broadcast", + "async-trait", + "block", + "byteorder", + "bytes 1.5.0", + "cocoa", + "collections", + "core-foundation", + "core-graphics", + "foreign-types", + "futures 0.3.28", + "gpui2", + "hmac 0.12.1", + "jwt", + "live_kit_server", + "log", + "media", + "nanoid", + "objc", + "parking_lot 0.11.2", + "postage", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.7", + "simplelog", +] + [[package]] name = "live_kit_server" version = "0.1.0" diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index f0e47832ed..e918ada3e8 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -13,7 +13,7 @@ test-support = [ "client2/test-support", "collections/test-support", "gpui2/test-support", - "live_kit_client/test-support", + "live_kit_client2/test-support", "project2/test-support", "util/test-support" ] @@ -24,7 +24,7 @@ client2 = { path = "../client2" } collections = { path = "../collections" } gpui2 = { path = "../gpui2" } log.workspace = true -live_kit_client = { path = "../live_kit_client" } +live_kit_client2 = { path = "../live_kit_client2" } fs2 = { path = "../fs2" } language2 = { path = "../language2" } media = { path = "../media" } @@ -47,6 +47,6 @@ fs2 = { path = "../fs2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] } -live_kit_client = { path = "../live_kit_client", features = ["test-support"] } +live_kit_client2 = { path = "../live_kit_client2", features = ["test-support"] } project2 = { path = "../project2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index 7f3e91dbba..a1837e3ad0 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use client2::ParticipantIndex; use client2::{proto, User}; use gpui2::WeakModel; -pub use live_kit_client::Frame; +pub use live_kit_client2::Frame; use project2::Project; use std::{fmt, sync::Arc}; @@ -51,7 +51,7 @@ pub struct RemoteParticipant { #[derive(Clone)] pub struct RemoteVideoTrack { - pub(crate) live_kit_track: Arc, + pub(crate) live_kit_track: Arc, } unsafe impl Send for RemoteVideoTrack {} diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index b7bac52a8b..f0e0b8de17 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -19,7 +19,7 @@ use gpui2::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language2::LanguageRegistry; -use live_kit_client::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate}; +use live_kit_client2::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate}; use postage::{sink::Sink, stream::Stream, watch}; use project2::Project; use settings2::Settings; @@ -59,7 +59,7 @@ pub enum Event { pub struct Room { id: u64, channel_id: Option, - // live_kit: Option, + live_kit: Option, status: RoomStatus, shared_projects: HashSet>, joined_projects: HashSet>, @@ -114,125 +114,130 @@ impl Room { user_store: Model, cx: &mut ModelContext, ) -> Self { - todo!() - // let _live_kit_room = if let Some(connection_info) = live_kit_connection_info { - // let room = live_kit_client::Room::new(); - // let mut status = room.status(); - // // Consume the initial status of the room. - // let _ = status.try_recv(); - // let _maintain_room = cx.spawn(|this, mut cx| async move { - // while let Some(status) = status.next().await { - // let this = if let Some(this) = this.upgrade() { - // this - // } else { - // break; - // }; + let live_kit_room = if let Some(connection_info) = live_kit_connection_info { + let room = live_kit_client2::Room::new(); + let mut status = room.status(); + // Consume the initial status of the room. + let _ = status.try_recv(); + let _maintain_room = cx.spawn(|this, mut cx| async move { + while let Some(status) = status.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; - // if status == live_kit_client::ConnectionState::Disconnected { - // this.update(&mut cx, |this, cx| this.leave(cx).log_err()) - // .ok(); - // break; - // } - // } - // }); + if status == live_kit_client2::ConnectionState::Disconnected { + this.update(&mut cx, |this, cx| this.leave(cx).log_err()) + .ok(); + break; + } + } + }); - // let mut track_video_changes = room.remote_video_track_updates(); - // let _maintain_video_tracks = cx.spawn(|this, mut cx| async move { - // while let Some(track_change) = track_video_changes.next().await { - // let this = if let Some(this) = this.upgrade() { - // this - // } else { - // break; - // }; + let _maintain_video_tracks = cx.spawn_on_main({ + let room = room.clone(); + move |this, mut cx| async move { + let mut track_video_changes = room.remote_video_track_updates(); + while let Some(track_change) = track_video_changes.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; - // this.update(&mut cx, |this, cx| { - // this.remote_video_track_updated(track_change, cx).log_err() - // }) - // .ok(); - // } - // }); + this.update(&mut cx, |this, cx| { + this.remote_video_track_updated(track_change, cx).log_err() + }) + .ok(); + } + } + }); - // let mut track_audio_changes = room.remote_audio_track_updates(); - // let _maintain_audio_tracks = cx.spawn(|this, mut cx| async move { - // while let Some(track_change) = track_audio_changes.next().await { - // let this = if let Some(this) = this.upgrade() { - // this - // } else { - // break; - // }; + let _maintain_audio_tracks = cx.spawn_on_main({ + let room = room.clone(); + |this, mut cx| async move { + let mut track_audio_changes = room.remote_audio_track_updates(); + while let Some(track_change) = track_audio_changes.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; - // this.update(&mut cx, |this, cx| { - // this.remote_audio_track_updated(track_change, cx).log_err() - // }) - // .ok(); - // } - // }); + this.update(&mut cx, |this, cx| { + this.remote_audio_track_updated(track_change, cx).log_err() + }) + .ok(); + } + } + }); - // let connect = room.connect(&connection_info.server_url, &connection_info.token); - // cx.spawn(|this, mut cx| async move { - // connect.await?; + let connect = room.connect(&connection_info.server_url, &connection_info.token); + cx.spawn(|this, mut cx| async move { + connect.await?; - // if !cx.update(|cx| Self::mute_on_join(cx))? { - // this.update(&mut cx, |this, cx| this.share_microphone(cx))? - // .await?; - // } + if !cx.update(|cx| Self::mute_on_join(cx))? { + this.update(&mut cx, |this, cx| this.share_microphone(cx))? + .await?; + } - // anyhow::Ok(()) - // }) - // .detach_and_log_err(cx); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); - // Some(LiveKitRoom { - // room, - // screen_track: LocalTrack::None, - // microphone_track: LocalTrack::None, - // next_publish_id: 0, - // muted_by_user: false, - // deafened: false, - // speaking: false, - // _maintain_room, - // _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], - // }) - // } else { - // None - // }; + Some(LiveKitRoom { + room, + screen_track: LocalTrack::None, + microphone_track: LocalTrack::None, + next_publish_id: 0, + muted_by_user: false, + deafened: false, + speaking: false, + _maintain_room, + _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], + }) + } else { + None + }; - // let maintain_connection = cx.spawn({ - // let client = client.clone(); - // move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err() - // }); + let maintain_connection = cx.spawn({ + let client = client.clone(); + move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err() + }); - // Audio::play_sound(Sound::Joined, cx); + Audio::play_sound(Sound::Joined, cx); - // let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); + let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); - // Self { - // id, - // channel_id, - // // live_kit: live_kit_room, - // status: RoomStatus::Online, - // shared_projects: Default::default(), - // joined_projects: Default::default(), - // participant_user_ids: Default::default(), - // local_participant: Default::default(), - // remote_participants: Default::default(), - // pending_participants: Default::default(), - // pending_call_count: 0, - // client_subscriptions: vec![ - // client.add_message_handler(cx.weak_handle(), Self::handle_room_updated) - // ], - // _subscriptions: vec![ - // cx.on_release(Self::released), - // cx.on_app_quit(Self::app_will_quit), - // ], - // leave_when_empty: false, - // pending_room_update: None, - // client, - // user_store, - // follows_by_leader_id_project_id: Default::default(), - // maintain_connection: Some(maintain_connection), - // room_update_completed_tx, - // room_update_completed_rx, - // } + Self { + id, + channel_id, + live_kit: live_kit_room, + status: RoomStatus::Online, + shared_projects: Default::default(), + joined_projects: Default::default(), + participant_user_ids: Default::default(), + local_participant: Default::default(), + remote_participants: Default::default(), + pending_participants: Default::default(), + pending_call_count: 0, + client_subscriptions: vec![ + client.add_message_handler(cx.weak_model(), Self::handle_room_updated) + ], + _subscriptions: vec![ + cx.on_release(Self::released), + cx.on_app_quit(Self::app_will_quit), + ], + leave_when_empty: false, + pending_room_update: None, + client, + user_store, + follows_by_leader_id_project_id: Default::default(), + maintain_connection: Some(maintain_connection), + room_update_completed_tx, + room_update_completed_rx, + } } pub(crate) fn create( @@ -1518,7 +1523,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) { todo!() // self.live_kit // .as_ref() @@ -1529,7 +1534,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/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index b925bc8f0d..85ae088565 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-protobuf.git", "state": { "branch": null, - "revision": "ce20dc083ee485524b802669890291c0d8090170", - "version": "1.22.1" + "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e", + "version": "1.21.0" } } ] diff --git a/crates/live_kit_client2/.cargo/config.toml b/crates/live_kit_client2/.cargo/config.toml new file mode 100644 index 0000000000..b33fe211bd --- /dev/null +++ b/crates/live_kit_client2/.cargo/config.toml @@ -0,0 +1,2 @@ +[live_kit_client_test] +rustflags = ["-C", "link-args=-ObjC"] diff --git a/crates/live_kit_client2/Cargo.toml b/crates/live_kit_client2/Cargo.toml new file mode 100644 index 0000000000..b5b45a8d45 --- /dev/null +++ b/crates/live_kit_client2/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "live_kit_client2" +version = "0.1.0" +edition = "2021" +description = "Bindings to LiveKit Swift client SDK" +publish = false + +[lib] +path = "src/live_kit_client2.rs" +doctest = false + +[[example]] +name = "test_app" + +[features] +test-support = [ + "async-trait", + "collections/test-support", + "gpui2/test-support", + "live_kit_server", + "nanoid", +] + +[dependencies] +collections = { path = "../collections", optional = true } +gpui2 = { path = "../gpui2", optional = true } +live_kit_server = { path = "../live_kit_server", optional = true } +media = { path = "../media" } + +anyhow.workspace = true +async-broadcast = "0.4" +core-foundation = "0.9.3" +core-graphics = "0.22.3" +futures.workspace = true +log.workspace = true +parking_lot.workspace = true +postage.workspace = true + +async-trait = { workspace = true, optional = true } +nanoid = { version ="0.4", optional = true} + +[dev-dependencies] +collections = { path = "../collections", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +live_kit_server = { path = "../live_kit_server" } +media = { path = "../media" } +nanoid = "0.4" + +anyhow.workspace = true +async-trait.workspace = true +block = "0.1" +bytes = "1.2" +byteorder = "1.4" +cocoa = "0.24" +core-foundation = "0.9.3" +core-graphics = "0.22.3" +foreign-types = "0.3" +futures.workspace = true +hmac = "0.12" +jwt = "0.16" +objc = "0.2" +parking_lot.workspace = true +serde.workspace = true +serde_derive.workspace = true +sha2 = "0.10" +simplelog = "0.9" + +[build-dependencies] +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true diff --git a/crates/live_kit_client2/LiveKitBridge/Package.resolved b/crates/live_kit_client2/LiveKitBridge/Package.resolved new file mode 100644 index 0000000000..b925bc8f0d --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "LiveKit", + "repositoryURL": "https://github.com/livekit/client-sdk-swift.git", + "state": { + "branch": null, + "revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff", + "version": "1.0.12" + } + }, + { + "package": "Promises", + "repositoryURL": "https://github.com/google/promises.git", + "state": { + "branch": null, + "revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", + "version": "2.2.0" + } + }, + { + "package": "WebRTC", + "repositoryURL": "https://github.com/webrtc-sdk/Specs.git", + "state": { + "branch": null, + "revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65", + "version": "104.5112.17" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "32e8d724467f8fe623624570367e3d50c5638e46", + "version": "1.5.2" + } + }, + { + "package": "SwiftProtobuf", + "repositoryURL": "https://github.com/apple/swift-protobuf.git", + "state": { + "branch": null, + "revision": "ce20dc083ee485524b802669890291c0d8090170", + "version": "1.22.1" + } + } + ] + }, + "version": 1 +} diff --git a/crates/live_kit_client2/LiveKitBridge/Package.swift b/crates/live_kit_client2/LiveKitBridge/Package.swift new file mode 100644 index 0000000000..d7b5c271b9 --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.5 + +import PackageDescription + +let package = Package( + name: "LiveKitBridge", + platforms: [ + .macOS(.v10_15) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "LiveKitBridge", + type: .static, + targets: ["LiveKitBridge"]), + ], + dependencies: [ + .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "LiveKitBridge", + dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]), + ] +) diff --git a/crates/live_kit_client2/LiveKitBridge/README.md b/crates/live_kit_client2/LiveKitBridge/README.md new file mode 100644 index 0000000000..b982c67286 --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/README.md @@ -0,0 +1,3 @@ +# LiveKitBridge + +A description of this package. diff --git a/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift new file mode 100644 index 0000000000..5f22acf581 --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -0,0 +1,327 @@ +import Foundation +import LiveKit +import WebRTC +import ScreenCaptureKit + +class LKRoomDelegate: RoomDelegate { + var data: UnsafeRawPointer + var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void + var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void + var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void + var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void + var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void + var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + + init( + data: UnsafeRawPointer, + onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, + onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, + onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) + { + self.data = data + self.onDidDisconnect = onDidDisconnect + self.onDidSubscribeToRemoteAudioTrack = onDidSubscribeToRemoteAudioTrack + self.onDidUnsubscribeFromRemoteAudioTrack = onDidUnsubscribeFromRemoteAudioTrack + self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack + self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack + self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack + self.onActiveSpeakersChanged = onActiveSpeakersChanged + } + + func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { + if connectionState.isDisconnected { + self.onDidDisconnect(self.data) + } + } + + func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) { + if track.kind == .video { + self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) + } else if track.kind == .audio { + self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque()) + } + } + + func room(_ room: Room, participant: Participant, didUpdate publication: TrackPublication, muted: Bool) { + if publication.kind == .audio { + self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted) + } + } + + func room(_ room: Room, didUpdate speakers: [Participant]) { + guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return } + self.onActiveSpeakersChanged(self.data, speaker_ids) + } + + func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { + if track.kind == .video { + self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString) + } else if track.kind == .audio { + self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) + } + } +} + +class LKVideoRenderer: NSObject, VideoRenderer { + var data: UnsafeRawPointer + var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool + var onDrop: @convention(c) (UnsafeRawPointer) -> Void + var adaptiveStreamIsEnabled: Bool = false + var adaptiveStreamSize: CGSize = .zero + weak var track: VideoTrack? + + init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) { + self.data = data + self.onFrame = onFrame + self.onDrop = onDrop + } + + deinit { + self.onDrop(self.data) + } + + func setSize(_ size: CGSize) { + } + + func renderFrame(_ frame: RTCVideoFrame?) { + let buffer = frame?.buffer as? RTCCVPixelBuffer + if let pixelBuffer = buffer?.pixelBuffer { + if !self.onFrame(self.data, pixelBuffer) { + DispatchQueue.main.async { + self.track?.remove(videoRenderer: self) + } + } + } + } +} + +@_cdecl("LKRoomDelegateCreate") +public func LKRoomDelegateCreate( + data: UnsafeRawPointer, + onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, + onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, + onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void +) -> UnsafeMutableRawPointer { + let delegate = LKRoomDelegate( + data: data, + onDidDisconnect: onDidDisconnect, + onDidSubscribeToRemoteAudioTrack: onDidSubscribeToRemoteAudioTrack, + onDidUnsubscribeFromRemoteAudioTrack: onDidUnsubscribeFromRemoteAudioTrack, + onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, + onActiveSpeakersChanged: onActiveSpeakerChanged, + onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, + onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack + ) + return Unmanaged.passRetained(delegate).toOpaque() +} + +@_cdecl("LKRoomCreate") +public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer { + let delegate = Unmanaged.fromOpaque(delegate).takeUnretainedValue() + return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque() +} + +@_cdecl("LKRoomConnect") +public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + room.connect(url as String, token as String).then { _ in + callback(callback_data, UnsafeRawPointer(nil) as! CFString?) + }.catch { error in + callback(callback_data, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRoomDisconnect") +public func LKRoomDisconnect(room: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + room.disconnect() +} + +@_cdecl("LKRoomPublishVideoTrack") +public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + room.localParticipant?.publishVideoTrack(track: track).then { publication in + callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) + }.catch { error in + callback(callback_data, nil, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRoomPublishAudioTrack") +public func LKRoomPublishAudioTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + room.localParticipant?.publishAudioTrack(track: track).then { publication in + callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) + }.catch { error in + callback(callback_data, nil, error.localizedDescription as CFString) + } +} + + +@_cdecl("LKRoomUnpublishTrack") +public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + let _ = room.localParticipant?.unpublish(publication: publication) +} + +@_cdecl("LKRoomAudioTracksForRemoteParticipant") +public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant") +public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKRoomVideoTracksForRemoteParticipant") +public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKLocalAudioTrackCreateTrack") +public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer { + let track = LocalAudioTrack.createTrack(options: AudioCaptureOptions( + echoCancellation: true, + noiseSuppression: true + )) + + return Unmanaged.passRetained(track).toOpaque() +} + + +@_cdecl("LKCreateScreenShareTrackForDisplay") +public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + let display = Unmanaged.fromOpaque(display).takeUnretainedValue() + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: display, preferredMethod: .legacy) + return Unmanaged.passRetained(track).toOpaque() +} + +@_cdecl("LKVideoRendererCreate") +public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer { + Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque() +} + +@_cdecl("LKVideoTrackAddRenderer") +public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() as! VideoTrack + let renderer = Unmanaged.fromOpaque(renderer).takeRetainedValue() + renderer.track = track + track.add(videoRenderer: renderer) +} + +@_cdecl("LKRemoteVideoTrackGetSid") +public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + return track.sid! as CFString +} + +@_cdecl("LKRemoteAudioTrackGetSid") +public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + return track.sid! as CFString +} + +@_cdecl("LKDisplaySources") +public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) { + MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in + callback(data, displaySources as CFArray, nil) + }.catch { error in + callback(data, nil, error.localizedDescription as CFString) + } +} + +@_cdecl("LKLocalTrackPublicationSetMute") +public func LKLocalTrackPublicationSetMute( + publication: UnsafeRawPointer, + muted: Bool, + on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, + callback_data: UnsafeRawPointer +) { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + if muted { + publication.mute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } + } else { + publication.unmute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } + } +} + +@_cdecl("LKRemoteTrackPublicationSetEnabled") +public func LKRemoteTrackPublicationSetEnabled( + publication: UnsafeRawPointer, + enabled: Bool, + on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, + callback_data: UnsafeRawPointer +) { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + publication.set(enabled: enabled).then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRemoteTrackPublicationIsMuted") +public func LKRemoteTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.muted +} + +@_cdecl("LKRemoteTrackPublicationGetSid") +public func LKRemoteTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client2/build.rs b/crates/live_kit_client2/build.rs new file mode 100644 index 0000000000..1445704b46 --- /dev/null +++ b/crates/live_kit_client2/build.rs @@ -0,0 +1,172 @@ +use serde::Deserialize; +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SwiftTargetInfo { + pub triple: String, + pub unversioned_triple: String, + pub module_triple: String, + pub swift_runtime_compatibility_version: String, + #[serde(rename = "librariesRequireRPath")] + pub libraries_require_rpath: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SwiftPaths { + pub runtime_library_paths: Vec, + pub runtime_library_import_paths: Vec, + pub runtime_resource_path: String, +} + +#[derive(Debug, Deserialize)] +pub struct SwiftTarget { + pub target: SwiftTargetInfo, + pub paths: SwiftPaths, +} + +const MACOS_TARGET_VERSION: &str = "10.15.7"; + +fn main() { + if cfg!(not(any(test, feature = "test-support"))) { + let swift_target = get_swift_target(); + + build_bridge(&swift_target); + link_swift_stdlib(&swift_target); + link_webrtc_framework(&swift_target); + + // Register exported Objective-C selectors, protocols, etc when building example binaries. + println!("cargo:rustc-link-arg=-Wl,-ObjC"); + } +} + +fn build_bridge(swift_target: &SwiftTarget) { + println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET"); + println!("cargo:rerun-if-changed={}/Sources", SWIFT_PACKAGE_NAME); + println!( + "cargo:rerun-if-changed={}/Package.swift", + SWIFT_PACKAGE_NAME + ); + println!( + "cargo:rerun-if-changed={}/Package.resolved", + SWIFT_PACKAGE_NAME + ); + + let swift_package_root = swift_package_root(); + let swift_target_folder = swift_target_folder(); + if !Command::new("swift") + .arg("build") + .arg("--disable-automatic-resolution") + .args(["--configuration", &env::var("PROFILE").unwrap()]) + .args(["--triple", &swift_target.target.triple]) + .args(["--build-path".into(), swift_target_folder]) + .current_dir(&swift_package_root) + .status() + .unwrap() + .success() + { + panic!( + "Failed to compile swift package in {}", + swift_package_root.display() + ); + } + + println!( + "cargo:rustc-link-search=native={}", + swift_target.out_dir_path().display() + ); + println!("cargo:rustc-link-lib=static={}", SWIFT_PACKAGE_NAME); +} + +fn link_swift_stdlib(swift_target: &SwiftTarget) { + for path in &swift_target.paths.runtime_library_paths { + println!("cargo:rustc-link-search=native={}", path); + } +} + +fn link_webrtc_framework(swift_target: &SwiftTarget) { + let swift_out_dir_path = swift_target.out_dir_path(); + println!("cargo:rustc-link-lib=framework=WebRTC"); + println!( + "cargo:rustc-link-search=framework={}", + swift_out_dir_path.display() + ); + // Find WebRTC.framework as a sibling of the executable when running tests. + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); + // Find WebRTC.framework in parent directory of the executable when running examples. + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/.."); + + let source_path = swift_out_dir_path.join("WebRTC.framework"); + let deps_dir_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../deps/WebRTC.framework"); + let target_dir_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../WebRTC.framework"); + copy_dir(&source_path, &deps_dir_path); + copy_dir(&source_path, &target_dir_path); +} + +fn get_swift_target() -> SwiftTarget { + let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + if arch == "aarch64" { + arch = "arm64".into(); + } + let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION); + + let swift_target_info_str = Command::new("swift") + .args(["-target", &target, "-print-target-info"]) + .output() + .unwrap() + .stdout; + + serde_json::from_slice(&swift_target_info_str).unwrap() +} + +fn swift_package_root() -> PathBuf { + env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME) +} + +fn swift_target_folder() -> PathBuf { + env::current_dir() + .unwrap() + .join(format!("../../target/{SWIFT_PACKAGE_NAME}")) +} + +fn copy_dir(source: &Path, destination: &Path) { + assert!( + Command::new("rm") + .arg("-rf") + .arg(destination) + .status() + .unwrap() + .success(), + "could not remove {:?} before copying", + destination + ); + + assert!( + Command::new("cp") + .arg("-R") + .args([source, destination]) + .status() + .unwrap() + .success(), + "could not copy {:?} to {:?}", + source, + destination + ); +} + +impl SwiftTarget { + fn out_dir_path(&self) -> PathBuf { + swift_target_folder() + .join(&self.target.unversioned_triple) + .join(env::var("PROFILE").unwrap()) + } +} diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs new file mode 100644 index 0000000000..2147f6ab8c --- /dev/null +++ b/crates/live_kit_client2/examples/test_app.rs @@ -0,0 +1,175 @@ +// use std::time::Duration; +// todo!() + +// use futures::StreamExt; +// use gpui2::{actions, keymap_matcher::Binding, Menu, MenuItem}; +// use live_kit_client2::{ +// LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, +// }; +// use live_kit_server::token::{self, VideoGrant}; +// use log::LevelFilter; +// use simplelog::SimpleLogger; + +// actions!(capture, [Quit]); + +fn main() { + // SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); + + // gpui2::App::new(()).unwrap().run(|cx| { + // #[cfg(any(test, feature = "test-support"))] + // println!("USING TEST LIVEKIT"); + + // #[cfg(not(any(test, feature = "test-support")))] + // println!("USING REAL LIVEKIT"); + + // cx.platform().activate(true); + // cx.add_global_action(quit); + + // cx.add_bindings([Binding::new("cmd-q", Quit, None)]); + // cx.set_menus(vec![Menu { + // name: "Zed", + // items: vec![MenuItem::Action { + // name: "Quit", + // action: Box::new(Quit), + // os_action: None, + // }], + // }]); + + // let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); + // let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); + // let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); + + // cx.spawn(|cx| async move { + // let user_a_token = token::create( + // &live_kit_key, + // &live_kit_secret, + // Some("test-participant-1"), + // VideoGrant::to_join("test-room"), + // ) + // .unwrap(); + // let room_a = Room::new(); + // room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); + + // let user2_token = token::create( + // &live_kit_key, + // &live_kit_secret, + // Some("test-participant-2"), + // VideoGrant::to_join("test-room"), + // ) + // .unwrap(); + // let room_b = Room::new(); + // room_b.connect(&live_kit_url, &user2_token).await.unwrap(); + + // let mut audio_track_updates = room_b.remote_audio_track_updates(); + // let audio_track = LocalAudioTrack::create(); + // let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); + + // if let RemoteAudioTrackUpdate::Subscribed(track, _) = + // audio_track_updates.next().await.unwrap() + // { + // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + // assert_eq!(remote_tracks.len(), 1); + // assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); + // assert_eq!(track.publisher_id(), "test-participant-1"); + // } else { + // panic!("unexpected message"); + // } + + // audio_track_publication.set_mute(true).await.unwrap(); + + // println!("waiting for mute changed!"); + // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + // audio_track_updates.next().await.unwrap() + // { + // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + // assert_eq!(remote_tracks[0].sid(), track_id); + // assert_eq!(muted, true); + // } else { + // panic!("unexpected message"); + // } + + // audio_track_publication.set_mute(false).await.unwrap(); + + // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + // audio_track_updates.next().await.unwrap() + // { + // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + // assert_eq!(remote_tracks[0].sid(), track_id); + // assert_eq!(muted, false); + // } else { + // panic!("unexpected message"); + // } + + // println!("Pausing for 5 seconds to test audio, make some noise!"); + // let timer = cx.background().timer(Duration::from_secs(5)); + // timer.await; + // let remote_audio_track = room_b + // .remote_audio_tracks("test-participant-1") + // .pop() + // .unwrap(); + // room_a.unpublish_track(audio_track_publication); + + // // Clear out any active speakers changed messages + // let mut next = audio_track_updates.next().await.unwrap(); + // while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { + // println!("Speakers changed: {:?}", speakers); + // next = audio_track_updates.next().await.unwrap(); + // } + + // if let RemoteAudioTrackUpdate::Unsubscribed { + // publisher_id, + // track_id, + // } = next + // { + // assert_eq!(publisher_id, "test-participant-1"); + // assert_eq!(remote_audio_track.sid(), track_id); + // assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); + // } else { + // panic!("unexpected message"); + // } + + // let mut video_track_updates = room_b.remote_video_track_updates(); + // let displays = room_a.display_sources().await.unwrap(); + // let display = displays.into_iter().next().unwrap(); + + // let local_video_track = LocalVideoTrack::screen_share_for_display(&display); + // let local_video_track_publication = + // room_a.publish_video_track(local_video_track).await.unwrap(); + + // if let RemoteVideoTrackUpdate::Subscribed(track) = + // video_track_updates.next().await.unwrap() + // { + // let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); + // assert_eq!(remote_video_tracks.len(), 1); + // assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); + // assert_eq!(track.publisher_id(), "test-participant-1"); + // } else { + // panic!("unexpected message"); + // } + + // let remote_video_track = room_b + // .remote_video_tracks("test-participant-1") + // .pop() + // .unwrap(); + // room_a.unpublish_track(local_video_track_publication); + // if let RemoteVideoTrackUpdate::Unsubscribed { + // publisher_id, + // track_id, + // } = video_track_updates.next().await.unwrap() + // { + // assert_eq!(publisher_id, "test-participant-1"); + // assert_eq!(remote_video_track.sid(), track_id); + // assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); + // } else { + // panic!("unexpected message"); + // } + + // cx.platform().quit(); + // }) + // .detach(); + // }); +} + +// fn quit(_: &Quit, cx: &mut gpui2::AppContext) { +// cx.platform().quit(); +// } diff --git a/crates/live_kit_client2/src/live_kit_client2.rs b/crates/live_kit_client2/src/live_kit_client2.rs new file mode 100644 index 0000000000..35682382e9 --- /dev/null +++ b/crates/live_kit_client2/src/live_kit_client2.rs @@ -0,0 +1,11 @@ +// #[cfg(not(any(test, feature = "test-support")))] +pub mod prod; + +// #[cfg(not(any(test, feature = "test-support")))] +pub use prod::*; + +// #[cfg(any(test, feature = "test-support"))] +// pub mod test; + +// #[cfg(any(test, feature = "test-support"))] +// pub use test::*; diff --git a/crates/live_kit_client2/src/prod.rs b/crates/live_kit_client2/src/prod.rs new file mode 100644 index 0000000000..65ed8b754f --- /dev/null +++ b/crates/live_kit_client2/src/prod.rs @@ -0,0 +1,943 @@ +use anyhow::{anyhow, Context, Result}; +use core_foundation::{ + array::{CFArray, CFArrayRef}, + base::{CFRelease, CFRetain, TCFType}, + string::{CFString, CFStringRef}, +}; +use futures::{ + channel::{mpsc, oneshot}, + Future, +}; +pub use media::core_video::CVImageBuffer; +use media::core_video::CVImageBufferRef; +use parking_lot::Mutex; +use postage::watch; +use std::{ + ffi::c_void, + sync::{Arc, Weak}, +}; + +// SAFETY: Most live kit types are threadsafe: +// https://github.com/livekit/client-sdk-swift#thread-safety +macro_rules! pointer_type { + ($pointer_name:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Debug)] + pub struct $pointer_name(pub *const std::ffi::c_void); + unsafe impl Send for $pointer_name {} + }; +} + +mod swift { + pointer_type!(Room); + pointer_type!(LocalAudioTrack); + pointer_type!(RemoteAudioTrack); + pointer_type!(LocalVideoTrack); + pointer_type!(RemoteVideoTrack); + pointer_type!(LocalTrackPublication); + pointer_type!(RemoteTrackPublication); + pointer_type!(MacOSDisplay); + pointer_type!(RoomDelegate); +} + +extern "C" { + fn LKRoomDelegateCreate( + callback_data: *mut c_void, + on_did_disconnect: extern "C" fn(callback_data: *mut c_void), + on_did_subscribe_to_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + remote_track: swift::RemoteAudioTrack, + remote_publication: swift::RemoteTrackPublication, + ), + on_did_unsubscribe_from_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ), + on_mute_changed_from_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + track_id: CFStringRef, + muted: bool, + ), + on_active_speakers_changed: extern "C" fn( + callback_data: *mut c_void, + participants: CFArrayRef, + ), + on_did_subscribe_to_remote_video_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + remote_track: swift::RemoteVideoTrack, + ), + on_did_unsubscribe_from_remote_video_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ), + ) -> swift::RoomDelegate; + + fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; + fn LKRoomConnect( + room: swift::Room, + url: CFStringRef, + token: CFStringRef, + callback: extern "C" fn(*mut c_void, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomDisconnect(room: swift::Room); + fn LKRoomPublishVideoTrack( + room: swift::Room, + track: swift::LocalVideoTrack, + callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomPublishAudioTrack( + room: swift::Room, + track: swift::LocalAudioTrack, + callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication); + + fn LKRoomAudioTracksForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKRoomAudioTrackPublicationsForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKRoomVideoTracksForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKVideoRendererCreate( + callback_data: *mut c_void, + on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool, + on_drop: extern "C" fn(callback_data: *mut c_void), + ) -> *const c_void; + + fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef; + fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); + fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef; + + fn LKDisplaySources( + callback_data: *mut c_void, + callback: extern "C" fn( + callback_data: *mut c_void, + sources: CFArrayRef, + error: CFStringRef, + ), + ); + fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack; + fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack; + + fn LKLocalTrackPublicationSetMute( + publication: swift::LocalTrackPublication, + muted: bool, + on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), + callback_data: *mut c_void, + ); + + fn LKRemoteTrackPublicationSetEnabled( + publication: swift::RemoteTrackPublication, + enabled: bool, + on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), + callback_data: *mut c_void, + ); + + fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; + fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; +} + +pub type Sid = String; + +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, +} + +pub struct Room { + native_room: Mutex, + connection: Mutex<( + watch::Sender, + watch::Receiver, + )>, + remote_audio_track_subscribers: Mutex>>, + remote_video_track_subscribers: Mutex>>, + _delegate: Mutex, +} + +trait AssertSendSync: Send {} +impl AssertSendSync for Room {} + +impl Room { + pub fn new() -> Arc { + Arc::new_cyclic(|weak_room| { + let delegate = RoomDelegate::new(weak_room.clone()); + Self { + native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }), + connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), + remote_audio_track_subscribers: Default::default(), + remote_video_track_subscribers: Default::default(), + _delegate: Mutex::new(delegate), + } + }) + } + + pub fn status(&self) -> watch::Receiver { + self.connection.lock().1.clone() + } + + pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { + let url = CFString::new(url); + let token = CFString::new(token); + let (did_connect, tx, rx) = Self::build_done_callback(); + unsafe { + LKRoomConnect( + *self.native_room.lock(), + url.as_concrete_TypeRef(), + token.as_concrete_TypeRef(), + did_connect, + tx, + ) + } + + let this = self.clone(); + let url = url.to_string(); + let token = token.to_string(); + async move { + rx.await.unwrap().context("error connecting to room")?; + *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) + } + } + + fn did_disconnect(&self) { + *self.connection.lock().0.borrow_mut() = ConnectionState::Disconnected; + } + + pub fn display_sources(self: &Arc) -> impl Future>> { + extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) { + unsafe { + let tx = Box::from_raw(tx as *mut oneshot::Sender>>); + + if sources.is_null() { + let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error)))); + } else { + let sources = CFArray::wrap_under_get_rule(sources) + .into_iter() + .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source))) + .collect(); + + let _ = tx.send(Ok(sources)); + } + } + } + + let (tx, rx) = oneshot::channel(); + + unsafe { + LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback); + } + + async move { rx.await.unwrap() } + } + + pub fn publish_video_track( + self: &Arc, + track: LocalVideoTrack, + ) -> impl Future> { + let (tx, rx) = oneshot::channel::>(); + extern "C" fn callback( + tx: *mut c_void, + publication: swift::LocalTrackPublication, + error: CFStringRef, + ) { + let tx = + unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(LocalTrackPublication::new(publication))); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + unsafe { + LKRoomPublishVideoTrack( + *self.native_room.lock(), + track.0, + callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ); + } + async { rx.await.unwrap().context("error publishing video track") } + } + + pub fn publish_audio_track( + self: &Arc, + track: LocalAudioTrack, + ) -> impl Future> { + let (tx, rx) = oneshot::channel::>(); + extern "C" fn callback( + tx: *mut c_void, + publication: swift::LocalTrackPublication, + error: CFStringRef, + ) { + let tx = + unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(LocalTrackPublication::new(publication))); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + unsafe { + LKRoomPublishAudioTrack( + *self.native_room.lock(), + track.0, + callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ); + } + async { rx.await.unwrap().context("error publishing audio track") } + } + + pub fn unpublish_track(&self, publication: LocalTrackPublication) { + unsafe { + LKRoomUnpublishTrack(*self.native_room.lock(), publication.0); + } + } + + pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { + unsafe { + let tracks = LKRoomVideoTracksForRemoteParticipant( + *self.native_room.lock(), + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track| { + let native_track = swift::RemoteVideoTrack(*native_track); + let id = + CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track)) + .to_string(); + Arc::new(RemoteVideoTrack::new( + native_track, + id, + participant_id.into(), + )) + }) + .collect() + } + } + } + + pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { + unsafe { + let tracks = LKRoomAudioTracksForRemoteParticipant( + *self.native_room.lock(), + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track| { + let native_track = swift::RemoteAudioTrack(*native_track); + let id = + CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track)) + .to_string(); + Arc::new(RemoteAudioTrack::new( + native_track, + id, + participant_id.into(), + )) + }) + .collect() + } + } + } + + pub fn remote_audio_track_publications( + &self, + participant_id: &str, + ) -> Vec> { + unsafe { + let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( + *self.native_room.lock(), + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track_publication| { + let native_track_publication = + swift::RemoteTrackPublication(*native_track_publication); + Arc::new(RemoteTrackPublication::new(native_track_publication)) + }) + .collect() + } + } + } + + pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + self.remote_audio_track_subscribers.lock().push(tx); + rx + } + + pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + self.remote_video_track_subscribers.lock().push(tx); + rx + } + + fn did_subscribe_to_remote_audio_track( + &self, + track: RemoteAudioTrack, + publication: RemoteTrackPublication, + ) { + let track = Arc::new(track); + let publication = Arc::new(publication); + self.remote_audio_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) + .is_ok() + }); + } + + fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { + self.remote_audio_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed { + publisher_id: publisher_id.clone(), + track_id: track_id.clone(), + }) + .is_ok() + }); + } + + fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { + self.remote_audio_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged { + track_id: track_id.clone(), + muted, + }) + .is_ok() + }); + } + + // A vec of publisher IDs + fn active_speakers_changed(&self, speakers: Vec) { + self.remote_audio_track_subscribers + .lock() + .retain(move |tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged { + speakers: speakers.clone(), + }) + .is_ok() + }); + } + + fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { + let track = Arc::new(track); + self.remote_video_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .is_ok() + }); + } + + fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { + self.remote_video_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed { + publisher_id: publisher_id.clone(), + track_id: track_id.clone(), + }) + .is_ok() + }); + } + + fn build_done_callback() -> ( + extern "C" fn(*mut c_void, CFStringRef), + *mut c_void, + oneshot::Receiver>, + ) { + let (tx, rx) = oneshot::channel(); + extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(())); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + ( + done_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + rx, + ) + } +} + +impl Drop for Room { + fn drop(&mut self) { + unsafe { + let native_room = &*self.native_room.lock(); + LKRoomDisconnect(*native_room); + CFRelease(native_room.0); + } + } +} + +struct RoomDelegate { + native_delegate: swift::RoomDelegate, + _weak_room: Weak, +} + +impl RoomDelegate { + fn new(weak_room: Weak) -> Self { + let native_delegate = unsafe { + LKRoomDelegateCreate( + weak_room.as_ptr() as *mut c_void, + Self::on_did_disconnect, + Self::on_did_subscribe_to_remote_audio_track, + Self::on_did_unsubscribe_from_remote_audio_track, + Self::on_mute_change_from_remote_audio_track, + Self::on_active_speakers_changed, + Self::on_did_subscribe_to_remote_video_track, + Self::on_did_unsubscribe_from_remote_video_track, + ) + }; + Self { + native_delegate, + _weak_room: weak_room, + } + } + + extern "C" fn on_did_disconnect(room: *mut c_void) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + room.did_disconnect(); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_subscribe_to_remote_audio_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + track: swift::RemoteAudioTrack, + publication: swift::RemoteTrackPublication, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + let track = RemoteAudioTrack::new(track, track_id, publisher_id); + let publication = RemoteTrackPublication::new(publication); + if let Some(room) = room.upgrade() { + room.did_subscribe_to_remote_audio_track(track, publication); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_unsubscribe_from_remote_audio_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_mute_change_from_remote_audio_track( + room: *mut c_void, + track_id: CFStringRef, + muted: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.mute_changed_from_remote_audio_track(track_id, muted); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) { + if participants.is_null() { + return; + } + + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let speakers = unsafe { + CFArray::wrap_under_get_rule(participants) + .into_iter() + .map( + |speaker: core_foundation::base::ItemRef<'_, *const c_void>| { + CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string() + }, + ) + .collect() + }; + + if let Some(room) = room.upgrade() { + room.active_speakers_changed(speakers); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_subscribe_to_remote_video_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + track: swift::RemoteVideoTrack, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + let track = RemoteVideoTrack::new(track, track_id, publisher_id); + if let Some(room) = room.upgrade() { + room.did_subscribe_to_remote_video_track(track); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_unsubscribe_from_remote_video_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.did_unsubscribe_from_remote_video_track(publisher_id, track_id); + } + let _ = Weak::into_raw(room); + } +} + +impl Drop for RoomDelegate { + fn drop(&mut self) { + unsafe { + CFRelease(self.native_delegate.0); + } + } +} + +pub struct LocalAudioTrack(swift::LocalAudioTrack); + +impl LocalAudioTrack { + pub fn create() -> Self { + Self(unsafe { LKLocalAudioTrackCreateTrack() }) + } +} + +impl Drop for LocalAudioTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct LocalVideoTrack(swift::LocalVideoTrack); + +impl LocalVideoTrack { + pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { + Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) }) + } +} + +impl Drop for LocalVideoTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct LocalTrackPublication(swift::LocalTrackPublication); + +impl LocalTrackPublication { + pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self { + unsafe { + CFRetain(native_track_publication.0); + } + Self(native_track_publication) + } + + pub fn set_mute(&self, muted: bool) -> impl Future> { + let (tx, rx) = futures::channel::oneshot::channel(); + + extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; + if error.is_null() { + tx.send(Ok(())).ok(); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + tx.send(Err(anyhow!(error))).ok(); + } + } + + unsafe { + LKLocalTrackPublicationSetMute( + self.0, + muted, + complete_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ) + } + + async move { rx.await.unwrap() } + } +} + +impl Drop for LocalTrackPublication { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct RemoteTrackPublication { + native_publication: Mutex, +} + +impl RemoteTrackPublication { + pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { + unsafe { + CFRetain(native_track_publication.0); + } + Self { + native_publication: Mutex::new(native_track_publication), + } + } + + pub fn sid(&self) -> String { + unsafe { + CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid( + *self.native_publication.lock(), + )) + .to_string() + } + } + + pub fn is_muted(&self) -> bool { + unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) } + } + + pub fn set_enabled(&self, enabled: bool) -> impl Future> { + let (tx, rx) = futures::channel::oneshot::channel(); + + extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; + if error.is_null() { + tx.send(Ok(())).ok(); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + tx.send(Err(anyhow!(error))).ok(); + } + } + + unsafe { + LKRemoteTrackPublicationSetEnabled( + *self.native_publication.lock(), + enabled, + complete_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ) + } + + async move { rx.await.unwrap() } + } +} + +impl Drop for RemoteTrackPublication { + fn drop(&mut self) { + unsafe { CFRelease((*self.native_publication.lock()).0) } + } +} + +#[derive(Debug)] +pub struct RemoteAudioTrack { + native_track: Mutex, + sid: Sid, + publisher_id: String, +} + +impl RemoteAudioTrack { + fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self { + unsafe { + CFRetain(native_track.0); + } + Self { + native_track: Mutex::new(native_track), + sid, + publisher_id, + } + } + + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn enable(&self) -> impl Future> { + async { Ok(()) } + } + + pub fn disable(&self) -> impl Future> { + async { Ok(()) } + } +} + +impl Drop for RemoteAudioTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.native_track.lock().0) } + } +} + +#[derive(Debug)] +pub struct RemoteVideoTrack { + native_track: Mutex, + sid: Sid, + publisher_id: String, +} + +impl RemoteVideoTrack { + fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self { + unsafe { + CFRetain(native_track.0); + } + Self { + native_track: Mutex::new(native_track), + sid, + publisher_id, + } + } + + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn frames(&self) -> async_broadcast::Receiver { + extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool { + unsafe { + let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender); + let buffer = CVImageBuffer::wrap_under_get_rule(frame); + let result = tx.try_broadcast(Frame(buffer)); + let _ = Box::into_raw(tx); + match result { + Ok(_) => true, + Err(async_broadcast::TrySendError::Closed(_)) + | Err(async_broadcast::TrySendError::Inactive(_)) => { + log::warn!("no active receiver for frame"); + false + } + Err(async_broadcast::TrySendError::Full(_)) => { + log::warn!("skipping frame as receiver is not keeping up"); + true + } + } + } + } + + extern "C" fn on_drop(callback_data: *mut c_void) { + unsafe { + let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender); + } + } + + let (tx, rx) = async_broadcast::broadcast(64); + unsafe { + let renderer = LKVideoRendererCreate( + Box::into_raw(Box::new(tx)) as *mut c_void, + on_frame, + on_drop, + ); + LKVideoTrackAddRenderer(*self.native_track.lock(), renderer); + rx + } + } +} + +impl Drop for RemoteVideoTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.native_track.lock().0) } + } +} + +pub enum RemoteVideoTrackUpdate { + Subscribed(Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +pub enum RemoteAudioTrackUpdate { + ActiveSpeakersChanged { speakers: Vec }, + MuteChanged { track_id: Sid, muted: bool }, + Subscribed(Arc, Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +pub struct MacOSDisplay(swift::MacOSDisplay); + +impl MacOSDisplay { + fn new(ptr: swift::MacOSDisplay) -> Self { + unsafe { + CFRetain(ptr.0); + } + Self(ptr) + } +} + +impl Drop for MacOSDisplay { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +#[derive(Clone)] +pub struct Frame(CVImageBuffer); + +impl Frame { + pub fn width(&self) -> usize { + self.0.width() + } + + pub fn height(&self) -> usize { + self.0.height() + } + + pub fn image(&self) -> CVImageBuffer { + self.0.clone() + } +} diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs new file mode 100644 index 0000000000..7185c11fa8 --- /dev/null +++ b/crates/live_kit_client2/src/test.rs @@ -0,0 +1,647 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use collections::{BTreeMap, HashMap}; +use futures::Stream; +use gpui2::Executor; +use live_kit_server::token; +use media::core_video::CVImageBuffer; +use parking_lot::Mutex; +use postage::watch; +use std::{future::Future, mem, sync::Arc}; + +static SERVERS: Mutex>> = Mutex::new(BTreeMap::new()); + +pub struct TestServer { + pub url: String, + pub api_key: String, + pub secret_key: String, + rooms: Mutex>, + executor: Arc, +} + +impl TestServer { + pub fn create( + url: String, + api_key: String, + secret_key: String, + executor: Arc, + ) -> Result> { + let mut servers = SERVERS.lock(); + if servers.contains_key(&url) { + Err(anyhow!("a server with url {:?} already exists", url)) + } else { + let server = Arc::new(TestServer { + url: url.clone(), + api_key, + secret_key, + rooms: Default::default(), + executor, + }); + servers.insert(url, server.clone()); + Ok(server) + } + } + + fn get(url: &str) -> Result> { + Ok(SERVERS + .lock() + .get(url) + .ok_or_else(|| anyhow!("no server found for url"))? + .clone()) + } + + pub fn teardown(&self) -> Result<()> { + SERVERS + .lock() + .remove(&self.url) + .ok_or_else(|| anyhow!("server with url {:?} does not exist", self.url))?; + Ok(()) + } + + pub fn create_api_client(&self) -> TestApiClient { + TestApiClient { + url: self.url.clone(), + } + } + + pub async fn create_room(&self, room: String) -> Result<()> { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + if server_rooms.contains_key(&room) { + Err(anyhow!("room {:?} already exists", room)) + } else { + server_rooms.insert(room, Default::default()); + Ok(()) + } + } + + async fn delete_room(&self, room: String) -> Result<()> { + // TODO: clear state associated with all `Room`s. + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + server_rooms + .remove(&room) + .ok_or_else(|| anyhow!("room {:?} does not exist", room))?; + Ok(()) + } + + async fn join_room(&self, token: String, client_room: Arc) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + let mut server_rooms = self.rooms.lock(); + let room = (*server_rooms).entry(room_name.to_string()).or_default(); + + if room.client_rooms.contains_key(&identity) { + Err(anyhow!( + "{:?} attempted to join room {:?} twice", + identity, + room_name + )) + } else { + for track in &room.video_tracks { + client_room + .0 + .lock() + .video_track_updates + .0 + .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .unwrap(); + } + room.client_rooms.insert(identity, client_room); + Ok(()) + } + } + + async fn leave_room(&self, token: String) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.client_rooms.remove(&identity).ok_or_else(|| { + anyhow!( + "{:?} attempted to leave room {:?} before joining it", + identity, + room_name + ) + })?; + Ok(()) + } + + async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> { + // TODO: clear state associated with the `Room`. + + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.client_rooms.remove(&identity).ok_or_else(|| { + anyhow!( + "participant {:?} did not join room {:?}", + identity, + room_name + ) + })?; + Ok(()) + } + + pub async fn disconnect_client(&self, client_identity: String) { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + for room in server_rooms.values_mut() { + if let Some(room) = room.client_rooms.remove(&client_identity) { + *room.0.lock().connection.0.borrow_mut() = ConnectionState::Disconnected; + } + } + } + + async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + + let track = Arc::new(RemoteVideoTrack { + sid: nanoid::nanoid!(17), + publisher_id: identity.clone(), + frames_rx: local_track.frames_rx.clone(), + }); + + room.video_tracks.push(track.clone()); + + for (id, client_room) in &room.client_rooms { + if *id != identity { + let _ = client_room + .0 + .lock() + .video_track_updates + .0 + .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .unwrap(); + } + } + + Ok(()) + } + + async fn publish_audio_track( + &self, + token: String, + _local_track: &LocalAudioTrack, + ) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + + let track = Arc::new(RemoteAudioTrack { + sid: nanoid::nanoid!(17), + publisher_id: identity.clone(), + }); + + let publication = Arc::new(RemoteTrackPublication); + + room.audio_tracks.push(track.clone()); + + for (id, client_room) in &room.client_rooms { + if *id != identity { + let _ = client_room + .0 + .lock() + .audio_track_updates + .0 + .try_broadcast(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) + .unwrap(); + } + } + + Ok(()) + } + + fn video_tracks(&self, token: String) -> Result>> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + Ok(room.video_tracks.clone()) + } + + fn audio_tracks(&self, token: String) -> Result>> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + Ok(room.audio_tracks.clone()) + } +} + +#[derive(Default)] +struct TestServerRoom { + client_rooms: HashMap>, + video_tracks: Vec>, + audio_tracks: Vec>, +} + +impl TestServerRoom {} + +pub struct TestApiClient { + url: String, +} + +#[async_trait] +impl live_kit_server::api::Client for TestApiClient { + fn url(&self) -> &str { + &self.url + } + + async fn create_room(&self, name: String) -> Result<()> { + let server = TestServer::get(&self.url)?; + server.create_room(name).await?; + Ok(()) + } + + async fn delete_room(&self, name: String) -> Result<()> { + let server = TestServer::get(&self.url)?; + server.delete_room(name).await?; + Ok(()) + } + + async fn remove_participant(&self, room: String, identity: String) -> Result<()> { + let server = TestServer::get(&self.url)?; + server.remove_participant(room, identity).await?; + Ok(()) + } + + fn room_token(&self, room: &str, identity: &str) -> Result { + let server = TestServer::get(&self.url)?; + token::create( + &server.api_key, + &server.secret_key, + Some(identity), + token::VideoGrant::to_join(room), + ) + } + + fn guest_token(&self, room: &str, identity: &str) -> Result { + let server = TestServer::get(&self.url)?; + token::create( + &server.api_key, + &server.secret_key, + Some(identity), + token::VideoGrant::for_guest(room), + ) + } +} + +pub type Sid = String; + +struct RoomState { + connection: ( + watch::Sender, + watch::Receiver, + ), + display_sources: Vec, + audio_track_updates: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), + video_track_updates: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), +} + +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, +} + +pub struct Room(Mutex); + +impl Room { + pub fn new() -> Arc { + Arc::new(Self(Mutex::new(RoomState { + connection: watch::channel_with(ConnectionState::Disconnected), + display_sources: Default::default(), + video_track_updates: async_broadcast::broadcast(128), + audio_track_updates: async_broadcast::broadcast(128), + }))) + } + + pub fn status(&self) -> watch::Receiver { + self.0.lock().connection.1.clone() + } + + pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { + let this = self.clone(); + let url = url.to_string(); + let token = token.to_string(); + async move { + let server = TestServer::get(&url)?; + server.join_room(token.clone(), this.clone()).await?; + *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) + } + } + + pub fn display_sources(self: &Arc) -> impl Future>> { + let this = self.clone(); + async move { + let server = this.test_server(); + server.executor.simulate_random_delay().await; + Ok(this.0.lock().display_sources.clone()) + } + } + + pub fn publish_video_track( + self: &Arc, + track: LocalVideoTrack, + ) -> impl Future> { + let this = self.clone(); + let track = track.clone(); + async move { + this.test_server() + .publish_video_track(this.token(), track) + .await?; + Ok(LocalTrackPublication) + } + } + pub fn publish_audio_track( + self: &Arc, + track: LocalAudioTrack, + ) -> impl Future> { + let this = self.clone(); + let track = track.clone(); + async move { + this.test_server() + .publish_audio_track(this.token(), &track) + .await?; + Ok(LocalTrackPublication) + } + } + + pub fn unpublish_track(&self, _publication: LocalTrackPublication) {} + + pub fn remote_audio_tracks(&self, publisher_id: &str) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .audio_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .collect() + } + + pub fn remote_audio_track_publications( + &self, + publisher_id: &str, + ) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .audio_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .map(|_track| Arc::new(RemoteTrackPublication {})) + .collect() + } + + pub fn remote_video_tracks(&self, publisher_id: &str) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .video_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .collect() + } + + pub fn remote_audio_track_updates(&self) -> impl Stream { + self.0.lock().audio_track_updates.1.clone() + } + + pub fn remote_video_track_updates(&self) -> impl Stream { + self.0.lock().video_track_updates.1.clone() + } + + pub fn set_display_sources(&self, sources: Vec) { + self.0.lock().display_sources = sources; + } + + fn test_server(&self) -> Arc { + match self.0.lock().connection.1.borrow().clone() { + ConnectionState::Disconnected => panic!("must be connected to call this method"), + ConnectionState::Connected { url, .. } => TestServer::get(&url).unwrap(), + } + } + + fn token(&self) -> String { + match self.0.lock().connection.1.borrow().clone() { + ConnectionState::Disconnected => panic!("must be connected to call this method"), + ConnectionState::Connected { token, .. } => token, + } + } + + fn is_connected(&self) -> bool { + match *self.0.lock().connection.1.borrow() { + ConnectionState::Disconnected => false, + ConnectionState::Connected { .. } => true, + } + } +} + +impl Drop for Room { + fn drop(&mut self) { + if let ConnectionState::Connected { token, .. } = mem::replace( + &mut *self.0.lock().connection.0.borrow_mut(), + ConnectionState::Disconnected, + ) { + if let Ok(server) = TestServer::get(&token) { + let executor = server.executor.clone(); + executor + .spawn(async move { server.leave_room(token).await.unwrap() }) + .detach(); + } + } + } +} + +pub struct LocalTrackPublication; + +impl LocalTrackPublication { + pub fn set_mute(&self, _mute: bool) -> impl Future> { + async { Ok(()) } + } +} + +pub struct RemoteTrackPublication; + +impl RemoteTrackPublication { + pub fn set_enabled(&self, _enabled: bool) -> impl Future> { + async { Ok(()) } + } + + pub fn is_muted(&self) -> bool { + false + } + + pub fn sid(&self) -> String { + "".to_string() + } +} + +#[derive(Clone)] +pub struct LocalVideoTrack { + frames_rx: async_broadcast::Receiver, +} + +impl LocalVideoTrack { + pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { + Self { + frames_rx: display.frames.1.clone(), + } + } +} + +#[derive(Clone)] +pub struct LocalAudioTrack; + +impl LocalAudioTrack { + pub fn create() -> Self { + Self + } +} + +pub struct RemoteVideoTrack { + sid: Sid, + publisher_id: Sid, + frames_rx: async_broadcast::Receiver, +} + +impl RemoteVideoTrack { + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn frames(&self) -> async_broadcast::Receiver { + self.frames_rx.clone() + } +} + +#[derive(Debug)] +pub struct RemoteAudioTrack { + sid: Sid, + publisher_id: Sid, +} + +impl RemoteAudioTrack { + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn enable(&self) -> impl Future> { + async { Ok(()) } + } + + pub fn disable(&self) -> impl Future> { + async { Ok(()) } + } +} + +#[derive(Clone)] +pub enum RemoteVideoTrackUpdate { + Subscribed(Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +#[derive(Clone)] +pub enum RemoteAudioTrackUpdate { + ActiveSpeakersChanged { speakers: Vec }, + MuteChanged { track_id: Sid, muted: bool }, + Subscribed(Arc, Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +#[derive(Clone)] +pub struct MacOSDisplay { + frames: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), +} + +impl MacOSDisplay { + pub fn new() -> Self { + Self { + frames: async_broadcast::broadcast(128), + } + } + + pub fn send_frame(&self, frame: Frame) { + self.frames.0.try_broadcast(frame).unwrap(); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Frame { + pub label: String, + pub width: usize, + pub height: usize, +} + +impl Frame { + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn image(&self) -> CVImageBuffer { + unimplemented!("you can't call this in test mode") + } +} From 0ecf6bde732a8174e89d9f807474ce3c59423b46 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 16:15:30 -0600 Subject: [PATCH 036/156] 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 795369a1e3d08795ad3cdc6a8a41413c90e2e484 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 31 Oct 2023 18:10:23 -0400 Subject: [PATCH 037/156] Port `multi_buffer` to gpui2 --- Cargo.lock | 47 + Cargo.toml | 1 + crates/gpui2/src/subscription.rs | 2 +- crates/multi_buffer2/Cargo.toml | 78 + crates/multi_buffer2/src/anchor.rs | 138 + crates/multi_buffer2/src/multi_buffer2.rs | 5393 +++++++++++++++++++++ 6 files changed, 5658 insertions(+), 1 deletion(-) create mode 100644 crates/multi_buffer2/Cargo.toml create mode 100644 crates/multi_buffer2/src/anchor.rs create mode 100644 crates/multi_buffer2/src/multi_buffer2.rs diff --git a/Cargo.lock b/Cargo.lock index 3aca27106c..7b1a259e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5035,6 +5035,53 @@ dependencies = [ "workspace", ] +[[package]] +name = "multi_buffer2" +version = "0.1.0" +dependencies = [ + "aho-corasick", + "anyhow", + "client2", + "clock", + "collections", + "convert_case 0.6.0", + "copilot2", + "ctor", + "env_logger 0.9.3", + "futures 0.3.28", + "git", + "gpui2", + "indoc", + "itertools 0.10.5", + "language2", + "lazy_static", + "log", + "lsp2", + "ordered-float 2.10.0", + "parking_lot 0.11.2", + "postage", + "project2", + "pulldown-cmark", + "rand 0.8.5", + "rich_text", + "schemars", + "serde", + "serde_derive", + "settings2", + "smallvec", + "smol", + "snippet", + "sum_tree", + "text", + "theme2", + "tree-sitter", + "tree-sitter-html", + "tree-sitter-rust", + "tree-sitter-typescript", + "unindent", + "util", +] + [[package]] name = "multimap" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index ac490ce935..60742b7416 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ "crates/menu", "crates/menu2", "crates/multi_buffer", + "crates/multi_buffer2", "crates/node_runtime", "crates/notifications", "crates/outline", diff --git a/crates/gpui2/src/subscription.rs b/crates/gpui2/src/subscription.rs index 3bf28792bb..c2799d2fe6 100644 --- a/crates/gpui2/src/subscription.rs +++ b/crates/gpui2/src/subscription.rs @@ -47,8 +47,8 @@ where subscribers.remove(&subscriber_id); if subscribers.is_empty() { lock.subscribers.remove(&emitter_key); - return; } + return; } // We didn't manage to remove the subscription, which means it was dropped diff --git a/crates/multi_buffer2/Cargo.toml b/crates/multi_buffer2/Cargo.toml new file mode 100644 index 0000000000..4c56bab9dc --- /dev/null +++ b/crates/multi_buffer2/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "multi_buffer2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/multi_buffer2.rs" +doctest = false + +[features] +test-support = [ + "copilot2/test-support", + "text/test-support", + "language2/test-support", + "gpui2/test-support", + "util/test-support", + "tree-sitter-rust", + "tree-sitter-typescript" +] + +[dependencies] +client2 = { path = "../client2" } +clock = { path = "../clock" } +collections = { path = "../collections" } +git = { path = "../git" } +gpui2 = { path = "../gpui2" } +language2 = { path = "../language2" } +lsp2 = { path = "../lsp2" } +rich_text = { path = "../rich_text" } +settings2 = { path = "../settings2" } +snippet = { path = "../snippet" } +sum_tree = { path = "../sum_tree" } +text = { path = "../text" } +theme2 = { path = "../theme2" } +util = { path = "../util" } + +aho-corasick = "1.1" +anyhow.workspace = true +convert_case = "0.6.0" +futures.workspace = true +indoc = "1.0.4" +itertools = "0.10" +lazy_static.workspace = true +log.workspace = true +ordered-float.workspace = true +parking_lot.workspace = true +postage.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } +rand.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true +smallvec.workspace = true +smol.workspace = true + +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-html = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } + +[dev-dependencies] +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"] } +util = { path = "../util", features = ["test-support"] } +project2 = { path = "../project2", features = ["test-support"] } +settings2 = { path = "../settings2", features = ["test-support"] } + +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true +unindent.workspace = true +tree-sitter.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-html.workspace = true +tree-sitter-typescript.workspace = true diff --git a/crates/multi_buffer2/src/anchor.rs b/crates/multi_buffer2/src/anchor.rs new file mode 100644 index 0000000000..fa65bfc800 --- /dev/null +++ b/crates/multi_buffer2/src/anchor.rs @@ -0,0 +1,138 @@ +use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint}; +use language2::{OffsetUtf16, Point, TextDimension}; +use std::{ + cmp::Ordering, + ops::{Range, Sub}, +}; +use sum_tree::Bias; + +#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] +pub struct Anchor { + pub buffer_id: Option, + pub excerpt_id: ExcerptId, + pub text_anchor: text::Anchor, +} + +impl Anchor { + pub fn min() -> Self { + Self { + buffer_id: None, + excerpt_id: ExcerptId::min(), + text_anchor: text::Anchor::MIN, + } + } + + pub fn max() -> Self { + Self { + buffer_id: None, + excerpt_id: ExcerptId::max(), + text_anchor: text::Anchor::MAX, + } + } + + pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering { + let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot); + if excerpt_id_cmp.is_eq() { + if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() { + Ordering::Equal + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer) + } else { + Ordering::Equal + } + } else { + excerpt_id_cmp + } + } + + pub fn bias(&self) -> Bias { + self.text_anchor.bias + } + + pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { + if self.text_anchor.bias != Bias::Left { + if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_left(&excerpt.buffer), + }; + } + } + self.clone() + } + + pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { + if self.text_anchor.bias != Bias::Right { + if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_right(&excerpt.buffer), + }; + } + } + self.clone() + } + + pub fn summary(&self, snapshot: &MultiBufferSnapshot) -> D + where + D: TextDimension + Ord + Sub, + { + snapshot.summary_for_anchor(self) + } + + pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool { + if *self == Anchor::min() || *self == Anchor::max() { + true + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + excerpt.contains(self) + && (self.text_anchor == excerpt.range.context.start + || self.text_anchor == excerpt.range.context.end + || self.text_anchor.is_valid(&excerpt.buffer)) + } else { + false + } + } +} + +impl ToOffset for Anchor { + fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize { + self.summary(snapshot) + } +} + +impl ToOffsetUtf16 for Anchor { + fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { + self.summary(snapshot) + } +} + +impl ToPoint for Anchor { + fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point { + self.summary(snapshot) + } +} + +pub trait AnchorRangeExt { + fn cmp(&self, b: &Range, buffer: &MultiBufferSnapshot) -> Ordering; + fn to_offset(&self, content: &MultiBufferSnapshot) -> Range; + fn to_point(&self, content: &MultiBufferSnapshot) -> Range; +} + +impl AnchorRangeExt for Range { + fn cmp(&self, other: &Range, buffer: &MultiBufferSnapshot) -> Ordering { + match self.start.cmp(&other.start, buffer) { + Ordering::Equal => other.end.cmp(&self.end, buffer), + ord => ord, + } + } + + fn to_offset(&self, content: &MultiBufferSnapshot) -> Range { + self.start.to_offset(content)..self.end.to_offset(content) + } + + fn to_point(&self, content: &MultiBufferSnapshot) -> Range { + self.start.to_point(content)..self.end.to_point(content) + } +} diff --git a/crates/multi_buffer2/src/multi_buffer2.rs b/crates/multi_buffer2/src/multi_buffer2.rs new file mode 100644 index 0000000000..c5827b8b13 --- /dev/null +++ b/crates/multi_buffer2/src/multi_buffer2.rs @@ -0,0 +1,5393 @@ +mod anchor; + +pub use anchor::{Anchor, AnchorRangeExt}; +use anyhow::{anyhow, Result}; +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::{ + char_kind, + language_settings::{language_settings, LanguageSettings}, + AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, + DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, + Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, + ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, +}; +use std::{ + borrow::Cow, + cell::{Ref, RefCell}, + cmp, fmt, + future::Future, + io, + iter::{self, FromIterator}, + mem, + ops::{Range, RangeBounds, Sub}, + str, + sync::Arc, + time::{Duration, Instant}, +}; +use sum_tree::{Bias, Cursor, SumTree}; +use text::{ + locator::Locator, + subscription::{Subscription, Topic}, + Edit, TextSummary, +}; +use theme2::SyntaxTheme; +use util::post_inc; + +#[cfg(any(test, feature = "test-support"))] +use gpui2::Context; + +const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; + +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ExcerptId(usize); + +pub struct MultiBuffer { + snapshot: RefCell, + buffers: RefCell>, + next_excerpt_id: usize, + subscriptions: Topic, + singleton: bool, + replica_id: ReplicaId, + history: History, + title: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Event { + ExcerptsAdded { + buffer: Model, + predecessor: ExcerptId, + excerpts: Vec<(ExcerptId, ExcerptRange)>, + }, + ExcerptsRemoved { + ids: Vec, + }, + ExcerptsEdited { + ids: Vec, + }, + Edited { + sigleton_buffer_edited: bool, + }, + TransactionUndone { + transaction_id: TransactionId, + }, + Reloaded, + DiffBaseChanged, + LanguageChanged, + Reparsed, + Saved, + FileHandleChanged, + Closed, + DirtyChanged, + DiagnosticsUpdated, +} + +#[derive(Clone)] +struct History { + next_transaction_id: TransactionId, + undo_stack: Vec, + redo_stack: Vec, + transaction_depth: usize, + group_interval: Duration, +} + +#[derive(Clone)] +struct Transaction { + id: TransactionId, + buffer_transactions: HashMap, + first_edit_at: Instant, + last_edit_at: Instant, + suppress_grouping: bool, +} + +pub trait ToOffset: 'static + fmt::Debug { + fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; +} + +pub trait ToOffsetUtf16: 'static + fmt::Debug { + fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16; +} + +pub trait ToPoint: 'static + fmt::Debug { + fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point; +} + +pub trait ToPointUtf16: 'static + fmt::Debug { + fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16; +} + +struct BufferState { + buffer: Model, + last_version: clock::Global, + last_parse_count: usize, + last_selections_update_count: usize, + last_diagnostics_update_count: usize, + last_file_update_count: usize, + last_git_diff_update_count: usize, + excerpts: Vec, + _subscriptions: [gpui2::Subscription; 2], +} + +#[derive(Clone, Default)] +pub struct MultiBufferSnapshot { + singleton: bool, + excerpts: SumTree, + excerpt_ids: SumTree, + parse_count: usize, + diagnostics_update_count: usize, + trailing_excerpt_update_count: usize, + git_diff_update_count: usize, + edit_count: usize, + is_dirty: bool, + has_conflict: bool, +} + +pub struct ExcerptBoundary { + pub id: ExcerptId, + pub row: u32, + pub buffer: BufferSnapshot, + pub range: ExcerptRange, + pub starts_new_buffer: bool, +} + +#[derive(Clone)] +struct Excerpt { + id: ExcerptId, + locator: Locator, + buffer_id: u64, + buffer: BufferSnapshot, + range: ExcerptRange, + max_buffer_row: u32, + text_summary: TextSummary, + has_trailing_newline: bool, +} + +#[derive(Clone, Debug)] +struct ExcerptIdMapping { + id: ExcerptId, + locator: Locator, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ExcerptRange { + pub context: Range, + pub primary: Option>, +} + +#[derive(Clone, Debug, Default)] +struct ExcerptSummary { + excerpt_id: ExcerptId, + excerpt_locator: Locator, + max_buffer_row: u32, + text: TextSummary, +} + +#[derive(Clone)] +pub struct MultiBufferRows<'a> { + buffer_row_range: Range, + excerpts: Cursor<'a, Excerpt, Point>, +} + +pub struct MultiBufferChunks<'a> { + range: Range, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_chunks: Option>, + language_aware: bool, +} + +pub struct MultiBufferBytes<'a> { + range: Range, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_bytes: Option>, + chunk: &'a [u8], +} + +pub struct ReversedMultiBufferBytes<'a> { + range: Range, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_bytes: Option>, + chunk: &'a [u8], +} + +struct ExcerptChunks<'a> { + content_chunks: BufferChunks<'a>, + footer_height: usize, +} + +struct ExcerptBytes<'a> { + content_bytes: text::Bytes<'a>, + footer_height: usize, +} + +impl MultiBuffer { + pub fn new(replica_id: ReplicaId) -> Self { + Self { + snapshot: Default::default(), + buffers: Default::default(), + next_excerpt_id: 1, + subscriptions: Default::default(), + singleton: false, + replica_id, + history: History { + next_transaction_id: Default::default(), + undo_stack: Default::default(), + redo_stack: Default::default(), + transaction_depth: 0, + group_interval: Duration::from_millis(300), + }, + title: Default::default(), + } + } + + pub fn clone(&self, new_cx: &mut ModelContext) -> Self { + let mut buffers = HashMap::default(); + for (buffer_id, buffer_state) in self.buffers.borrow().iter() { + buffers.insert( + *buffer_id, + BufferState { + buffer: buffer_state.buffer.clone(), + last_version: buffer_state.last_version.clone(), + last_parse_count: buffer_state.last_parse_count, + last_selections_update_count: buffer_state.last_selections_update_count, + last_diagnostics_update_count: buffer_state.last_diagnostics_update_count, + last_file_update_count: buffer_state.last_file_update_count, + last_git_diff_update_count: buffer_state.last_git_diff_update_count, + excerpts: buffer_state.excerpts.clone(), + _subscriptions: [ + new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()), + new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event), + ], + }, + ); + } + Self { + snapshot: RefCell::new(self.snapshot.borrow().clone()), + buffers: RefCell::new(buffers), + next_excerpt_id: 1, + subscriptions: Default::default(), + singleton: self.singleton, + replica_id: self.replica_id, + history: self.history.clone(), + title: self.title.clone(), + } + } + + pub fn with_title(mut self, title: String) -> Self { + self.title = Some(title); + self + } + + pub fn singleton(buffer: Model, cx: &mut ModelContext) -> Self { + let mut this = Self::new(buffer.read(cx).replica_id()); + this.singleton = true; + this.push_excerpts( + buffer, + [ExcerptRange { + context: text::Anchor::MIN..text::Anchor::MAX, + primary: None, + }], + cx, + ); + this.snapshot.borrow_mut().singleton = true; + this + } + + pub fn replica_id(&self) -> ReplicaId { + self.replica_id + } + + pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot { + self.sync(cx); + self.snapshot.borrow().clone() + } + + pub fn read(&self, cx: &AppContext) -> Ref { + self.sync(cx); + self.snapshot.borrow() + } + + pub fn as_singleton(&self) -> Option> { + if self.singleton { + return Some( + self.buffers + .borrow() + .values() + .next() + .unwrap() + .buffer + .clone(), + ); + } else { + None + } + } + + pub fn is_singleton(&self) -> bool { + self.singleton + } + + pub fn subscribe(&mut self) -> Subscription { + self.subscriptions.subscribe() + } + + pub fn is_dirty(&self, cx: &AppContext) -> bool { + self.read(cx).is_dirty() + } + + pub fn has_conflict(&self, cx: &AppContext) -> bool { + self.read(cx).has_conflict() + } + + // The `is_empty` signature doesn't match what clippy expects + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, cx: &AppContext) -> usize { + self.read(cx).len() + } + + pub fn is_empty(&self, cx: &AppContext) -> bool { + self.len(cx) != 0 + } + + pub fn symbols_containing( + &self, + offset: T, + theme: Option<&SyntaxTheme>, + cx: &AppContext, + ) -> Option<(u64, Vec>)> { + self.read(cx).symbols_containing(offset, theme) + } + + pub fn edit( + &mut self, + edits: I, + mut autoindent_mode: Option, + cx: &mut ModelContext, + ) where + I: IntoIterator, T)>, + S: ToOffset, + T: Into>, + { + if self.buffers.borrow().is_empty() { + return; + } + + let snapshot = self.read(cx); + let edits = edits.into_iter().map(|(range, new_text)| { + let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); + if range.start > range.end { + mem::swap(&mut range.start, &mut range.end); + } + (range, new_text) + }); + + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| { + buffer.edit(edits, autoindent_mode, cx); + }); + } + + let original_indent_columns = match &mut autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) => mem::take(original_indent_columns), + _ => Default::default(), + }; + + struct BufferEdit { + range: Range, + new_text: Arc, + is_insertion: bool, + original_indent_column: u32, + } + let mut buffer_edits: HashMap> = Default::default(); + let mut edited_excerpt_ids = Vec::new(); + let mut cursor = snapshot.excerpts.cursor::(); + for (ix, (range, new_text)) in edits.enumerate() { + let new_text: Arc = new_text.into(); + let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0); + cursor.seek(&range.start, Bias::Right, &()); + if cursor.item().is_none() && range.start == *cursor.start() { + cursor.prev(&()); + } + let start_excerpt = cursor.item().expect("start offset out of bounds"); + let start_overshoot = range.start - cursor.start(); + let buffer_start = start_excerpt + .range + .context + .start + .to_offset(&start_excerpt.buffer) + + start_overshoot; + edited_excerpt_ids.push(start_excerpt.id); + + cursor.seek(&range.end, Bias::Right, &()); + if cursor.item().is_none() && range.end == *cursor.start() { + cursor.prev(&()); + } + let end_excerpt = cursor.item().expect("end offset out of bounds"); + let end_overshoot = range.end - cursor.start(); + let buffer_end = end_excerpt + .range + .context + .start + .to_offset(&end_excerpt.buffer) + + end_overshoot; + + if start_excerpt.id == end_excerpt.id { + buffer_edits + .entry(start_excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: buffer_start..buffer_end, + new_text, + is_insertion: true, + original_indent_column, + }); + } else { + edited_excerpt_ids.push(end_excerpt.id); + let start_excerpt_range = buffer_start + ..start_excerpt + .range + .context + .end + .to_offset(&start_excerpt.buffer); + let end_excerpt_range = end_excerpt + .range + .context + .start + .to_offset(&end_excerpt.buffer) + ..buffer_end; + buffer_edits + .entry(start_excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: start_excerpt_range, + new_text: new_text.clone(), + is_insertion: true, + original_indent_column, + }); + buffer_edits + .entry(end_excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: end_excerpt_range, + new_text: new_text.clone(), + is_insertion: false, + original_indent_column, + }); + + cursor.seek(&range.start, Bias::Right, &()); + cursor.next(&()); + while let Some(excerpt) = cursor.item() { + if excerpt.id == end_excerpt.id { + break; + } + buffer_edits + .entry(excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: excerpt.range.context.to_offset(&excerpt.buffer), + new_text: new_text.clone(), + is_insertion: false, + original_indent_column, + }); + edited_excerpt_ids.push(excerpt.id); + cursor.next(&()); + } + } + } + + drop(cursor); + drop(snapshot); + // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR. + fn tail( + this: &mut MultiBuffer, + buffer_edits: HashMap>, + autoindent_mode: Option, + edited_excerpt_ids: Vec, + cx: &mut ModelContext, + ) { + for (buffer_id, mut edits) in buffer_edits { + edits.sort_unstable_by_key(|edit| edit.range.start); + this.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + let mut edits = edits.into_iter().peekable(); + let mut insertions = Vec::new(); + let mut original_indent_columns = Vec::new(); + let mut deletions = Vec::new(); + let empty_str: Arc = "".into(); + while let Some(BufferEdit { + mut range, + new_text, + mut is_insertion, + original_indent_column, + }) = edits.next() + { + while let Some(BufferEdit { + range: next_range, + is_insertion: next_is_insertion, + .. + }) = edits.peek() + { + if range.end >= next_range.start { + range.end = cmp::max(next_range.end, range.end); + is_insertion |= *next_is_insertion; + edits.next(); + } else { + break; + } + } + + if is_insertion { + original_indent_columns.push(original_indent_column); + insertions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + new_text.clone(), + )); + } else if !range.is_empty() { + deletions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + empty_str.clone(), + )); + } + } + + let deletion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns: Default::default(), + }) + } else { + None + }; + let insertion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) + } else { + None + }; + + buffer.edit(deletions, deletion_autoindent_mode, cx); + buffer.edit(insertions, insertion_autoindent_mode, cx); + }) + } + + cx.emit(Event::ExcerptsEdited { + ids: edited_excerpt_ids, + }); + } + tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx); + } + + pub fn start_transaction(&mut self, cx: &mut ModelContext) -> Option { + self.start_transaction_at(Instant::now(), cx) + } + + pub fn start_transaction_at( + &mut self, + now: Instant, + cx: &mut ModelContext, + ) -> Option { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now)); + } + + for BufferState { buffer, .. } in self.buffers.borrow().values() { + buffer.update(cx, |buffer, _| buffer.start_transaction_at(now)); + } + self.history.start_transaction(now) + } + + pub fn end_transaction(&mut self, cx: &mut ModelContext) -> Option { + self.end_transaction_at(Instant::now(), cx) + } + + pub fn end_transaction_at( + &mut self, + now: Instant, + cx: &mut ModelContext, + ) -> Option { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)); + } + + let mut buffer_transactions = HashMap::default(); + for BufferState { buffer, .. } in self.buffers.borrow().values() { + if let Some(transaction_id) = + buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) + { + buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id); + } + } + + if self.history.end_transaction(now, buffer_transactions) { + let transaction_id = self.history.group().unwrap(); + Some(transaction_id) + } else { + None + } + } + + pub fn merge_transactions( + &mut self, + transaction: TransactionId, + destination: TransactionId, + cx: &mut ModelContext, + ) { + if let Some(buffer) = self.as_singleton() { + buffer.update(cx, |buffer, _| { + buffer.merge_transactions(transaction, destination) + }); + } else { + if let Some(transaction) = self.history.forget(transaction) { + if let Some(destination) = self.history.transaction_mut(destination) { + for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions { + if let Some(destination_buffer_transaction_id) = + destination.buffer_transactions.get(&buffer_id) + { + if let Some(state) = self.buffers.borrow().get(&buffer_id) { + state.buffer.update(cx, |buffer, _| { + buffer.merge_transactions( + buffer_transaction_id, + *destination_buffer_transaction_id, + ) + }); + } + } else { + destination + .buffer_transactions + .insert(buffer_id, buffer_transaction_id); + } + } + } + } + } + } + + pub fn finalize_last_transaction(&mut self, cx: &mut ModelContext) { + self.history.finalize_last_transaction(); + for BufferState { buffer, .. } in self.buffers.borrow().values() { + buffer.update(cx, |buffer, _| { + buffer.finalize_last_transaction(); + }); + } + } + + pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &mut ModelContext) + where + T: IntoIterator, &'a language2::Transaction)>, + { + self.history + .push_transaction(buffer_transactions, Instant::now(), cx); + self.history.finalize_last_transaction(); + } + + pub fn group_until_transaction( + &mut self, + transaction_id: TransactionId, + cx: &mut ModelContext, + ) { + if let Some(buffer) = self.as_singleton() { + buffer.update(cx, |buffer, _| { + buffer.group_until_transaction(transaction_id) + }); + } else { + self.history.group_until(transaction_id); + } + } + + pub fn set_active_selections( + &mut self, + selections: &[Selection], + line_mode: bool, + cursor_shape: CursorShape, + cx: &mut ModelContext, + ) { + let mut selections_by_buffer: HashMap>> = + Default::default(); + let snapshot = self.read(cx); + let mut cursor = snapshot.excerpts.cursor::>(); + for selection in selections { + let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id); + let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id); + + cursor.seek(&Some(start_locator), Bias::Left, &()); + while let Some(excerpt) = cursor.item() { + if excerpt.locator > *end_locator { + break; + } + + let mut start = excerpt.range.context.start; + let mut end = excerpt.range.context.end; + if excerpt.id == selection.start.excerpt_id { + start = selection.start.text_anchor; + } + if excerpt.id == selection.end.excerpt_id { + end = selection.end.text_anchor; + } + selections_by_buffer + .entry(excerpt.buffer_id) + .or_default() + .push(Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + }); + + cursor.next(&()); + } + } + + for (buffer_id, buffer_state) in self.buffers.borrow().iter() { + if !selections_by_buffer.contains_key(buffer_id) { + buffer_state + .buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + } + } + + for (buffer_id, mut selections) in selections_by_buffer { + self.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer)); + let mut selections = selections.into_iter().peekable(); + let merged_selections = Arc::from_iter(iter::from_fn(|| { + let mut selection = selections.next()?; + while let Some(next_selection) = selections.peek() { + if selection.end.cmp(&next_selection.start, buffer).is_ge() { + let next_selection = selections.next().unwrap(); + if next_selection.end.cmp(&selection.end, buffer).is_ge() { + selection.end = next_selection.end; + } + } else { + break; + } + } + Some(selection) + })); + buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx); + }); + } + } + + pub fn remove_active_selections(&mut self, cx: &mut ModelContext) { + for buffer in self.buffers.borrow().values() { + buffer + .buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + } + } + + pub fn undo(&mut self, cx: &mut ModelContext) -> Option { + let mut transaction_id = None; + if let Some(buffer) = self.as_singleton() { + transaction_id = buffer.update(cx, |buffer, cx| buffer.undo(cx)); + } else { + while let Some(transaction) = self.history.pop_undo() { + let mut undone = false; + for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) { + undone |= buffer.update(cx, |buffer, cx| { + let undo_to = *buffer_transaction_id; + if let Some(entry) = buffer.peek_undo_stack() { + *buffer_transaction_id = entry.transaction_id(); + } + buffer.undo_to_transaction(undo_to, cx) + }); + } + } + + if undone { + transaction_id = Some(transaction.id); + break; + } + } + } + + if let Some(transaction_id) = transaction_id { + cx.emit(Event::TransactionUndone { transaction_id }); + } + + transaction_id + } + + pub fn redo(&mut self, cx: &mut ModelContext) -> Option { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| buffer.redo(cx)); + } + + while let Some(transaction) = self.history.pop_redo() { + let mut redone = false; + for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) { + redone |= buffer.update(cx, |buffer, cx| { + let redo_to = *buffer_transaction_id; + if let Some(entry) = buffer.peek_redo_stack() { + *buffer_transaction_id = entry.transaction_id(); + } + buffer.redo_to_transaction(redo_to, cx) + }); + } + } + + if redone { + return Some(transaction.id); + } + } + + None + } + + pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut ModelContext) { + if let Some(buffer) = self.as_singleton() { + buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx)); + } else if let Some(transaction) = self.history.remove_from_undo(transaction_id) { + for (buffer_id, transaction_id) in &transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) { + buffer.update(cx, |buffer, cx| { + buffer.undo_transaction(*transaction_id, cx) + }); + } + } + } + } + + pub fn stream_excerpts_with_context_lines( + &mut self, + buffer: Model, + ranges: Vec>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> mpsc::Receiver> { + let (buffer_id, buffer_snapshot) = + buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot())); + + let (mut tx, rx) = mpsc::channel(256); + cx.spawn(move |this, mut cx| async move { + let mut excerpt_ranges = Vec::new(); + let mut range_counts = Vec::new(); + cx.executor() + .scoped(|scope| { + scope.spawn(async { + let (ranges, counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + excerpt_ranges = ranges; + range_counts = counts; + }); + }) + .await; + + let mut ranges = ranges.into_iter(); + let mut range_counts = range_counts.into_iter(); + for excerpt_ranges in excerpt_ranges.chunks(100) { + let excerpt_ids = match this.update(&mut cx, |this, cx| { + this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx) + }) { + Ok(excerpt_ids) => excerpt_ids, + Err(_) => return, + }; + + for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.by_ref()) + { + for range in ranges.by_ref().take(range_count) { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.start, + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.end, + }; + if tx.send(start..end).await.is_err() { + break; + } + } + } + } + }) + .detach(); + + rx + } + + pub fn push_excerpts( + &mut self, + buffer: Model, + ranges: impl IntoIterator>, + cx: &mut ModelContext, + ) -> Vec + where + O: text::ToOffset, + { + self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx) + } + + pub fn push_excerpts_with_context_lines( + &mut self, + buffer: Model, + ranges: Vec>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> Vec> + where + O: text::ToPoint + text::ToOffset, + { + let buffer_id = buffer.read(cx).remote_id(); + let buffer_snapshot = buffer.read(cx).snapshot(); + let (excerpt_ranges, range_counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + + let excerpt_ids = self.push_excerpts(buffer, excerpt_ranges, cx); + + let mut anchor_ranges = Vec::new(); + let mut ranges = ranges.into_iter(); + for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.into_iter()) { + anchor_ranges.extend(ranges.by_ref().take(range_count).map(|range| { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: buffer_snapshot.anchor_after(range.start), + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: buffer_snapshot.anchor_after(range.end), + }; + start..end + })) + } + anchor_ranges + } + + pub fn insert_excerpts_after( + &mut self, + prev_excerpt_id: ExcerptId, + buffer: Model, + ranges: impl IntoIterator>, + cx: &mut ModelContext, + ) -> Vec + where + O: text::ToOffset, + { + let mut ids = Vec::new(); + let mut next_excerpt_id = self.next_excerpt_id; + self.insert_excerpts_with_ids_after( + prev_excerpt_id, + buffer, + ranges.into_iter().map(|range| { + let id = ExcerptId(post_inc(&mut next_excerpt_id)); + ids.push(id); + (id, range) + }), + cx, + ); + ids + } + + pub fn insert_excerpts_with_ids_after( + &mut self, + prev_excerpt_id: ExcerptId, + buffer: Model, + ranges: impl IntoIterator)>, + cx: &mut ModelContext, + ) where + O: text::ToOffset, + { + assert_eq!(self.history.transaction_depth, 0); + let mut ranges = ranges.into_iter().peekable(); + if ranges.peek().is_none() { + return Default::default(); + } + + self.sync(cx); + + let buffer_id = buffer.read(cx).remote_id(); + let buffer_snapshot = buffer.read(cx).snapshot(); + + let mut buffers = self.buffers.borrow_mut(); + let buffer_state = buffers.entry(buffer_id).or_insert_with(|| BufferState { + last_version: buffer_snapshot.version().clone(), + last_parse_count: buffer_snapshot.parse_count(), + last_selections_update_count: buffer_snapshot.selections_update_count(), + last_diagnostics_update_count: buffer_snapshot.diagnostics_update_count(), + last_file_update_count: buffer_snapshot.file_update_count(), + last_git_diff_update_count: buffer_snapshot.git_diff_update_count(), + excerpts: Default::default(), + _subscriptions: [ + cx.observe(&buffer, |_, _, cx| cx.notify()), + cx.subscribe(&buffer, Self::on_buffer_event), + ], + buffer: buffer.clone(), + }); + + let mut snapshot = self.snapshot.borrow_mut(); + + let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone(); + let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids); + let mut cursor = snapshot.excerpts.cursor::>(); + let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &()); + prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone(); + + let edit_start = new_excerpts.summary().text.len; + new_excerpts.update_last( + |excerpt| { + excerpt.has_trailing_newline = true; + }, + &(), + ); + + let next_locator = if let Some(excerpt) = cursor.item() { + excerpt.locator.clone() + } else { + Locator::max() + }; + + let mut excerpts = Vec::new(); + while let Some((id, range)) = ranges.next() { + let locator = Locator::between(&prev_locator, &next_locator); + if let Err(ix) = buffer_state.excerpts.binary_search(&locator) { + buffer_state.excerpts.insert(ix, locator.clone()); + } + let range = ExcerptRange { + context: buffer_snapshot.anchor_before(&range.context.start) + ..buffer_snapshot.anchor_after(&range.context.end), + primary: range.primary.map(|primary| { + buffer_snapshot.anchor_before(&primary.start) + ..buffer_snapshot.anchor_after(&primary.end) + }), + }; + if id.0 >= self.next_excerpt_id { + self.next_excerpt_id = id.0 + 1; + } + excerpts.push((id, range.clone())); + let excerpt = Excerpt::new( + id, + locator.clone(), + buffer_id, + buffer_snapshot.clone(), + range, + ranges.peek().is_some() || cursor.item().is_some(), + ); + new_excerpts.push(excerpt, &()); + prev_locator = locator.clone(); + new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &()); + } + + let edit_end = new_excerpts.summary().text.len; + + let suffix = cursor.suffix(&()); + let changed_trailing_excerpt = suffix.is_empty(); + new_excerpts.append(suffix, &()); + drop(cursor); + snapshot.excerpts = new_excerpts; + snapshot.excerpt_ids = new_excerpt_ids; + if changed_trailing_excerpt { + snapshot.trailing_excerpt_update_count += 1; + } + + self.subscriptions.publish_mut([Edit { + old: edit_start..edit_start, + new: edit_start..edit_end, + }]); + cx.emit(Event::Edited { + sigleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsAdded { + buffer, + predecessor: prev_excerpt_id, + excerpts, + }); + cx.notify(); + } + + pub fn clear(&mut self, cx: &mut ModelContext) { + self.sync(cx); + let ids = self.excerpt_ids(); + self.buffers.borrow_mut().clear(); + let mut snapshot = self.snapshot.borrow_mut(); + let prev_len = snapshot.len(); + snapshot.excerpts = Default::default(); + snapshot.trailing_excerpt_update_count += 1; + snapshot.is_dirty = false; + snapshot.has_conflict = false; + + self.subscriptions.publish_mut([Edit { + old: 0..prev_len, + new: 0..0, + }]); + cx.emit(Event::Edited { + sigleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsRemoved { ids }); + cx.notify(); + } + + pub fn excerpts_for_buffer( + &self, + buffer: &Model, + cx: &AppContext, + ) -> Vec<(ExcerptId, ExcerptRange)> { + let mut excerpts = Vec::new(); + let snapshot = self.read(cx); + let buffers = self.buffers.borrow(); + let mut cursor = snapshot.excerpts.cursor::>(); + for locator in buffers + .get(&buffer.read(cx).remote_id()) + .map(|state| &state.excerpts) + .into_iter() + .flatten() + { + cursor.seek_forward(&Some(locator), Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.locator == *locator { + excerpts.push((excerpt.id.clone(), excerpt.range.clone())); + } + } + } + + excerpts + } + + pub fn excerpt_ids(&self) -> Vec { + self.snapshot + .borrow() + .excerpts + .iter() + .map(|entry| entry.id) + .collect() + } + + pub fn excerpt_containing( + &self, + position: impl ToOffset, + cx: &AppContext, + ) -> Option<(ExcerptId, Model, Range)> { + let snapshot = self.read(cx); + let position = position.to_offset(&snapshot); + + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&position, Bias::Right, &()); + cursor + .item() + .or_else(|| snapshot.excerpts.last()) + .map(|excerpt| { + ( + excerpt.id.clone(), + self.buffers + .borrow() + .get(&excerpt.buffer_id) + .unwrap() + .buffer + .clone(), + excerpt.range.context.clone(), + ) + }) + } + + // If point is at the end of the buffer, the last excerpt is returned + pub fn point_to_buffer_offset( + &self, + point: T, + cx: &AppContext, + ) -> Option<(Model, usize, ExcerptId)> { + let snapshot = self.read(cx); + let offset = point.to_offset(&snapshot); + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + cursor.item().map(|excerpt| { + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let buffer_point = excerpt_start + offset - *cursor.start(); + let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); + + (buffer, buffer_point, excerpt.id) + }) + } + + pub fn range_to_buffer_ranges( + &self, + range: Range, + cx: &AppContext, + ) -> Vec<(Model, Range, ExcerptId)> { + let snapshot = self.read(cx); + let start = range.start.to_offset(&snapshot); + let end = range.end.to_offset(&snapshot); + + let mut result = Vec::new(); + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&start, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + while let Some(excerpt) = cursor.item() { + if *cursor.start() > end { + break; + } + + let mut end_before_newline = cursor.end(&()); + if excerpt.has_trailing_newline { + end_before_newline -= 1; + } + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start()); + let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start()); + let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); + result.push((buffer, start..end, excerpt.id)); + cursor.next(&()); + } + + result + } + + pub fn remove_excerpts( + &mut self, + excerpt_ids: impl IntoIterator, + cx: &mut ModelContext, + ) { + self.sync(cx); + let ids = excerpt_ids.into_iter().collect::>(); + if ids.is_empty() { + return; + } + + let mut buffers = self.buffers.borrow_mut(); + let mut snapshot = self.snapshot.borrow_mut(); + let mut new_excerpts = SumTree::new(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(); + let mut edits = Vec::new(); + let mut excerpt_ids = ids.iter().copied().peekable(); + + while let Some(excerpt_id) = excerpt_ids.next() { + // Seek to the next excerpt to remove, preserving any preceding excerpts. + let locator = snapshot.excerpt_locator_for_id(excerpt_id); + new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &()); + + if let Some(mut excerpt) = cursor.item() { + if excerpt.id != excerpt_id { + continue; + } + let mut old_start = cursor.start().1; + + // Skip over the removed excerpt. + 'remove_excerpts: loop { + if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) { + buffer_state.excerpts.retain(|l| l != &excerpt.locator); + if buffer_state.excerpts.is_empty() { + buffers.remove(&excerpt.buffer_id); + } + } + cursor.next(&()); + + // Skip over any subsequent excerpts that are also removed. + while let Some(&next_excerpt_id) = excerpt_ids.peek() { + let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id); + if let Some(next_excerpt) = cursor.item() { + if next_excerpt.locator == *next_locator { + excerpt_ids.next(); + excerpt = next_excerpt; + continue 'remove_excerpts; + } + } + break; + } + + break; + } + + // When removing the last excerpt, remove the trailing newline from + // the previous excerpt. + if cursor.item().is_none() && old_start > 0 { + old_start -= 1; + new_excerpts.update_last(|e| e.has_trailing_newline = false, &()); + } + + // Push an edit for the removal of this run of excerpts. + let old_end = cursor.start().1; + let new_start = new_excerpts.summary().text.len; + edits.push(Edit { + old: old_start..old_end, + new: new_start..new_start, + }); + } + } + let suffix = cursor.suffix(&()); + let changed_trailing_excerpt = suffix.is_empty(); + new_excerpts.append(suffix, &()); + drop(cursor); + snapshot.excerpts = new_excerpts; + + if changed_trailing_excerpt { + snapshot.trailing_excerpt_update_count += 1; + } + + self.subscriptions.publish_mut(edits); + cx.emit(Event::Edited { + sigleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsRemoved { ids }); + cx.notify(); + } + + pub fn wait_for_anchors<'a>( + &self, + anchors: impl 'a + Iterator, + cx: &mut ModelContext, + ) -> impl 'static + Future> { + let borrow = self.buffers.borrow(); + let mut error = None; + let mut futures = Vec::new(); + for anchor in anchors { + if let Some(buffer_id) = anchor.buffer_id { + if let Some(buffer) = borrow.get(&buffer_id) { + buffer.buffer.update(cx, |buffer, _| { + futures.push(buffer.wait_for_anchors([anchor.text_anchor])) + }); + } else { + error = Some(anyhow!( + "buffer {buffer_id} is not part of this multi-buffer" + )); + break; + } + } + } + async move { + if let Some(error) = error { + Err(error)?; + } + for future in futures { + future.await?; + } + Ok(()) + } + } + + pub fn text_anchor_for_position( + &self, + position: T, + cx: &AppContext, + ) -> Option<(Model, language2::Anchor)> { + let snapshot = self.read(cx); + let anchor = snapshot.anchor_before(position); + let buffer = self + .buffers + .borrow() + .get(&anchor.buffer_id?)? + .buffer + .clone(); + Some((buffer, anchor.text_anchor)) + } + + fn on_buffer_event( + &mut self, + _: Model, + event: &language2::Event, + cx: &mut ModelContext, + ) { + cx.emit(match event { + language2::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, + + // + language2::Event::Operation(_) => return, + }); + } + + pub fn all_buffers(&self) -> HashSet> { + self.buffers + .borrow() + .values() + .map(|state| state.buffer.clone()) + .collect() + } + + pub fn buffer(&self, buffer_id: u64) -> Option> { + self.buffers + .borrow() + .get(&buffer_id) + .map(|state| state.buffer.clone()) + } + + pub fn is_completion_trigger(&self, position: Anchor, text: &str, cx: &AppContext) -> bool { + let mut chars = text.chars(); + let char = if let Some(char) = chars.next() { + char + } else { + return false; + }; + if chars.next().is_some() { + return false; + } + + let snapshot = self.snapshot(cx); + let position = position.to_offset(&snapshot); + let scope = snapshot.language_scope_at(position); + if char_kind(&scope, char) == CharKind::Word { + return true; + } + + let anchor = snapshot.anchor_before(position); + anchor + .buffer_id + .and_then(|buffer_id| { + let buffer = self.buffers.borrow().get(&buffer_id)?.buffer.clone(); + Some( + buffer + .read(cx) + .completion_triggers() + .iter() + .any(|string| string == text), + ) + }) + .unwrap_or(false) + } + + pub fn language_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> Option> { + self.point_to_buffer_offset(point, cx) + .and_then(|(buffer, offset, _)| buffer.read(cx).language_at(offset)) + } + + pub fn settings_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let mut language = None; + let mut file = None; + if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) { + let buffer = buffer.read(cx); + language = buffer.language_at(offset); + file = buffer.file(); + } + language_settings(language.as_ref(), file, cx) + } + + pub fn for_each_buffer(&self, mut f: impl FnMut(&Model)) { + self.buffers + .borrow() + .values() + .for_each(|state| f(&state.buffer)) + } + + pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { + if let Some(title) = self.title.as_ref() { + return title.into(); + } + + if let Some(buffer) = self.as_singleton() { + if let Some(file) = buffer.read(cx).file() { + return file.file_name(cx).to_string_lossy(); + } + } + + "untitled".into() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn is_parsing(&self, cx: &AppContext) -> bool { + self.as_singleton().unwrap().read(cx).is_parsing() + } + + fn sync(&self, cx: &AppContext) { + let mut snapshot = self.snapshot.borrow_mut(); + let mut excerpts_to_edit = Vec::new(); + let mut reparsed = false; + let mut diagnostics_updated = false; + let mut git_diff_updated = false; + let mut is_dirty = false; + let mut has_conflict = false; + let mut edited = false; + let mut buffers = self.buffers.borrow_mut(); + for buffer_state in buffers.values_mut() { + let buffer = buffer_state.buffer.read(cx); + let version = buffer.version(); + let parse_count = buffer.parse_count(); + let selections_update_count = buffer.selections_update_count(); + let diagnostics_update_count = buffer.diagnostics_update_count(); + let file_update_count = buffer.file_update_count(); + let git_diff_update_count = buffer.git_diff_update_count(); + + let buffer_edited = version.changed_since(&buffer_state.last_version); + let buffer_reparsed = parse_count > buffer_state.last_parse_count; + let buffer_selections_updated = + selections_update_count > buffer_state.last_selections_update_count; + let buffer_diagnostics_updated = + diagnostics_update_count > buffer_state.last_diagnostics_update_count; + let buffer_file_updated = file_update_count > buffer_state.last_file_update_count; + let buffer_git_diff_updated = + git_diff_update_count > buffer_state.last_git_diff_update_count; + if buffer_edited + || buffer_reparsed + || buffer_selections_updated + || buffer_diagnostics_updated + || buffer_file_updated + || buffer_git_diff_updated + { + buffer_state.last_version = version; + buffer_state.last_parse_count = parse_count; + buffer_state.last_selections_update_count = selections_update_count; + buffer_state.last_diagnostics_update_count = diagnostics_update_count; + buffer_state.last_file_update_count = file_update_count; + buffer_state.last_git_diff_update_count = git_diff_update_count; + excerpts_to_edit.extend( + buffer_state + .excerpts + .iter() + .map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)), + ); + } + + edited |= buffer_edited; + reparsed |= buffer_reparsed; + diagnostics_updated |= buffer_diagnostics_updated; + git_diff_updated |= buffer_git_diff_updated; + is_dirty |= buffer.is_dirty(); + has_conflict |= buffer.has_conflict(); + } + if edited { + snapshot.edit_count += 1; + } + if reparsed { + snapshot.parse_count += 1; + } + if diagnostics_updated { + snapshot.diagnostics_update_count += 1; + } + if git_diff_updated { + snapshot.git_diff_update_count += 1; + } + snapshot.is_dirty = is_dirty; + snapshot.has_conflict = has_conflict; + + excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator); + + let mut edits = Vec::new(); + let mut new_excerpts = SumTree::new(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(); + + for (locator, buffer, buffer_edited) in excerpts_to_edit { + new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &()); + let old_excerpt = cursor.item().unwrap(); + let buffer = buffer.read(cx); + let buffer_id = buffer.remote_id(); + + let mut new_excerpt; + if buffer_edited { + edits.extend( + buffer + .edits_since_in_range::( + old_excerpt.buffer.version(), + old_excerpt.range.context.clone(), + ) + .map(|mut edit| { + let excerpt_old_start = cursor.start().1; + let excerpt_new_start = new_excerpts.summary().text.len; + edit.old.start += excerpt_old_start; + edit.old.end += excerpt_old_start; + edit.new.start += excerpt_new_start; + edit.new.end += excerpt_new_start; + edit + }), + ); + + new_excerpt = Excerpt::new( + old_excerpt.id, + locator.clone(), + buffer_id, + buffer.snapshot(), + old_excerpt.range.clone(), + old_excerpt.has_trailing_newline, + ); + } else { + new_excerpt = old_excerpt.clone(); + new_excerpt.buffer = buffer.snapshot(); + } + + new_excerpts.push(new_excerpt, &()); + cursor.next(&()); + } + new_excerpts.append(cursor.suffix(&()), &()); + + drop(cursor); + snapshot.excerpts = new_excerpts; + + self.subscriptions.publish(edits); + } +} + +#[cfg(any(test, feature = "test-support"))] +impl MultiBuffer { + pub fn build_simple(text: &str, cx: &mut gpui2::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, + ) -> Model { + let multi = cx.build_model(|_| Self::new(0)); + for (text, ranges) in excerpts { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); + let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange { + context: range, + primary: None, + }); + multi.update(cx, |multi, cx| { + multi.push_excerpts(buffer, excerpt_ranges, cx) + }); + } + + multi + } + + pub fn build_from_buffer(buffer: Model, cx: &mut gpui2::AppContext) -> Model { + cx.build_model(|cx| Self::singleton(buffer, cx)) + } + + pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui2::AppContext) -> Model { + cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + let mutation_count = rng.gen_range(1..=5); + multibuffer.randomly_edit_excerpts(rng, mutation_count, cx); + multibuffer + }) + } + + pub fn randomly_edit( + &mut self, + rng: &mut impl rand::Rng, + edit_count: usize, + cx: &mut ModelContext, + ) { + use util::RandomCharIter; + + let snapshot = self.read(cx); + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= snapshot.len()) { + break; + } + + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let end = snapshot.clip_offset(rng.gen_range(new_start..=snapshot.len()), Bias::Right); + let start = snapshot.clip_offset(rng.gen_range(new_start..=end), Bias::Right); + last_end = Some(end); + + let mut range = start..end; + if rng.gen_bool(0.2) { + mem::swap(&mut range.start, &mut range.end); + } + + let new_text_len = rng.gen_range(0..10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + edits.push((range, new_text.into())); + } + log::info!("mutating multi-buffer with {:?}", edits); + drop(snapshot); + + self.edit(edits, None, cx); + } + + pub fn randomly_edit_excerpts( + &mut self, + rng: &mut impl rand::Rng, + mutation_count: usize, + cx: &mut ModelContext, + ) { + use rand::prelude::*; + use std::env; + use util::RandomCharIter; + + let max_excerpts = env::var("MAX_EXCERPTS") + .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable")) + .unwrap_or(5); + + let mut buffers = Vec::new(); + for _ in 0..mutation_count { + if rng.gen_bool(0.05) { + log::info!("Clearing multi-buffer"); + self.clear(cx); + continue; + } + + let excerpt_ids = self.excerpt_ids(); + if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) { + let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() { + let text = RandomCharIter::new(&mut *rng).take(10).collect::(); + buffers + .push(cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text))); + let buffer = buffers.last().unwrap().read(cx); + log::info!( + "Creating new buffer {} with text: {:?}", + buffer.remote_id(), + buffer.text() + ); + buffers.last().unwrap().clone() + } else { + self.buffers + .borrow() + .values() + .choose(rng) + .unwrap() + .buffer + .clone() + }; + + let buffer = buffer_handle.read(cx); + let buffer_text = buffer.text(); + let ranges = (0..rng.gen_range(0..5)) + .map(|_| { + let end_ix = + buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right); + let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + ExcerptRange { + context: start_ix..end_ix, + primary: None, + } + }) + .collect::>(); + log::info!( + "Inserting excerpts from buffer {} and ranges {:?}: {:?}", + buffer_handle.read(cx).remote_id(), + ranges.iter().map(|r| &r.context).collect::>(), + ranges + .iter() + .map(|r| &buffer_text[r.context.clone()]) + .collect::>() + ); + + let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx); + log::info!("Inserted with ids: {:?}", excerpt_id); + } else { + let remove_count = rng.gen_range(1..=excerpt_ids.len()); + let mut excerpts_to_remove = excerpt_ids + .choose_multiple(rng, remove_count) + .cloned() + .collect::>(); + let snapshot = self.snapshot.borrow(); + excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot)); + drop(snapshot); + log::info!("Removing excerpts {:?}", excerpts_to_remove); + self.remove_excerpts(excerpts_to_remove, cx); + } + } + } + + pub fn randomly_mutate( + &mut self, + rng: &mut impl rand::Rng, + mutation_count: usize, + cx: &mut ModelContext, + ) { + use rand::prelude::*; + + if rng.gen_bool(0.7) || self.singleton { + let buffer = self + .buffers + .borrow() + .values() + .choose(rng) + .map(|state| state.buffer.clone()); + + if let Some(buffer) = buffer { + buffer.update(cx, |buffer, cx| { + if rng.gen() { + buffer.randomly_edit(rng, mutation_count, cx); + } else { + buffer.randomly_undo_redo(rng, cx); + } + }); + } else { + self.randomly_edit(rng, mutation_count, cx); + } + } else { + self.randomly_edit_excerpts(rng, mutation_count, cx); + } + + self.check_invariants(cx); + } + + fn check_invariants(&self, cx: &mut ModelContext) { + let snapshot = self.read(cx); + let excerpts = snapshot.excerpts.items(&()); + let excerpt_ids = snapshot.excerpt_ids.items(&()); + + for (ix, excerpt) in excerpts.iter().enumerate() { + if ix == 0 { + if excerpt.locator <= Locator::min() { + panic!("invalid first excerpt locator {:?}", excerpt.locator); + } + } else { + if excerpt.locator <= excerpts[ix - 1].locator { + panic!("excerpts are out-of-order: {:?}", excerpts); + } + } + } + + for (ix, entry) in excerpt_ids.iter().enumerate() { + if ix == 0 { + if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() { + panic!("invalid first excerpt id {:?}", entry.id); + } + } else { + if entry.id <= excerpt_ids[ix - 1].id { + panic!("excerpt ids are out-of-order: {:?}", excerpt_ids); + } + } + } + } +} + +impl EventEmitter for MultiBuffer { + type Event = Event; +} + +impl MultiBufferSnapshot { + pub fn text(&self) -> String { + self.chunks(0..self.len(), false) + .map(|chunk| chunk.text) + .collect() + } + + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + '_ { + let mut offset = position.to_offset(self); + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Left, &()); + let mut excerpt_chunks = cursor.item().map(|excerpt| { + let end_before_footer = cursor.start() + excerpt.text_summary.len; + let start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let end = start + (cmp::min(offset, end_before_footer) - cursor.start()); + excerpt.buffer.reversed_chunks_in_range(start..end) + }); + iter::from_fn(move || { + if offset == *cursor.start() { + cursor.prev(&()); + let excerpt = cursor.item()?; + excerpt_chunks = Some( + excerpt + .buffer + .reversed_chunks_in_range(excerpt.range.context.clone()), + ); + } + + let excerpt = cursor.item().unwrap(); + if offset == cursor.end(&()) && excerpt.has_trailing_newline { + offset -= 1; + Some("\n") + } else { + let chunk = excerpt_chunks.as_mut().unwrap().next().unwrap(); + offset -= chunk.len(); + Some(chunk) + } + }) + .flat_map(|c| c.chars().rev()) + } + + pub fn chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.text_for_range(offset..self.len()) + .flat_map(|chunk| chunk.chars()) + } + + pub fn text_for_range(&self, range: Range) -> impl Iterator + '_ { + self.chunks(range, false).map(|chunk| chunk.text) + } + + pub fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_in_range(position..self.len()) + .flatten() + .copied() + .take(needle.len()) + .eq(needle.bytes()) + } + + pub fn surrounding_word(&self, start: T) -> (Range, Option) { + let mut start = start.to_offset(self); + let mut end = start; + let mut next_chars = self.chars_at(start).peekable(); + let mut prev_chars = self.reversed_chars_at(start).peekable(); + + let scope = self.language_scope_at(start); + let kind = |c| char_kind(&scope, c); + let word_kind = cmp::max( + prev_chars.peek().copied().map(kind), + next_chars.peek().copied().map(kind), + ); + + for ch in prev_chars { + if Some(kind(ch)) == word_kind && ch != '\n' { + start -= ch.len_utf8(); + } else { + break; + } + } + + for ch in next_chars { + if Some(kind(ch)) == word_kind && ch != '\n' { + end += ch.len_utf8(); + } else { + break; + } + } + + (start..end, word_kind) + } + + pub fn as_singleton(&self) -> Option<(&ExcerptId, u64, &BufferSnapshot)> { + if self.singleton { + self.excerpts + .iter() + .next() + .map(|e| (&e.id, e.buffer_id, &e.buffer)) + } else { + None + } + } + + pub fn len(&self) -> usize { + self.excerpts.summary().text.len + } + + pub fn is_empty(&self) -> bool { + self.excerpts.summary().text.len == 0 + } + + pub fn max_buffer_row(&self) -> u32 { + self.excerpts.summary().max_buffer_row + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_offset(offset, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let buffer_offset = excerpt + .buffer + .clip_offset(excerpt_start + (offset - cursor.start()), bias); + buffer_offset.saturating_sub(excerpt_start) + } else { + 0 + }; + cursor.start() + overshoot + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_point(point, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&point, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .clip_point(excerpt_start + (point - cursor.start()), bias); + buffer_point.saturating_sub(excerpt_start) + } else { + Point::zero() + }; + *cursor.start() + overshoot + } + + pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_offset_utf16(offset, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt.range.context.start.to_offset_utf16(&excerpt.buffer); + let buffer_offset = excerpt + .buffer + .clip_offset_utf16(excerpt_start + (offset - cursor.start()), bias); + OffsetUtf16(buffer_offset.0.saturating_sub(excerpt_start.0)) + } else { + OffsetUtf16(0) + }; + *cursor.start() + overshoot + } + + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_point_utf16(point, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&point.0, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt + .buffer + .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); + let buffer_point = excerpt + .buffer + .clip_point_utf16(Unclipped(excerpt_start + (point.0 - cursor.start())), bias); + buffer_point.saturating_sub(excerpt_start) + } else { + PointUtf16::zero() + }; + *cursor.start() + overshoot + } + + pub fn bytes_in_range(&self, range: Range) -> MultiBufferBytes { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut excerpts = self.excerpts.cursor::(); + excerpts.seek(&range.start, Bias::Right, &()); + + let mut chunk = &[][..]; + let excerpt_bytes = if let Some(excerpt) = excerpts.item() { + let mut excerpt_bytes = excerpt + .bytes_in_range(range.start - excerpts.start()..range.end - excerpts.start()); + chunk = excerpt_bytes.next().unwrap_or(&[][..]); + Some(excerpt_bytes) + } else { + None + }; + MultiBufferBytes { + range, + excerpts, + excerpt_bytes, + chunk, + } + } + + pub fn reversed_bytes_in_range( + &self, + range: Range, + ) -> ReversedMultiBufferBytes { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut excerpts = self.excerpts.cursor::(); + excerpts.seek(&range.end, Bias::Left, &()); + + let mut chunk = &[][..]; + let excerpt_bytes = if let Some(excerpt) = excerpts.item() { + let mut excerpt_bytes = excerpt.reversed_bytes_in_range( + range.start - excerpts.start()..range.end - excerpts.start(), + ); + chunk = excerpt_bytes.next().unwrap_or(&[][..]); + Some(excerpt_bytes) + } else { + None + }; + + ReversedMultiBufferBytes { + range, + excerpts, + excerpt_bytes, + chunk, + } + } + + pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows { + let mut result = MultiBufferRows { + buffer_row_range: 0..0, + excerpts: self.excerpts.cursor(), + }; + result.seek(start_row); + result + } + + pub fn chunks(&self, range: Range, language_aware: bool) -> MultiBufferChunks { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut chunks = MultiBufferChunks { + range: range.clone(), + excerpts: self.excerpts.cursor(), + excerpt_chunks: None, + language_aware, + }; + chunks.seek(range.start); + chunks + } + + pub fn offset_to_point(&self, offset: usize) -> Point { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_to_point(offset); + } + + let mut cursor = self.excerpts.cursor::<(usize, Point)>(); + cursor.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_point) = cursor.start(); + let overshoot = offset - start_offset; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .offset_to_point(excerpt_start_offset + overshoot); + *start_point + (buffer_point - excerpt_start_point) + } else { + self.excerpts.summary().text.lines + } + } + + pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_to_point_utf16(offset); + } + + let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>(); + cursor.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_point) = cursor.start(); + let overshoot = offset - start_offset; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt.range.context.start.to_point_utf16(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .offset_to_point_utf16(excerpt_start_offset + overshoot); + *start_point + (buffer_point - excerpt_start_point) + } else { + self.excerpts.summary().text.lines_utf16() + } + } + + pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.point_to_point_utf16(point); + } + + let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_point) = cursor.start(); + let overshoot = point - start_offset; + let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer); + let excerpt_start_point_utf16 = + excerpt.range.context.start.to_point_utf16(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .point_to_point_utf16(excerpt_start_point + overshoot); + *start_point + (buffer_point - excerpt_start_point_utf16) + } else { + self.excerpts.summary().text.lines_utf16() + } + } + + pub fn point_to_offset(&self, point: Point) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.point_to_offset(point); + } + + let mut cursor = self.excerpts.cursor::<(Point, usize)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_point, start_offset) = cursor.start(); + let overshoot = point - start_point; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer); + let buffer_offset = excerpt + .buffer + .point_to_offset(excerpt_start_point + overshoot); + *start_offset + buffer_offset - excerpt_start_offset + } else { + self.excerpts.summary().text.len + } + } + + pub fn offset_utf16_to_offset(&self, offset_utf16: OffsetUtf16) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_utf16_to_offset(offset_utf16); + } + + let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>(); + cursor.seek(&offset_utf16, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset_utf16, start_offset) = cursor.start(); + let overshoot = offset_utf16 - start_offset_utf16; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_offset_utf16 = + excerpt.buffer.offset_to_offset_utf16(excerpt_start_offset); + let buffer_offset = excerpt + .buffer + .offset_utf16_to_offset(excerpt_start_offset_utf16 + overshoot); + *start_offset + (buffer_offset - excerpt_start_offset) + } else { + self.excerpts.summary().text.len + } + } + + pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_to_offset_utf16(offset); + } + + let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>(); + cursor.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_offset_utf16) = cursor.start(); + let overshoot = offset - start_offset; + let excerpt_start_offset_utf16 = + excerpt.range.context.start.to_offset_utf16(&excerpt.buffer); + let excerpt_start_offset = excerpt + .buffer + .offset_utf16_to_offset(excerpt_start_offset_utf16); + let buffer_offset_utf16 = excerpt + .buffer + .offset_to_offset_utf16(excerpt_start_offset + overshoot); + *start_offset_utf16 + (buffer_offset_utf16 - excerpt_start_offset_utf16) + } else { + self.excerpts.summary().text.len_utf16 + } + } + + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.point_utf16_to_offset(point); + } + + let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_point, start_offset) = cursor.start(); + let overshoot = point - start_point; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt + .buffer + .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); + let buffer_offset = excerpt + .buffer + .point_utf16_to_offset(excerpt_start_point + overshoot); + *start_offset + (buffer_offset - excerpt_start_offset) + } else { + self.excerpts.summary().text.len + } + } + + pub fn point_to_buffer_offset( + &self, + point: T, + ) -> Option<(&BufferSnapshot, usize)> { + let offset = point.to_offset(&self); + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + cursor.item().map(|excerpt| { + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let buffer_point = excerpt_start + offset - *cursor.start(); + (&excerpt.buffer, buffer_point) + }) + } + + pub fn suggested_indents( + &self, + rows: impl IntoIterator, + cx: &AppContext, + ) -> BTreeMap { + let mut result = BTreeMap::new(); + + let mut rows_for_excerpt = Vec::new(); + let mut cursor = self.excerpts.cursor::(); + let mut rows = rows.into_iter().peekable(); + let mut prev_row = u32::MAX; + let mut prev_language_indent_size = IndentSize::default(); + + while let Some(row) = rows.next() { + cursor.seek(&Point::new(row, 0), Bias::Right, &()); + let excerpt = match cursor.item() { + Some(excerpt) => excerpt, + _ => continue, + }; + + // Retrieve the language and indent size once for each disjoint region being indented. + let single_indent_size = if row.saturating_sub(1) == prev_row { + prev_language_indent_size + } else { + excerpt + .buffer + .language_indent_size_at(Point::new(row, 0), cx) + }; + prev_language_indent_size = single_indent_size; + prev_row = row; + + let start_buffer_row = excerpt.range.context.start.to_point(&excerpt.buffer).row; + let start_multibuffer_row = cursor.start().row; + + rows_for_excerpt.push(row); + while let Some(next_row) = rows.peek().copied() { + if cursor.end(&()).row > next_row { + rows_for_excerpt.push(next_row); + rows.next(); + } else { + break; + } + } + + let buffer_rows = rows_for_excerpt + .drain(..) + .map(|row| start_buffer_row + row - start_multibuffer_row); + let buffer_indents = excerpt + .buffer + .suggested_indents(buffer_rows, single_indent_size); + let multibuffer_indents = buffer_indents + .into_iter() + .map(|(row, indent)| (start_multibuffer_row + row - start_buffer_row, indent)); + result.extend(multibuffer_indents); + } + + result + } + + pub fn indent_size_for_line(&self, row: u32) -> IndentSize { + if let Some((buffer, range)) = self.buffer_line_for_row(row) { + let mut size = buffer.indent_size_for_line(range.start.row); + size.len = size + .len + .min(range.end.column) + .saturating_sub(range.start.column); + size + } else { + IndentSize::spaces(0) + } + } + + pub fn prev_non_blank_row(&self, mut row: u32) -> Option { + while row > 0 { + row -= 1; + if !self.is_line_blank(row) { + return Some(row); + } + } + None + } + + pub fn line_len(&self, row: u32) -> u32 { + if let Some((_, range)) = self.buffer_line_for_row(row) { + range.end.column - range.start.column + } else { + 0 + } + } + + pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range)> { + let mut cursor = self.excerpts.cursor::(); + let point = Point::new(row, 0); + cursor.seek(&point, Bias::Right, &()); + if cursor.item().is_none() && *cursor.start() == point { + cursor.prev(&()); + } + if let Some(excerpt) = cursor.item() { + let overshoot = row - cursor.start().row; + let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer); + let excerpt_end = excerpt.range.context.end.to_point(&excerpt.buffer); + let buffer_row = excerpt_start.row + overshoot; + let line_start = Point::new(buffer_row, 0); + let line_end = Point::new(buffer_row, excerpt.buffer.line_len(buffer_row)); + return Some(( + &excerpt.buffer, + line_start.max(excerpt_start)..line_end.min(excerpt_end), + )); + } + None + } + + pub fn max_point(&self) -> Point { + self.text_summary().lines + } + + pub fn text_summary(&self) -> TextSummary { + self.excerpts.summary().text.clone() + } + + pub fn text_summary_for_range(&self, range: Range) -> D + where + D: TextDimension, + O: ToOffset, + { + let mut summary = D::default(); + let mut range = range.start.to_offset(self)..range.end.to_offset(self); + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&range.start, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let mut end_before_newline = cursor.end(&()); + if excerpt.has_trailing_newline { + end_before_newline -= 1; + } + + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let start_in_excerpt = excerpt_start + (range.start - cursor.start()); + let end_in_excerpt = + excerpt_start + (cmp::min(end_before_newline, range.end) - cursor.start()); + summary.add_assign( + &excerpt + .buffer + .text_summary_for_range(start_in_excerpt..end_in_excerpt), + ); + + if range.end > end_before_newline { + summary.add_assign(&D::from_text_summary(&TextSummary::from("\n"))); + } + + cursor.next(&()); + } + + if range.end > *cursor.start() { + summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>( + &range.end, + Bias::Right, + &(), + ))); + if let Some(excerpt) = cursor.item() { + range.end = cmp::max(*cursor.start(), range.end); + + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let end_in_excerpt = excerpt_start + (range.end - cursor.start()); + summary.add_assign( + &excerpt + .buffer + .text_summary_for_range(excerpt_start..end_in_excerpt), + ); + } + } + + summary + } + + pub fn summary_for_anchor(&self, anchor: &Anchor) -> D + where + D: TextDimension + Ord + Sub, + { + let mut cursor = self.excerpts.cursor::(); + let locator = self.excerpt_locator_for_id(anchor.excerpt_id); + + cursor.seek(locator, Bias::Left, &()); + if cursor.item().is_none() { + cursor.next(&()); + } + + let mut position = D::from_text_summary(&cursor.start().text); + if let Some(excerpt) = cursor.item() { + if excerpt.id == anchor.excerpt_id { + let excerpt_buffer_start = + excerpt.range.context.start.summary::(&excerpt.buffer); + let excerpt_buffer_end = excerpt.range.context.end.summary::(&excerpt.buffer); + let buffer_position = cmp::min( + excerpt_buffer_end, + anchor.text_anchor.summary::(&excerpt.buffer), + ); + if buffer_position > excerpt_buffer_start { + position.add_assign(&(buffer_position - excerpt_buffer_start)); + } + } + } + position + } + + pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec + where + D: TextDimension + Ord + Sub, + I: 'a + IntoIterator, + { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer + .summaries_for_anchors(anchors.into_iter().map(|a| &a.text_anchor)) + .collect(); + } + + let mut anchors = anchors.into_iter().peekable(); + let mut cursor = self.excerpts.cursor::(); + let mut summaries = Vec::new(); + while let Some(anchor) = anchors.peek() { + let excerpt_id = anchor.excerpt_id; + let excerpt_anchors = iter::from_fn(|| { + let anchor = anchors.peek()?; + if anchor.excerpt_id == excerpt_id { + Some(&anchors.next().unwrap().text_anchor) + } else { + None + } + }); + + let locator = self.excerpt_locator_for_id(excerpt_id); + cursor.seek_forward(locator, Bias::Left, &()); + if cursor.item().is_none() { + cursor.next(&()); + } + + let position = D::from_text_summary(&cursor.start().text); + if let Some(excerpt) = cursor.item() { + if excerpt.id == excerpt_id { + let excerpt_buffer_start = + excerpt.range.context.start.summary::(&excerpt.buffer); + let excerpt_buffer_end = + excerpt.range.context.end.summary::(&excerpt.buffer); + summaries.extend( + excerpt + .buffer + .summaries_for_anchors::(excerpt_anchors) + .map(move |summary| { + let summary = cmp::min(excerpt_buffer_end.clone(), summary); + let mut position = position.clone(); + let excerpt_buffer_start = excerpt_buffer_start.clone(); + if summary > excerpt_buffer_start { + position.add_assign(&(summary - excerpt_buffer_start)); + } + position + }), + ); + continue; + } + } + + summaries.extend(excerpt_anchors.map(|_| position.clone())); + } + + summaries + } + + pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)> + where + I: 'a + IntoIterator, + { + let mut anchors = anchors.into_iter().enumerate().peekable(); + let mut cursor = self.excerpts.cursor::>(); + cursor.next(&()); + + let mut result = Vec::new(); + + while let Some((_, anchor)) = anchors.peek() { + let old_excerpt_id = anchor.excerpt_id; + + // Find the location where this anchor's excerpt should be. + let old_locator = self.excerpt_locator_for_id(old_excerpt_id); + cursor.seek_forward(&Some(old_locator), Bias::Left, &()); + + if cursor.item().is_none() { + cursor.next(&()); + } + + let next_excerpt = cursor.item(); + let prev_excerpt = cursor.prev_item(); + + // Process all of the anchors for this excerpt. + while let Some((_, anchor)) = anchors.peek() { + if anchor.excerpt_id != old_excerpt_id { + break; + } + let (anchor_ix, anchor) = anchors.next().unwrap(); + let mut anchor = *anchor; + + // Leave min and max anchors unchanged if invalid or + // if the old excerpt still exists at this location + let mut kept_position = next_excerpt + .map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor)) + || old_excerpt_id == ExcerptId::max() + || old_excerpt_id == ExcerptId::min(); + + // If the old excerpt no longer exists at this location, then attempt to + // find an equivalent position for this anchor in an adjacent excerpt. + if !kept_position { + for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { + if excerpt.contains(&anchor) { + anchor.excerpt_id = excerpt.id.clone(); + kept_position = true; + break; + } + } + } + + // If there's no adjacent excerpt that contains the anchor's position, + // then report that the anchor has lost its position. + if !kept_position { + anchor = if let Some(excerpt) = next_excerpt { + let mut text_anchor = excerpt + .range + .context + .start + .bias(anchor.text_anchor.bias, &excerpt.buffer); + if text_anchor + .cmp(&excerpt.range.context.end, &excerpt.buffer) + .is_gt() + { + text_anchor = excerpt.range.context.end; + } + Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor, + } + } else if let Some(excerpt) = prev_excerpt { + let mut text_anchor = excerpt + .range + .context + .end + .bias(anchor.text_anchor.bias, &excerpt.buffer); + if text_anchor + .cmp(&excerpt.range.context.start, &excerpt.buffer) + .is_lt() + { + text_anchor = excerpt.range.context.start; + } + Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor, + } + } else if anchor.text_anchor.bias == Bias::Left { + Anchor::min() + } else { + Anchor::max() + }; + } + + result.push((anchor_ix, anchor, kept_position)); + } + } + result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self)); + result + } + + pub fn anchor_before(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Left) + } + + pub fn anchor_after(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Right) + } + + pub fn anchor_at(&self, position: T, mut bias: Bias) -> Anchor { + let offset = position.to_offset(self); + if let Some((excerpt_id, buffer_id, buffer)) = self.as_singleton() { + return Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: buffer.anchor_at(offset, bias), + }; + } + + let mut cursor = self.excerpts.cursor::<(usize, Option)>(); + cursor.seek(&offset, Bias::Right, &()); + if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left { + cursor.prev(&()); + } + if let Some(excerpt) = cursor.item() { + let mut overshoot = offset.saturating_sub(cursor.start().0); + if excerpt.has_trailing_newline && offset == cursor.end(&()).0 { + overshoot -= 1; + bias = Bias::Right; + } + + let buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let text_anchor = + excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias)); + Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor, + } + } else if offset == 0 && bias == Bias::Left { + Anchor::min() + } else { + Anchor::max() + } + } + + pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor { + let locator = self.excerpt_locator_for_id(excerpt_id); + let mut cursor = self.excerpts.cursor::>(); + cursor.seek(locator, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == excerpt_id { + let text_anchor = excerpt.clip_anchor(text_anchor); + drop(cursor); + return Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id, + text_anchor, + }; + } + } + panic!("excerpt not found"); + } + + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { + true + } else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) { + excerpt.buffer.can_resolve(&anchor.text_anchor) + } else { + false + } + } + + pub fn excerpts( + &self, + ) -> impl Iterator)> { + self.excerpts + .iter() + .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone())) + } + + pub fn excerpt_boundaries_in_range( + &self, + range: R, + ) -> impl Iterator + '_ + where + R: RangeBounds, + T: ToOffset, + { + let start_offset; + let start = match range.start_bound() { + Bound::Included(start) => { + start_offset = start.to_offset(self); + Bound::Included(start_offset) + } + Bound::Excluded(start) => { + start_offset = start.to_offset(self); + Bound::Excluded(start_offset) + } + Bound::Unbounded => { + start_offset = 0; + Bound::Unbounded + } + }; + let end = match range.end_bound() { + Bound::Included(end) => Bound::Included(end.to_offset(self)), + Bound::Excluded(end) => Bound::Excluded(end.to_offset(self)), + Bound::Unbounded => Bound::Unbounded, + }; + let bounds = (start, end); + + let mut cursor = self.excerpts.cursor::<(usize, Point)>(); + cursor.seek(&start_offset, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + if !bounds.contains(&cursor.start().0) { + cursor.next(&()); + } + + let mut prev_buffer_id = cursor.prev_item().map(|excerpt| excerpt.buffer_id); + std::iter::from_fn(move || { + if self.singleton { + None + } else if bounds.contains(&cursor.start().0) { + let excerpt = cursor.item()?; + let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id; + let boundary = ExcerptBoundary { + id: excerpt.id.clone(), + row: cursor.start().1.row, + buffer: excerpt.buffer.clone(), + range: excerpt.range.clone(), + starts_new_buffer, + }; + + prev_buffer_id = Some(excerpt.buffer_id); + cursor.next(&()); + Some(boundary) + } else { + None + } + }) + } + + pub fn edit_count(&self) -> usize { + self.edit_count + } + + pub fn parse_count(&self) -> usize { + self.parse_count + } + + /// Returns the smallest enclosing bracket ranges containing the given range or + /// None if no brackets contain range or the range is not contained in a single + /// excerpt + pub fn innermost_enclosing_bracket_ranges( + &self, + range: Range, + ) -> Option<(Range, Range)> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + // Get the ranges of the innermost pair of brackets. + let mut result: Option<(Range, Range)> = None; + + let Some(enclosing_bracket_ranges) = self.enclosing_bracket_ranges(range.clone()) else { + return None; + }; + + for (open, close) in enclosing_bracket_ranges { + let len = close.end - open.start; + + if let Some((existing_open, existing_close)) = &result { + let existing_len = existing_close.end - existing_open.start; + if len > existing_len { + continue; + } + } + + result = Some((open, close)); + } + + result + } + + /// Returns enclosing bracket ranges containing the given range or returns None if the range is + /// not contained in a single excerpt + pub fn enclosing_bracket_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option, Range)> + 'a> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + self.bracket_ranges(range.clone()).map(|range_pairs| { + range_pairs + .filter(move |(open, close)| open.start <= range.start && close.end >= range.end) + }) + } + + /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is + /// not contained in a single excerpt + pub fn bracket_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option, Range)> + 'a> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let excerpt = self.excerpt_containing(range.clone()); + excerpt.map(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; + + let start_in_buffer = excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); + + excerpt + .buffer + .bracket_ranges(start_in_buffer..end_in_buffer) + .filter_map(move |(start_bracket_range, end_bracket_range)| { + if start_bracket_range.start < excerpt_buffer_start + || end_bracket_range.end > excerpt_buffer_end + { + return None; + } + + let mut start_bracket_range = start_bracket_range.clone(); + start_bracket_range.start = + excerpt_offset + (start_bracket_range.start - excerpt_buffer_start); + start_bracket_range.end = + excerpt_offset + (start_bracket_range.end - excerpt_buffer_start); + + let mut end_bracket_range = end_bracket_range.clone(); + end_bracket_range.start = + excerpt_offset + (end_bracket_range.start - excerpt_buffer_start); + end_bracket_range.end = + excerpt_offset + (end_bracket_range.end - excerpt_buffer_start); + Some((start_bracket_range, end_bracket_range)) + }) + }) + } + + pub fn diagnostics_update_count(&self) -> usize { + self.diagnostics_update_count + } + + pub fn git_diff_update_count(&self) -> usize { + self.git_diff_update_count + } + + pub fn trailing_excerpt_update_count(&self) -> usize { + self.trailing_excerpt_update_count + } + + pub fn file_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc> { + self.point_to_buffer_offset(point) + .and_then(|(buffer, _)| buffer.file()) + } + + pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc> { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_at(offset)) + } + + pub fn settings_at<'a, T: ToOffset>( + &'a self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let mut language = None; + let mut file = None; + if let Some((buffer, offset)) = self.point_to_buffer_offset(point) { + language = buffer.language_at(offset); + file = buffer.file(); + } + language_settings(language, file, cx) + } + + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) + } + + pub fn language_indent_size_at( + &self, + position: T, + cx: &AppContext, + ) -> Option { + let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?; + Some(buffer_snapshot.language_indent_size_at(offset, cx)) + } + + pub fn is_dirty(&self) -> bool { + self.is_dirty + } + + pub fn has_conflict(&self) -> bool { + self.has_conflict + } + + pub fn diagnostic_group<'a, O>( + &'a self, + group_id: usize, + ) -> impl Iterator> + 'a + where + O: text::FromAnchor + 'a, + { + self.as_singleton() + .into_iter() + .flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id)) + } + + pub fn diagnostics_in_range<'a, T, O>( + &'a self, + range: Range, + reversed: bool, + ) -> impl Iterator> + 'a + where + T: 'a + ToOffset, + O: 'a + text::FromAnchor + Ord, + { + self.as_singleton() + .into_iter() + .flat_map(move |(_, _, buffer)| { + buffer.diagnostics_in_range( + range.start.to_offset(self)..range.end.to_offset(self), + reversed, + ) + }) + } + + pub fn has_git_diffs(&self) -> bool { + for excerpt in self.excerpts.iter() { + if !excerpt.buffer.git_diff.is_empty() { + return true; + } + } + false + } + + pub fn git_diff_hunks_in_range_rev<'a>( + &'a self, + row_range: Range, + ) -> impl 'a + Iterator> { + let mut cursor = self.excerpts.cursor::(); + + cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + std::iter::from_fn(move || { + let excerpt = cursor.item()?; + let multibuffer_start = *cursor.start(); + let multibuffer_end = multibuffer_start + excerpt.text_summary.lines; + if multibuffer_start.row >= row_range.end { + return None; + } + + let mut buffer_start = excerpt.range.context.start; + let mut buffer_end = excerpt.range.context.end; + let excerpt_start_point = buffer_start.to_point(&excerpt.buffer); + let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines; + + if row_range.start > multibuffer_start.row { + let buffer_start_point = + excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0); + buffer_start = excerpt.buffer.anchor_before(buffer_start_point); + } + + if row_range.end < multibuffer_end.row { + let buffer_end_point = + excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0); + buffer_end = excerpt.buffer.anchor_before(buffer_end_point); + } + + let buffer_hunks = excerpt + .buffer + .git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end) + .filter_map(move |hunk| { + let start = multibuffer_start.row + + hunk + .buffer_range + .start + .saturating_sub(excerpt_start_point.row); + let end = multibuffer_start.row + + hunk + .buffer_range + .end + .min(excerpt_end_point.row + 1) + .saturating_sub(excerpt_start_point.row); + + Some(DiffHunk { + buffer_range: start..end, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }); + + cursor.prev(&()); + + Some(buffer_hunks) + }) + .flatten() + } + + pub fn git_diff_hunks_in_range<'a>( + &'a self, + row_range: Range, + ) -> impl 'a + Iterator> { + let mut cursor = self.excerpts.cursor::(); + + cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); + + std::iter::from_fn(move || { + let excerpt = cursor.item()?; + let multibuffer_start = *cursor.start(); + let multibuffer_end = multibuffer_start + excerpt.text_summary.lines; + if multibuffer_start.row >= row_range.end { + return None; + } + + let mut buffer_start = excerpt.range.context.start; + let mut buffer_end = excerpt.range.context.end; + let excerpt_start_point = buffer_start.to_point(&excerpt.buffer); + let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines; + + if row_range.start > multibuffer_start.row { + let buffer_start_point = + excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0); + buffer_start = excerpt.buffer.anchor_before(buffer_start_point); + } + + if row_range.end < multibuffer_end.row { + let buffer_end_point = + excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0); + buffer_end = excerpt.buffer.anchor_before(buffer_end_point); + } + + let buffer_hunks = excerpt + .buffer + .git_diff_hunks_intersecting_range(buffer_start..buffer_end) + .filter_map(move |hunk| { + let start = multibuffer_start.row + + hunk + .buffer_range + .start + .saturating_sub(excerpt_start_point.row); + let end = multibuffer_start.row + + hunk + .buffer_range + .end + .min(excerpt_end_point.row + 1) + .saturating_sub(excerpt_start_point.row); + + Some(DiffHunk { + buffer_range: start..end, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }); + + cursor.next(&()); + + Some(buffer_hunks) + }) + .flatten() + } + + pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + self.excerpt_containing(range.clone()) + .and_then(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; + + let start_in_buffer = + excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); + let mut ancestor_buffer_range = excerpt + .buffer + .range_for_syntax_ancestor(start_in_buffer..end_in_buffer)?; + ancestor_buffer_range.start = + cmp::max(ancestor_buffer_range.start, excerpt_buffer_start); + ancestor_buffer_range.end = cmp::min(ancestor_buffer_range.end, excerpt_buffer_end); + + let start = excerpt_offset + (ancestor_buffer_range.start - excerpt_buffer_start); + let end = excerpt_offset + (ancestor_buffer_range.end - excerpt_buffer_start); + Some(start..end) + }) + } + + pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option> { + let (excerpt_id, _, buffer) = self.as_singleton()?; + let outline = buffer.outline(theme)?; + Some(Outline::new( + outline + .items + .into_iter() + .map(|item| OutlineItem { + depth: item.depth, + range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start) + ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end), + text: item.text, + highlight_ranges: item.highlight_ranges, + name_ranges: item.name_ranges, + }) + .collect(), + )) + } + + pub fn symbols_containing( + &self, + offset: T, + theme: Option<&SyntaxTheme>, + ) -> Option<(u64, Vec>)> { + let anchor = self.anchor_before(offset); + let excerpt_id = anchor.excerpt_id; + let excerpt = self.excerpt(excerpt_id)?; + Some(( + excerpt.buffer_id, + excerpt + .buffer + .symbols_containing(anchor.text_anchor, theme) + .into_iter() + .flatten() + .map(|item| OutlineItem { + depth: item.depth, + range: self.anchor_in_excerpt(excerpt_id, item.range.start) + ..self.anchor_in_excerpt(excerpt_id, item.range.end), + text: item.text, + highlight_ranges: item.highlight_ranges, + name_ranges: item.name_ranges, + }) + .collect(), + )) + } + + fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator { + if id == ExcerptId::min() { + Locator::min_ref() + } else if id == ExcerptId::max() { + Locator::max_ref() + } else { + let mut cursor = self.excerpt_ids.cursor::(); + cursor.seek(&id, Bias::Left, &()); + if let Some(entry) = cursor.item() { + if entry.id == id { + return &entry.locator; + } + } + panic!("invalid excerpt id {:?}", id) + } + } + + pub fn buffer_id_for_excerpt(&self, excerpt_id: ExcerptId) -> Option { + Some(self.excerpt(excerpt_id)?.buffer_id) + } + + pub fn buffer_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&BufferSnapshot> { + Some(&self.excerpt(excerpt_id)?.buffer) + } + + fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> { + let mut cursor = self.excerpts.cursor::>(); + let locator = self.excerpt_locator_for_id(excerpt_id); + cursor.seek(&Some(locator), Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == excerpt_id { + return Some(excerpt); + } + } + None + } + + /// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts + fn excerpt_containing<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option<(&'a Excerpt, usize)> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&range.start, Bias::Right, &()); + let start_excerpt = cursor.item(); + + if range.start == range.end { + return start_excerpt.map(|excerpt| (excerpt, *cursor.start())); + } + + cursor.seek(&range.end, Bias::Right, &()); + let end_excerpt = cursor.item(); + + start_excerpt + .zip(end_excerpt) + .and_then(|(start_excerpt, end_excerpt)| { + if start_excerpt.id != end_excerpt.id { + return None; + } + + Some((start_excerpt, *cursor.start())) + }) + } + + pub fn remote_selections_in_range<'a>( + &'a self, + range: &'a Range, + ) -> impl 'a + Iterator)> { + let mut cursor = self.excerpts.cursor::(); + let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id); + let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id); + cursor.seek(start_locator, Bias::Left, &()); + cursor + .take_while(move |excerpt| excerpt.locator <= *end_locator) + .flat_map(move |excerpt| { + let mut query_range = excerpt.range.context.start..excerpt.range.context.end; + if excerpt.id == range.start.excerpt_id { + query_range.start = range.start.text_anchor; + } + if excerpt.id == range.end.excerpt_id { + query_range.end = range.end.text_anchor; + } + + excerpt + .buffer + .remote_selections_in_range(query_range) + .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| { + selections.map(move |selection| { + let mut start = Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor: selection.start, + }; + let mut end = Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor: selection.end, + }; + if range.start.cmp(&start, self).is_gt() { + start = range.start.clone(); + } + if range.end.cmp(&end, self).is_lt() { + end = range.end.clone(); + } + + ( + replica_id, + line_mode, + cursor_shape, + Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + }, + ) + }) + }) + }) + } +} + +#[cfg(any(test, feature = "test-support"))] +impl MultiBufferSnapshot { + pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { + let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); + let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); + start..end + } +} + +impl History { + fn start_transaction(&mut self, now: Instant) -> Option { + self.transaction_depth += 1; + if self.transaction_depth == 1 { + let id = self.next_transaction_id.tick(); + self.undo_stack.push(Transaction { + id, + buffer_transactions: Default::default(), + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + Some(id) + } else { + None + } + } + + fn end_transaction( + &mut self, + now: Instant, + buffer_transactions: HashMap, + ) -> bool { + assert_ne!(self.transaction_depth, 0); + self.transaction_depth -= 1; + if self.transaction_depth == 0 { + if buffer_transactions.is_empty() { + self.undo_stack.pop(); + false + } else { + self.redo_stack.clear(); + let transaction = self.undo_stack.last_mut().unwrap(); + transaction.last_edit_at = now; + for (buffer_id, transaction_id) in buffer_transactions { + transaction + .buffer_transactions + .entry(buffer_id) + .or_insert(transaction_id); + } + true + } + } else { + false + } + } + + fn push_transaction<'a, T>( + &mut self, + buffer_transactions: T, + now: Instant, + cx: &mut ModelContext, + ) where + T: IntoIterator, &'a language2::Transaction)>, + { + assert_eq!(self.transaction_depth, 0); + let transaction = Transaction { + id: self.next_transaction_id.tick(), + buffer_transactions: buffer_transactions + .into_iter() + .map(|(buffer, transaction)| (buffer.read(cx).remote_id(), transaction.id)) + .collect(), + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }; + if !transaction.buffer_transactions.is_empty() { + self.undo_stack.push(transaction); + self.redo_stack.clear(); + } + } + + fn finalize_last_transaction(&mut self) { + if let Some(transaction) = self.undo_stack.last_mut() { + transaction.suppress_grouping = true; + } + } + + fn forget(&mut self, transaction_id: TransactionId) -> Option { + if let Some(ix) = self + .undo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id) + { + Some(self.undo_stack.remove(ix)) + } else if let Some(ix) = self + .redo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id) + { + Some(self.redo_stack.remove(ix)) + } else { + None + } + } + + fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> { + self.undo_stack + .iter_mut() + .find(|transaction| transaction.id == transaction_id) + .or_else(|| { + self.redo_stack + .iter_mut() + .find(|transaction| transaction.id == transaction_id) + }) + } + + fn pop_undo(&mut self) -> Option<&mut Transaction> { + assert_eq!(self.transaction_depth, 0); + if let Some(transaction) = self.undo_stack.pop() { + self.redo_stack.push(transaction); + self.redo_stack.last_mut() + } else { + None + } + } + + fn pop_redo(&mut self) -> Option<&mut Transaction> { + assert_eq!(self.transaction_depth, 0); + if let Some(transaction) = self.redo_stack.pop() { + self.undo_stack.push(transaction); + self.undo_stack.last_mut() + } else { + None + } + } + + fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> { + let ix = self + .undo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id)?; + let transaction = self.undo_stack.remove(ix); + self.redo_stack.push(transaction); + self.redo_stack.last() + } + + fn group(&mut self) -> Option { + let mut count = 0; + let mut transactions = self.undo_stack.iter(); + if let Some(mut transaction) = transactions.next_back() { + while let Some(prev_transaction) = transactions.next_back() { + if !prev_transaction.suppress_grouping + && transaction.first_edit_at - prev_transaction.last_edit_at + <= self.group_interval + { + transaction = prev_transaction; + count += 1; + } else { + break; + } + } + } + self.group_trailing(count) + } + + fn group_until(&mut self, transaction_id: TransactionId) { + let mut count = 0; + for transaction in self.undo_stack.iter().rev() { + if transaction.id == transaction_id { + self.group_trailing(count); + break; + } else if transaction.suppress_grouping { + break; + } else { + count += 1; + } + } + } + + fn group_trailing(&mut self, n: usize) -> Option { + let new_len = self.undo_stack.len() - n; + let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len); + if let Some(last_transaction) = transactions_to_keep.last_mut() { + if let Some(transaction) = transactions_to_merge.last() { + last_transaction.last_edit_at = transaction.last_edit_at; + } + for to_merge in transactions_to_merge { + for (buffer_id, transaction_id) in &to_merge.buffer_transactions { + last_transaction + .buffer_transactions + .entry(*buffer_id) + .or_insert(*transaction_id); + } + } + } + + self.undo_stack.truncate(new_len); + self.undo_stack.last().map(|t| t.id) + } +} + +impl Excerpt { + fn new( + id: ExcerptId, + locator: Locator, + buffer_id: u64, + buffer: BufferSnapshot, + range: ExcerptRange, + has_trailing_newline: bool, + ) -> Self { + Excerpt { + id, + locator, + max_buffer_row: range.context.end.to_point(&buffer).row, + text_summary: buffer + .text_summary_for_range::(range.context.to_offset(&buffer)), + buffer_id, + buffer, + range, + has_trailing_newline, + } + } + + fn chunks_in_range(&self, range: Range, language_aware: bool) -> ExcerptChunks { + let content_start = self.range.context.start.to_offset(&self.buffer); + let chunks_start = content_start + range.start; + let chunks_end = content_start + cmp::min(range.end, self.text_summary.len); + + let footer_height = if self.has_trailing_newline + && range.start <= self.text_summary.len + && range.end > self.text_summary.len + { + 1 + } else { + 0 + }; + + let content_chunks = self.buffer.chunks(chunks_start..chunks_end, language_aware); + + ExcerptChunks { + content_chunks, + footer_height, + } + } + + fn bytes_in_range(&self, range: Range) -> ExcerptBytes { + let content_start = self.range.context.start.to_offset(&self.buffer); + let bytes_start = content_start + range.start; + let bytes_end = content_start + cmp::min(range.end, self.text_summary.len); + let footer_height = if self.has_trailing_newline + && range.start <= self.text_summary.len + && range.end > self.text_summary.len + { + 1 + } else { + 0 + }; + let content_bytes = self.buffer.bytes_in_range(bytes_start..bytes_end); + + ExcerptBytes { + content_bytes, + footer_height, + } + } + + fn reversed_bytes_in_range(&self, range: Range) -> ExcerptBytes { + let content_start = self.range.context.start.to_offset(&self.buffer); + let bytes_start = content_start + range.start; + let bytes_end = content_start + cmp::min(range.end, self.text_summary.len); + let footer_height = if self.has_trailing_newline + && range.start <= self.text_summary.len + && range.end > self.text_summary.len + { + 1 + } else { + 0 + }; + let content_bytes = self.buffer.reversed_bytes_in_range(bytes_start..bytes_end); + + ExcerptBytes { + content_bytes, + footer_height, + } + } + + fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor { + if text_anchor + .cmp(&self.range.context.start, &self.buffer) + .is_lt() + { + self.range.context.start + } else if text_anchor + .cmp(&self.range.context.end, &self.buffer) + .is_gt() + { + self.range.context.end + } else { + text_anchor + } + } + + fn contains(&self, anchor: &Anchor) -> bool { + Some(self.buffer_id) == anchor.buffer_id + && self + .range + .context + .start + .cmp(&anchor.text_anchor, &self.buffer) + .is_le() + && self + .range + .context + .end + .cmp(&anchor.text_anchor, &self.buffer) + .is_ge() + } +} + +impl ExcerptId { + pub fn min() -> Self { + Self(0) + } + + pub fn max() -> Self { + Self(usize::MAX) + } + + pub fn to_proto(&self) -> u64 { + self.0 as _ + } + + pub fn from_proto(proto: u64) -> Self { + Self(proto as _) + } + + pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering { + let a = snapshot.excerpt_locator_for_id(*self); + let b = snapshot.excerpt_locator_for_id(*other); + a.cmp(&b).then_with(|| self.0.cmp(&other.0)) + } +} + +impl Into for ExcerptId { + fn into(self) -> usize { + self.0 + } +} + +impl fmt::Debug for Excerpt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Excerpt") + .field("id", &self.id) + .field("locator", &self.locator) + .field("buffer_id", &self.buffer_id) + .field("range", &self.range) + .field("text_summary", &self.text_summary) + .field("has_trailing_newline", &self.has_trailing_newline) + .finish() + } +} + +impl sum_tree::Item for Excerpt { + type Summary = ExcerptSummary; + + fn summary(&self) -> Self::Summary { + let mut text = self.text_summary.clone(); + if self.has_trailing_newline { + text += TextSummary::from("\n"); + } + ExcerptSummary { + excerpt_id: self.id, + excerpt_locator: self.locator.clone(), + max_buffer_row: self.max_buffer_row, + text, + } + } +} + +impl sum_tree::Item for ExcerptIdMapping { + type Summary = ExcerptId; + + fn summary(&self) -> Self::Summary { + self.id + } +} + +impl sum_tree::KeyedItem for ExcerptIdMapping { + type Key = ExcerptId; + + fn key(&self) -> Self::Key { + self.id + } +} + +impl sum_tree::Summary for ExcerptId { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + *self = *other; + } +} + +impl sum_tree::Summary for ExcerptSummary { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + debug_assert!(summary.excerpt_locator > self.excerpt_locator); + self.excerpt_locator = summary.excerpt_locator.clone(); + self.text.add_summary(&summary.text, &()); + self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row); + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += &summary.text; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.len; + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize { + fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.text.len) + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator { + fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering { + Ord::cmp(&Some(self), cursor_location) + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator { + fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.excerpt_locator) + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for OffsetUtf16 { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.len_utf16; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.lines_utf16() + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self = Some(&summary.excerpt_locator); + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self = Some(summary.excerpt_id); + } +} + +impl<'a> MultiBufferRows<'a> { + pub fn seek(&mut self, row: u32) { + self.buffer_row_range = 0..0; + + self.excerpts + .seek_forward(&Point::new(row, 0), Bias::Right, &()); + if self.excerpts.item().is_none() { + self.excerpts.prev(&()); + + if self.excerpts.item().is_none() && row == 0 { + self.buffer_row_range = 0..1; + return; + } + } + + if let Some(excerpt) = self.excerpts.item() { + let overshoot = row - self.excerpts.start().row; + let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer).row; + self.buffer_row_range.start = excerpt_start + overshoot; + self.buffer_row_range.end = excerpt_start + excerpt.text_summary.lines.row + 1; + } + } +} + +impl<'a> Iterator for MultiBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + loop { + if !self.buffer_row_range.is_empty() { + let row = Some(self.buffer_row_range.start); + self.buffer_row_range.start += 1; + return Some(row); + } + self.excerpts.item()?; + self.excerpts.next(&()); + let excerpt = self.excerpts.item()?; + self.buffer_row_range.start = excerpt.range.context.start.to_point(&excerpt.buffer).row; + self.buffer_row_range.end = + self.buffer_row_range.start + excerpt.text_summary.lines.row + 1; + } + } +} + +impl<'a> MultiBufferChunks<'a> { + pub fn offset(&self) -> usize { + self.range.start + } + + pub fn seek(&mut self, offset: usize) { + self.range.start = offset; + self.excerpts.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = self.excerpts.item() { + self.excerpt_chunks = Some(excerpt.chunks_in_range( + self.range.start - self.excerpts.start()..self.range.end - self.excerpts.start(), + self.language_aware, + )); + } else { + self.excerpt_chunks = None; + } + } +} + +impl<'a> Iterator for MultiBufferChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.range.is_empty() { + None + } else if let Some(chunk) = self.excerpt_chunks.as_mut()?.next() { + self.range.start += chunk.text.len(); + Some(chunk) + } else { + self.excerpts.next(&()); + let excerpt = self.excerpts.item()?; + self.excerpt_chunks = Some(excerpt.chunks_in_range( + 0..self.range.end - self.excerpts.start(), + self.language_aware, + )); + self.next() + } + } +} + +impl<'a> MultiBufferBytes<'a> { + fn consume(&mut self, len: usize) { + self.range.start += len; + self.chunk = &self.chunk[len..]; + + if !self.range.is_empty() && self.chunk.is_empty() { + if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) { + self.chunk = chunk; + } else { + self.excerpts.next(&()); + if let Some(excerpt) = self.excerpts.item() { + let mut excerpt_bytes = + excerpt.bytes_in_range(0..self.range.end - self.excerpts.start()); + self.chunk = excerpt_bytes.next().unwrap(); + self.excerpt_bytes = Some(excerpt_bytes); + } + } + } + } +} + +impl<'a> Iterator for MultiBufferBytes<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + let chunk = self.chunk; + if chunk.is_empty() { + None + } else { + self.consume(chunk.len()); + Some(chunk) + } + } +} + +impl<'a> io::Read for MultiBufferBytes<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = cmp::min(buf.len(), self.chunk.len()); + buf[..len].copy_from_slice(&self.chunk[..len]); + if len > 0 { + self.consume(len); + } + Ok(len) + } +} + +impl<'a> ReversedMultiBufferBytes<'a> { + fn consume(&mut self, len: usize) { + self.range.end -= len; + self.chunk = &self.chunk[..self.chunk.len() - len]; + + if !self.range.is_empty() && self.chunk.is_empty() { + if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) { + self.chunk = chunk; + } else { + self.excerpts.next(&()); + if let Some(excerpt) = self.excerpts.item() { + let mut excerpt_bytes = + excerpt.bytes_in_range(0..self.range.end - self.excerpts.start()); + self.chunk = excerpt_bytes.next().unwrap(); + self.excerpt_bytes = Some(excerpt_bytes); + } + } + } + } +} + +impl<'a> io::Read for ReversedMultiBufferBytes<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = cmp::min(buf.len(), self.chunk.len()); + buf[..len].copy_from_slice(&self.chunk[..len]); + buf[..len].reverse(); + if len > 0 { + self.consume(len); + } + Ok(len) + } +} +impl<'a> Iterator for ExcerptBytes<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if let Some(chunk) = self.content_bytes.next() { + if !chunk.is_empty() { + return Some(chunk); + } + } + + if self.footer_height > 0 { + let result = &NEWLINES[..self.footer_height]; + self.footer_height = 0; + return Some(result); + } + + None + } +} + +impl<'a> Iterator for ExcerptChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if let Some(chunk) = self.content_chunks.next() { + return Some(chunk); + } + + if self.footer_height > 0 { + let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.footer_height]) }; + self.footer_height = 0; + return Some(Chunk { + text, + ..Default::default() + }); + } + + None + } +} + +impl ToOffset for Point { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_to_offset(*self) + } +} + +impl ToOffset for usize { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + assert!(*self <= snapshot.len(), "offset is out of range"); + *self + } +} + +impl ToOffset for OffsetUtf16 { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.offset_utf16_to_offset(*self) + } +} + +impl ToOffset for PointUtf16 { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) + } +} + +impl ToOffsetUtf16 for OffsetUtf16 { + fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { + *self + } +} + +impl ToOffsetUtf16 for usize { + fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { + snapshot.offset_to_offset_utf16(*self) + } +} + +impl ToPoint for usize { + fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point { + snapshot.offset_to_point(*self) + } +} + +impl ToPoint for Point { + fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point { + *self + } +} + +impl ToPointUtf16 for usize { + fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 { + snapshot.offset_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for Point { + fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 { + snapshot.point_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for PointUtf16 { + fn to_point_utf16<'a>(&self, _: &MultiBufferSnapshot) -> PointUtf16 { + *self + } +} + +fn build_excerpt_ranges( + buffer: &BufferSnapshot, + ranges: &[Range], + context_line_count: u32, +) -> (Vec>, Vec) +where + T: text::ToPoint, +{ + let max_point = buffer.max_point(); + let mut range_counts = Vec::new(); + let mut excerpt_ranges = Vec::new(); + let mut range_iter = ranges + .iter() + .map(|range| range.start.to_point(buffer)..range.end.to_point(buffer)) + .peekable(); + while let Some(range) = range_iter.next() { + let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); + let mut excerpt_end = Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); + let mut ranges_in_excerpt = 1; + + while let Some(next_range) = range_iter.peek() { + if next_range.start.row <= excerpt_end.row + context_line_count { + excerpt_end = + Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); + ranges_in_excerpt += 1; + range_iter.next(); + } else { + break; + } + } + + excerpt_ranges.push(ExcerptRange { + context: excerpt_start..excerpt_end, + primary: Some(range), + }); + range_counts.push(ranges_in_excerpt); + } + + (excerpt_ranges, range_counts) +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::StreamExt; + use gpui2::{AppContext, Context, TestAppContext}; + use language2::{Buffer, Rope}; + use parking_lot::RwLock; + use rand::prelude::*; + use settings2::SettingsStore; + use std::env; + use util::test::sample_text; + + #[gpui2::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'))); + let multibuffer = cx.build_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), buffer.read(cx).text()); + + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + (0..buffer.read(cx).row_count()) + .map(Some) + .collect::>() + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(snapshot.text(), buffer.read(cx).text()); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + (0..buffer.read(cx).row_count()) + .map(Some) + .collect::>() + ); + } + + #[gpui2::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| { + let state = host_buffer.read(cx).to_proto(); + let ops = cx + .executor() + .block(host_buffer.read(cx).serialize_ops(None, cx)); + let mut buffer = Buffer::from_proto(1, state, None).unwrap(); + buffer + .apply_ops( + ops.into_iter() + .map(|op| language2::proto::deserialize_operation(op).unwrap()), + cx, + ) + .unwrap(); + buffer + }); + let multibuffer = cx.build_model(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "a"); + + guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "ab"); + + guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "abc"); + } + + #[gpui2::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'))); + let buffer_2 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g'))); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + + let events = Arc::new(RwLock::new(Vec::::new())); + multibuffer.update(cx, |_, cx| { + let events = events.clone(); + cx.subscribe(&multibuffer, move |_, _, event, _| { + if let Event::Edited { .. } = event { + events.write().push(event.clone()) + } + }) + .detach(); + }); + + let subscription = multibuffer.update(cx, |multibuffer, cx| { + let subscription = multibuffer.subscribe(); + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: Point::new(1, 2)..Point::new(2, 5), + primary: None, + }], + cx, + ); + assert_eq!( + subscription.consume().into_inner(), + [Edit { + old: 0..0, + new: 0..10 + }] + ); + + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: Point::new(3, 3)..Point::new(4, 4), + primary: None, + }], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ExcerptRange { + context: Point::new(3, 1)..Point::new(3, 3), + primary: None, + }], + cx, + ); + assert_eq!( + subscription.consume().into_inner(), + [Edit { + old: 10..10, + new: 10..22 + }] + ); + + subscription + }); + + // Adding excerpts emits an edited event. + assert_eq!( + events.read().as_slice(), + &[ + Event::Edited { + sigleton_buffer_edited: false + }, + Event::Edited { + sigleton_buffer_edited: false + }, + Event::Edited { + sigleton_buffer_edited: false + } + ] + ); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + concat!( + "bbbb\n", // Preserve newlines + "ccccc\n", // + "ddd\n", // + "eeee\n", // + "jj" // + ) + ); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + [Some(1), Some(2), Some(3), Some(4), Some(3)] + ); + assert_eq!( + snapshot.buffer_rows(2).collect::>(), + [Some(3), Some(4), Some(3)] + ); + assert_eq!(snapshot.buffer_rows(4).collect::>(), [Some(3)]); + assert_eq!(snapshot.buffer_rows(5).collect::>(), []); + + assert_eq!( + boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot), + &[ + (0, "bbbb\nccccc".to_string(), true), + (2, "ddd\neeee".to_string(), false), + (4, "jj".to_string(), true), + ] + ); + assert_eq!( + boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot), + &[(0, "bbbb\nccccc".to_string(), true)] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot), + &[] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot), + &[] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot), + &[(2, "ddd\neeee".to_string(), false)] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot), + &[(2, "ddd\neeee".to_string(), false)] + ); + assert_eq!( + boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot), + &[(2, "ddd\neeee".to_string(), false)] + ); + assert_eq!( + boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot), + &[(4, "jj".to_string(), true)] + ); + assert_eq!( + boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot), + &[] + ); + + buffer_1.update(cx, |buffer, cx| { + let text = "\n"; + buffer.edit( + [ + (Point::new(0, 0)..Point::new(0, 0), text), + (Point::new(2, 1)..Point::new(2, 3), text), + ], + None, + cx, + ); + }); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + concat!( + "bbbb\n", // Preserve newlines + "c\n", // + "cc\n", // + "ddd\n", // + "eeee\n", // + "jj" // + ) + ); + + assert_eq!( + subscription.consume().into_inner(), + [Edit { + old: 6..8, + new: 6..7 + }] + ); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.clip_point(Point::new(0, 5), Bias::Left), + Point::new(0, 4) + ); + assert_eq!( + snapshot.clip_point(Point::new(0, 5), Bias::Right), + Point::new(0, 4) + ); + assert_eq!( + snapshot.clip_point(Point::new(5, 1), Bias::Right), + Point::new(5, 1) + ); + assert_eq!( + snapshot.clip_point(Point::new(5, 2), Bias::Right), + Point::new(5, 2) + ); + assert_eq!( + snapshot.clip_point(Point::new(5, 3), Bias::Right), + Point::new(5, 2) + ); + + let snapshot = multibuffer.update(cx, |multibuffer, cx| { + let (buffer_2_excerpt_id, _) = + multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone(); + multibuffer.remove_excerpts([buffer_2_excerpt_id], cx); + multibuffer.snapshot(cx) + }); + + assert_eq!( + snapshot.text(), + concat!( + "bbbb\n", // Preserve newlines + "c\n", // + "cc\n", // + "ddd\n", // + "eeee", // + ) + ); + + fn boundaries_in_range( + range: Range, + snapshot: &MultiBufferSnapshot, + ) -> Vec<(u32, String, bool)> { + snapshot + .excerpt_boundaries_in_range(range) + .map(|boundary| { + ( + boundary.row, + boundary + .buffer + .text_for_range(boundary.range.context) + .collect::(), + boundary.starts_new_buffer, + ) + }) + .collect::>() + } + } + + #[gpui2::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'))); + let buffer_2 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm'))); + + let leader_multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let follower_multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let follower_edit_event_count = Arc::new(RwLock::new(0)); + + follower_multibuffer.update(cx, |_, cx| { + let follower_edit_event_count = follower_edit_event_count.clone(); + cx.subscribe( + &leader_multibuffer, + move |follower, _, event, cx| match event.clone() { + Event::ExcerptsAdded { + buffer, + predecessor, + excerpts, + } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx), + Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx), + Event::Edited { .. } => { + *follower_edit_event_count.write() += 1; + } + _ => {} + }, + ) + .detach(); + }); + + leader_multibuffer.update(cx, |leader, cx| { + leader.push_excerpts( + buffer_1.clone(), + [ + ExcerptRange { + context: 0..8, + primary: None, + }, + ExcerptRange { + context: 12..16, + primary: None, + }, + ], + cx, + ); + leader.insert_excerpts_after( + leader.excerpt_ids()[0], + buffer_2.clone(), + [ + ExcerptRange { + context: 0..5, + primary: None, + }, + ExcerptRange { + context: 10..15, + primary: None, + }, + ], + cx, + ) + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 2); + + leader_multibuffer.update(cx, |leader, cx| { + let excerpt_ids = leader.excerpt_ids(); + leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 3); + + // Removing an empty set of excerpts is a noop. + leader_multibuffer.update(cx, |leader, cx| { + leader.remove_excerpts([], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 3); + + // Adding an empty set of excerpts is a noop. + leader_multibuffer.update(cx, |leader, cx| { + leader.push_excerpts::(buffer_2.clone(), [], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 3); + + leader_multibuffer.update(cx, |leader, cx| { + leader.clear(cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 4); + } + + #[gpui2::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'))); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.push_excerpts_with_context_lines( + buffer.clone(), + vec![ + Point::new(3, 2)..Point::new(4, 2), + Point::new(7, 1)..Point::new(7, 3), + Point::new(15, 0)..Point::new(15, 0), + ], + 2, + cx, + ) + }); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + + #[gpui2::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'))); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { + let snapshot = buffer.read(cx); + let ranges = vec![ + snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)), + snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)), + snapshot.anchor_before(Point::new(15, 0)) + ..snapshot.anchor_before(Point::new(15, 0)), + ]; + multibuffer.stream_excerpts_with_context_lines(buffer.clone(), ranges, 2, cx) + }); + + let anchor_ranges = anchor_ranges.collect::>().await; + + let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx)); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + + #[gpui2::test] + fn test_empty_multibuffer(cx: &mut AppContext) { + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), ""); + assert_eq!(snapshot.buffer_rows(0).collect::>(), &[Some(0)]); + assert_eq!(snapshot.buffer_rows(1).collect::>(), &[]); + } + + #[gpui2::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)); + let old_snapshot = multibuffer.read(cx).snapshot(cx); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "X")], None, cx); + buffer.edit([(5..5, "Y")], None, cx); + }); + let new_snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(old_snapshot.text(), "abcd"); + assert_eq!(new_snapshot.text(), "XabcdY"); + + assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0); + assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1); + assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5); + assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6); + } + + #[gpui2::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")); + let multibuffer = cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: 0..4, + primary: None, + }], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ExcerptRange { + context: 0..5, + primary: None, + }], + cx, + ); + multibuffer + }); + let old_snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0); + assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0); + assert_eq!(Anchor::min().to_offset(&old_snapshot), 0); + assert_eq!(Anchor::min().to_offset(&old_snapshot), 0); + assert_eq!(Anchor::max().to_offset(&old_snapshot), 10); + assert_eq!(Anchor::max().to_offset(&old_snapshot), 10); + + buffer_1.update(cx, |buffer, cx| { + buffer.edit([(0..0, "W")], None, cx); + buffer.edit([(5..5, "X")], None, cx); + }); + buffer_2.update(cx, |buffer, cx| { + buffer.edit([(0..0, "Y")], None, cx); + buffer.edit([(6..6, "Z")], None, cx); + }); + let new_snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(old_snapshot.text(), "abcd\nefghi"); + assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ"); + + assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0); + assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1); + assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2); + assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2); + assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3); + assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3); + assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7); + assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8); + assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13); + assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14); + } + + #[gpui2::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 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP")); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + + // Create an insertion id in buffer 1 that doesn't exist in buffer 2. + // Add an excerpt from buffer 1 that spans this new insertion. + buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx)); + let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| { + multibuffer + .push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: 0..7, + primary: None, + }], + cx, + ) + .pop() + .unwrap() + }); + + let snapshot_1 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_1.text(), "abcd123"); + + // Replace the buffer 1 excerpt with new excerpts from buffer 2. + let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([excerpt_id_1], cx); + let mut ids = multibuffer + .push_excerpts( + buffer_2.clone(), + [ + ExcerptRange { + context: 0..4, + primary: None, + }, + ExcerptRange { + context: 6..10, + primary: None, + }, + ExcerptRange { + context: 12..16, + primary: None, + }, + ], + cx, + ) + .into_iter(); + (ids.next().unwrap(), ids.next().unwrap()) + }); + let snapshot_2 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP"); + + // The old excerpt id doesn't get reused. + assert_ne!(excerpt_id_2, excerpt_id_1); + + // Resolve some anchors from the previous snapshot in the new snapshot. + // The current excerpts are from a different buffer, so we don't attempt to + // resolve the old text anchor in the new buffer. + assert_eq!( + snapshot_2.summary_for_anchor::(&snapshot_1.anchor_before(2)), + 0 + ); + assert_eq!( + snapshot_2.summaries_for_anchors::(&[ + snapshot_1.anchor_before(2), + snapshot_1.anchor_after(3) + ]), + vec![0, 0] + ); + + // Refresh anchors from the old snapshot. The return value indicates that both + // anchors lost their original excerpt. + let refresh = + snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]); + assert_eq!( + refresh, + &[ + (0, snapshot_2.anchor_before(0), false), + (1, snapshot_2.anchor_after(0), false), + ] + ); + + // Replace the middle excerpt with a smaller excerpt in buffer 2, + // that intersects the old excerpt. + let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([excerpt_id_3], cx); + multibuffer + .insert_excerpts_after( + excerpt_id_2, + buffer_2.clone(), + [ExcerptRange { + context: 5..8, + primary: None, + }], + cx, + ) + .pop() + .unwrap() + }); + + let snapshot_3 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP"); + assert_ne!(excerpt_id_5, excerpt_id_3); + + // Resolve some anchors from the previous snapshot in the new snapshot. + // The third anchor can't be resolved, since its excerpt has been removed, + // so it resolves to the same position as its predecessor. + let anchors = [ + snapshot_2.anchor_before(0), + snapshot_2.anchor_after(2), + snapshot_2.anchor_after(6), + snapshot_2.anchor_after(14), + ]; + assert_eq!( + snapshot_3.summaries_for_anchors::(&anchors), + &[0, 2, 9, 13] + ); + + let new_anchors = snapshot_3.refresh_anchors(&anchors); + assert_eq!( + new_anchors.iter().map(|a| (a.0, a.2)).collect::>(), + &[(0, true), (1, true), (2, true), (3, true)] + ); + assert_eq!( + snapshot_3.summaries_for_anchors::(new_anchors.iter().map(|a| &a.1)), + &[0, 2, 7, 13] + ); + } + + #[gpui2::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")) + .unwrap_or(10); + + let mut buffers: Vec> = Vec::new(); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let mut excerpt_ids = Vec::::new(); + let mut expected_excerpts = Vec::<(Model, Range)>::new(); + let mut anchors = Vec::new(); + let mut old_versions = Vec::new(); + + for _ in 0..operations { + match rng.gen_range(0..100) { + 0..=19 if !buffers.is_empty() => { + let buffer = buffers.choose(&mut rng).unwrap(); + buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx)); + } + 20..=29 if !expected_excerpts.is_empty() => { + let mut ids_to_remove = vec![]; + for _ in 0..rng.gen_range(1..=3) { + if expected_excerpts.is_empty() { + break; + } + + let ix = rng.gen_range(0..expected_excerpts.len()); + ids_to_remove.push(excerpt_ids.remove(ix)); + let (buffer, range) = expected_excerpts.remove(ix); + let buffer = buffer.read(cx); + log::info!( + "Removing excerpt {}: {:?}", + ix, + buffer + .text_for_range(range.to_offset(buffer)) + .collect::(), + ); + } + let snapshot = multibuffer.read(cx).read(cx); + ids_to_remove.sort_unstable_by(|a, b| a.cmp(&b, &snapshot)); + drop(snapshot); + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts(ids_to_remove, cx) + }); + } + 30..=39 if !expected_excerpts.is_empty() => { + let multibuffer = multibuffer.read(cx).read(cx); + let offset = + multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left); + let bias = if rng.gen() { Bias::Left } else { Bias::Right }; + log::info!("Creating anchor at {} with bias {:?}", offset, bias); + anchors.push(multibuffer.anchor_at(offset, bias)); + anchors.sort_by(|a, b| a.cmp(b, &multibuffer)); + } + 40..=44 if !anchors.is_empty() => { + let multibuffer = multibuffer.read(cx).read(cx); + let prev_len = anchors.len(); + anchors = multibuffer + .refresh_anchors(&anchors) + .into_iter() + .map(|a| a.1) + .collect(); + + // Ensure the newly-refreshed anchors point to a valid excerpt and don't + // overshoot its boundaries. + assert_eq!(anchors.len(), prev_len); + for anchor in &anchors { + if anchor.excerpt_id == ExcerptId::min() + || anchor.excerpt_id == ExcerptId::max() + { + continue; + } + + let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap(); + assert_eq!(excerpt.id, anchor.excerpt_id); + assert!(excerpt.contains(anchor)); + } + } + _ => { + let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) { + let base_text = util::RandomCharIter::new(&mut rng) + .take(10) + .collect::(); + buffers.push( + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text)), + ); + buffers.last().unwrap() + } else { + buffers.choose(&mut rng).unwrap() + }; + + let buffer = buffer_handle.read(cx); + let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right); + let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix); + let prev_excerpt_ix = rng.gen_range(0..=expected_excerpts.len()); + let prev_excerpt_id = excerpt_ids + .get(prev_excerpt_ix) + .cloned() + .unwrap_or_else(ExcerptId::max); + let excerpt_ix = (prev_excerpt_ix + 1).min(expected_excerpts.len()); + + log::info!( + "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}", + excerpt_ix, + expected_excerpts.len(), + buffer_handle.read(cx).remote_id(), + buffer.text(), + start_ix..end_ix, + &buffer.text()[start_ix..end_ix] + ); + + let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { + multibuffer + .insert_excerpts_after( + prev_excerpt_id, + buffer_handle.clone(), + [ExcerptRange { + context: start_ix..end_ix, + primary: None, + }], + cx, + ) + .pop() + .unwrap() + }); + + excerpt_ids.insert(excerpt_ix, excerpt_id); + expected_excerpts.insert(excerpt_ix, (buffer_handle.clone(), anchor_range)); + } + } + + if rng.gen_bool(0.3) { + multibuffer.update(cx, |multibuffer, cx| { + old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe())); + }) + } + + let snapshot = multibuffer.read(cx).snapshot(cx); + + let mut excerpt_starts = Vec::new(); + let mut expected_text = String::new(); + let mut expected_buffer_rows = Vec::new(); + for (buffer, range) in &expected_excerpts { + let buffer = buffer.read(cx); + let buffer_range = range.to_offset(buffer); + + excerpt_starts.push(TextSummary::from(expected_text.as_str())); + expected_text.extend(buffer.text_for_range(buffer_range.clone())); + expected_text.push('\n'); + + let buffer_row_range = buffer.offset_to_point(buffer_range.start).row + ..=buffer.offset_to_point(buffer_range.end).row; + for row in buffer_row_range { + expected_buffer_rows.push(Some(row)); + } + } + // Remove final trailing newline. + if !expected_excerpts.is_empty() { + expected_text.pop(); + } + + // Always report one buffer row + if expected_buffer_rows.is_empty() { + expected_buffer_rows.push(Some(0)); + } + + assert_eq!(snapshot.text(), expected_text); + log::info!("MultiBuffer text: {:?}", expected_text); + + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + expected_buffer_rows, + ); + + for _ in 0..5 { + let start_row = rng.gen_range(0..=expected_buffer_rows.len()); + assert_eq!( + snapshot.buffer_rows(start_row as u32).collect::>(), + &expected_buffer_rows[start_row..], + "buffer_rows({})", + start_row + ); + } + + assert_eq!( + snapshot.max_buffer_row(), + expected_buffer_rows.into_iter().flatten().max().unwrap() + ); + + let mut excerpt_starts = excerpt_starts.into_iter(); + for (buffer, range) in &expected_excerpts { + let buffer = buffer.read(cx); + let buffer_id = buffer.remote_id(); + let buffer_range = range.to_offset(buffer); + let buffer_start_point = buffer.offset_to_point(buffer_range.start); + let buffer_start_point_utf16 = + buffer.text_summary_for_range::(0..buffer_range.start); + + let excerpt_start = excerpt_starts.next().unwrap(); + let mut offset = excerpt_start.len; + let mut buffer_offset = buffer_range.start; + let mut point = excerpt_start.lines; + let mut buffer_point = buffer_start_point; + let mut point_utf16 = excerpt_start.lines_utf16(); + let mut buffer_point_utf16 = buffer_start_point_utf16; + for ch in buffer + .snapshot() + .chunks(buffer_range.clone(), false) + .flat_map(|c| c.text.chars()) + { + for _ in 0..ch.len_utf8() { + let left_offset = snapshot.clip_offset(offset, Bias::Left); + let right_offset = snapshot.clip_offset(offset, Bias::Right); + let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left); + let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right); + assert_eq!( + left_offset, + excerpt_start.len + (buffer_left_offset - buffer_range.start), + "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}", + offset, + buffer_id, + buffer_offset, + ); + assert_eq!( + right_offset, + excerpt_start.len + (buffer_right_offset - buffer_range.start), + "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}", + offset, + buffer_id, + buffer_offset, + ); + + let left_point = snapshot.clip_point(point, Bias::Left); + let right_point = snapshot.clip_point(point, Bias::Right); + let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left); + let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right); + assert_eq!( + left_point, + excerpt_start.lines + (buffer_left_point - buffer_start_point), + "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}", + point, + buffer_id, + buffer_point, + ); + assert_eq!( + right_point, + excerpt_start.lines + (buffer_right_point - buffer_start_point), + "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}", + point, + buffer_id, + buffer_point, + ); + + assert_eq!( + snapshot.point_to_offset(left_point), + left_offset, + "point_to_offset({:?})", + left_point, + ); + assert_eq!( + snapshot.offset_to_point(left_offset), + left_point, + "offset_to_point({:?})", + left_offset, + ); + + offset += 1; + buffer_offset += 1; + if ch == '\n' { + point += Point::new(1, 0); + buffer_point += Point::new(1, 0); + } else { + point += Point::new(0, 1); + buffer_point += Point::new(0, 1); + } + } + + for _ in 0..ch.len_utf16() { + let left_point_utf16 = + snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left); + let right_point_utf16 = + snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right); + let buffer_left_point_utf16 = + buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left); + let buffer_right_point_utf16 = + buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right); + assert_eq!( + left_point_utf16, + excerpt_start.lines_utf16() + + (buffer_left_point_utf16 - buffer_start_point_utf16), + "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}", + point_utf16, + buffer_id, + buffer_point_utf16, + ); + assert_eq!( + right_point_utf16, + excerpt_start.lines_utf16() + + (buffer_right_point_utf16 - buffer_start_point_utf16), + "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}", + point_utf16, + buffer_id, + buffer_point_utf16, + ); + + if ch == '\n' { + point_utf16 += PointUtf16::new(1, 0); + buffer_point_utf16 += PointUtf16::new(1, 0); + } else { + point_utf16 += PointUtf16::new(0, 1); + buffer_point_utf16 += PointUtf16::new(0, 1); + } + } + } + } + + for (row, line) in expected_text.split('\n').enumerate() { + assert_eq!( + snapshot.line_len(row as u32), + line.len() as u32, + "line_len({}).", + row + ); + } + + let text_rope = Rope::from(expected_text.as_str()); + for _ in 0..10 { + let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); + let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + + let text_for_range = snapshot + .text_for_range(start_ix..end_ix) + .collect::(); + assert_eq!( + text_for_range, + &expected_text[start_ix..end_ix], + "incorrect text for range {:?}", + start_ix..end_ix + ); + + let excerpted_buffer_ranges = multibuffer + .read(cx) + .range_to_buffer_ranges(start_ix..end_ix, cx); + let excerpted_buffers_text = excerpted_buffer_ranges + .iter() + .map(|(buffer, buffer_range, _)| { + buffer + .read(cx) + .text_for_range(buffer_range.clone()) + .collect::() + }) + .collect::>() + .join("\n"); + assert_eq!(excerpted_buffers_text, text_for_range); + if !expected_excerpts.is_empty() { + assert!(!excerpted_buffer_ranges.is_empty()); + } + + let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]); + assert_eq!( + snapshot.text_summary_for_range::(start_ix..end_ix), + expected_summary, + "incorrect summary for range {:?}", + start_ix..end_ix + ); + } + + // Anchor resolution + let summaries = snapshot.summaries_for_anchors::(&anchors); + assert_eq!(anchors.len(), summaries.len()); + for (anchor, resolved_offset) in anchors.iter().zip(summaries) { + assert!(resolved_offset <= snapshot.len()); + assert_eq!( + snapshot.summary_for_anchor::(anchor), + resolved_offset + ); + } + + for _ in 0..10 { + let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); + assert_eq!( + snapshot.reversed_chars_at(end_ix).collect::(), + expected_text[..end_ix].chars().rev().collect::(), + ); + } + + for _ in 0..10 { + let end_ix = rng.gen_range(0..=text_rope.len()); + let start_ix = rng.gen_range(0..=end_ix); + assert_eq!( + snapshot + .bytes_in_range(start_ix..end_ix) + .flatten() + .copied() + .collect::>(), + expected_text.as_bytes()[start_ix..end_ix].to_vec(), + "bytes_in_range({:?})", + start_ix..end_ix, + ); + } + } + + let snapshot = multibuffer.read(cx).snapshot(cx); + for (old_snapshot, subscription) in old_versions { + let edits = subscription.consume().into_inner(); + + log::info!( + "applying subscription edits to old text: {:?}: {:?}", + old_snapshot.text(), + edits, + ); + + let mut text = old_snapshot.text(); + for edit in edits { + let new_text: String = snapshot.text_for_range(edit.new.clone()).collect(); + text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), snapshot.text()); + } + } + + #[gpui2::test] + fn test_history(cx: &mut AppContext) { + let test_settings = SettingsStore::test(cx); + cx.set_global(test_settings); + + let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234")); + let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678")); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let group_interval = multibuffer.read(cx).history.group_interval; + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: 0..buffer_1.read(cx).len(), + primary: None, + }], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ExcerptRange { + context: 0..buffer_2.read(cx).len(), + primary: None, + }], + cx, + ); + }); + + let mut now = Instant::now(); + + multibuffer.update(cx, |multibuffer, cx| { + let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap(); + multibuffer.edit( + [ + (Point::new(0, 0)..Point::new(0, 0), "A"), + (Point::new(1, 0)..Point::new(1, 0), "A"), + ], + None, + cx, + ); + multibuffer.edit( + [ + (Point::new(0, 1)..Point::new(0, 1), "B"), + (Point::new(1, 1)..Point::new(1, 1), "B"), + ], + None, + cx, + ); + multibuffer.end_transaction_at(now, cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + // Edit buffer 1 through the multibuffer + now += 2 * group_interval; + multibuffer.start_transaction_at(now, cx); + multibuffer.edit([(2..2, "C")], None, cx); + multibuffer.end_transaction_at(now, cx); + assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678"); + + // Edit buffer 1 independently + buffer_1.update(cx, |buffer_1, cx| { + buffer_1.start_transaction_at(now); + buffer_1.edit([(3..3, "D")], None, cx); + buffer_1.end_transaction_at(now, cx); + + now += 2 * group_interval; + buffer_1.start_transaction_at(now); + buffer_1.edit([(4..4, "E")], None, cx); + buffer_1.end_transaction_at(now, cx); + }); + assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678"); + + // An undo in the multibuffer undoes the multibuffer transaction + // and also any individual buffer edits that have occurred since + // that transaction. + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678"); + + // Undo buffer 2 independently. + buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx)); + assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678"); + + // An undo in the multibuffer undoes the components of the + // the last multibuffer transaction that are not already undone. + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678"); + + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx)); + assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678"); + + // Redo stack gets cleared after an edit. + now += 2 * group_interval; + multibuffer.start_transaction_at(now, cx); + multibuffer.edit([(0..0, "X")], None, cx); + multibuffer.end_transaction_at(now, cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678"); + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + + // Transactions can be grouped manually. + multibuffer.redo(cx); + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + multibuffer.group_until_transaction(transaction_1, cx); + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + }); + } +} From 18431051d9d750d9e66284a71f7a55a1e31c1374 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 03:23:00 +0100 Subject: [PATCH 038/156] Rework `theme2` with new theme structure (#3194) This PR reworks the theme definition in the `theme2` crate to be based off of the new theme work that @iamnbutler has been working on. We're still developing the new theme system, but it is complete enough that we can now load the default theme and use it to theme the storybook (albeit with some further refining of the color palette required). --------- Co-authored-by: Nate Butler Co-authored-by: Marshall Bowers --- Cargo.lock | 1 + crates/storybook2/src/stories/colors.rs | 7 +- crates/storybook2/src/stories/focus.rs | 16 +- crates/storybook2/src/stories/scroll.rs | 10 +- crates/storybook2/src/storybook2.rs | 3 +- crates/theme2/Cargo.toml | 12 +- crates/theme2/src/colors.rs | 143 +++ .../src/{default.rs => default_colors.rs} | 838 ++++++++++++++++-- crates/theme2/src/default_theme.rs | 58 ++ crates/theme2/src/scale.rs | 193 ++-- crates/theme2/src/settings.rs | 10 +- crates/theme2/src/syntax.rs | 227 +++++ crates/theme2/src/theme2.rs | 63 +- crates/theme2/src/utils.rs | 43 + crates/ui2/src/components/breadcrumb.rs | 24 +- crates/ui2/src/components/buffer.rs | 19 +- crates/ui2/src/components/buffer_search.rs | 4 +- crates/ui2/src/components/collab_panel.rs | 30 +- crates/ui2/src/components/context_menu.rs | 6 +- crates/ui2/src/components/icon_button.rs | 14 +- crates/ui2/src/components/keybinding.rs | 6 +- crates/ui2/src/components/list.rs | 34 +- crates/ui2/src/components/modal.rs | 10 +- crates/ui2/src/components/multi_buffer.rs | 16 +- .../ui2/src/components/notification_toast.rs | 7 +- .../ui2/src/components/notifications_panel.rs | 4 +- crates/ui2/src/components/palette.rs | 16 +- crates/ui2/src/components/panel.rs | 6 +- crates/ui2/src/components/panes.rs | 4 +- crates/ui2/src/components/player_stack.rs | 4 +- crates/ui2/src/components/project_panel.rs | 4 +- crates/ui2/src/components/status_bar.rs | 4 +- crates/ui2/src/components/tab.rs | 13 +- crates/ui2/src/components/tab_bar.rs | 4 +- crates/ui2/src/components/terminal.rs | 6 +- crates/ui2/src/components/title_bar.rs | 3 +- crates/ui2/src/components/toast.rs | 4 +- crates/ui2/src/components/toolbar.rs | 14 +- crates/ui2/src/components/traffic_lights.rs | 10 +- crates/ui2/src/components/workspace.rs | 8 +- crates/ui2/src/elements/avatar.rs | 5 +- crates/ui2/src/elements/button.rs | 18 +- crates/ui2/src/elements/details.rs | 7 +- crates/ui2/src/elements/icon.rs | 13 +- crates/ui2/src/elements/input.rs | 16 +- crates/ui2/src/elements/label.rs | 16 +- crates/ui2/src/elements/player.rs | 8 +- crates/ui2/src/elements/tool_divider.rs | 4 +- crates/ui2/src/prelude.rs | 16 +- crates/ui2/src/static_data.rs | 96 +- crates/ui2/src/story.rs | 12 +- 51 files changed, 1615 insertions(+), 494 deletions(-) create mode 100644 crates/theme2/src/colors.rs rename crates/theme2/src/{default.rs => default_colors.rs} (65%) create mode 100644 crates/theme2/src/default_theme.rs create mode 100644 crates/theme2/src/syntax.rs create mode 100644 crates/theme2/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 3aca27106c..272320895d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8759,6 +8759,7 @@ dependencies = [ "gpui2", "indexmap 1.9.3", "parking_lot 0.11.2", + "refineable", "schemars", "serde", "serde_derive", diff --git a/crates/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index afc29660ff..0dd56071c8 100644 --- a/crates/storybook2/src/stories/colors.rs +++ b/crates/storybook2/src/stories/colors.rs @@ -1,5 +1,6 @@ use crate::story::Story; use gpui2::{px, Div, Render}; +use theme2::default_color_scales; use ui::prelude::*; pub struct ColorsStory; @@ -8,7 +9,7 @@ impl Render for ColorsStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let color_scales = theme2::default_color_scales(); + let color_scales = default_color_scales(); Story::container(cx) .child(Story::title(cx, "Colors")) @@ -20,14 +21,14 @@ impl Render for ColorsStory { .gap_1() .overflow_y_scroll() .text_color(gpui2::white()) - .children(color_scales.into_iter().map(|(name, scale)| { + .children(color_scales.into_iter().map(|scale| { div() .flex() .child( div() .w(px(75.)) .line_height(px(24.)) - .child(name.to_string()), + .child(scale.name().to_string()), ) .child(div().flex().gap_1().children( (1..=12).map(|step| div().flex().size_6().bg(scale.step(cx, step))), diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index f3f6a8d5fb..aa71040b47 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -3,7 +3,7 @@ use gpui2::{ StatelessInteractive, Styled, View, VisualContext, WindowContext, }; use serde::Deserialize; -use theme2::theme; +use theme2::ActiveTheme; #[derive(Clone, Default, PartialEq, Deserialize)] struct ActionA; @@ -34,13 +34,13 @@ impl Render for FocusStory { type Element = Div, FocusEnabled>; fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { - let theme = theme(cx); - let color_1 = theme.git_created; - let color_2 = theme.git_modified; - let color_3 = theme.git_deleted; - let color_4 = theme.git_conflict; - let color_5 = theme.git_ignored; - let color_6 = theme.git_renamed; + let theme = cx.theme(); + let color_1 = theme.styles.git.created; + let color_2 = theme.styles.git.modified; + let color_3 = theme.styles.git.deleted; + let color_4 = theme.styles.git.conflict; + let color_5 = theme.styles.git.ignored; + let color_6 = theme.styles.git.renamed; let child_1 = cx.focus_handle(); let child_2 = cx.focus_handle(); diff --git a/crates/storybook2/src/stories/scroll.rs b/crates/storybook2/src/stories/scroll.rs index b504a512a6..9236629c34 100644 --- a/crates/storybook2/src/stories/scroll.rs +++ b/crates/storybook2/src/stories/scroll.rs @@ -2,7 +2,7 @@ use gpui2::{ div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, View, VisualContext, WindowContext, }; -use theme2::theme; +use theme2::ActiveTheme; pub struct ScrollStory; @@ -16,13 +16,13 @@ impl Render for ScrollStory { type Element = Div>; fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { - let theme = theme(cx); - let color_1 = theme.git_created; - let color_2 = theme.git_modified; + let theme = cx.theme(); + let color_1 = theme.styles.git.created; + let color_2 = theme.styles.git.modified; div() .id("parent") - .bg(theme.background) + .bg(theme.colors().background) .size_full() .overflow_scroll() .children((0..10).map(|row| { diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index c2903c88e1..6028695d7f 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -68,10 +68,9 @@ fn main() { let theme_registry = cx.global::(); let mut theme_settings = ThemeSettings::get_global(cx).clone(); - theme_settings.active_theme = theme_registry.get(&theme_name).unwrap(); + theme_settings.old_active_theme = theme_registry.get(&theme_name).unwrap(); ThemeSettings::override_global(theme_settings, cx); - cx.set_global(theme.clone()); ui::settings::init(cx); let window = cx.open_window( diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index 2f89425d21..6b273e5042 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -16,19 +16,19 @@ path = "src/theme2.rs" doctest = false [dependencies] -gpui2 = { path = "../gpui2" } -fs = { path = "../fs" } -schemars.workspace = true -settings2 = { path = "../settings2" } -util = { path = "../util" } - anyhow.workspace = true +fs = { path = "../fs" } +gpui2 = { path = "../gpui2" } indexmap = "1.6.2" parking_lot.workspace = true +refineable.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true +settings2 = { path = "../settings2" } toml.workspace = true +util = { path = "../util" } [dev-dependencies] gpui2 = { path = "../gpui2", features = ["test-support"] } diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs new file mode 100644 index 0000000000..d23fde1ee0 --- /dev/null +++ b/crates/theme2/src/colors.rs @@ -0,0 +1,143 @@ +use gpui2::Hsla; +use refineable::Refineable; + +use crate::{generate_struct_with_overrides, SyntaxStyles}; + +pub struct SystemColors { + pub transparent: Hsla, + pub mac_os_traffic_light_red: Hsla, + pub mac_os_traffic_light_yellow: Hsla, + pub mac_os_traffic_light_green: Hsla, +} + +#[derive(Debug, Clone, Copy)] +pub struct PlayerColor { + pub cursor: Hsla, + pub background: Hsla, + pub selection: Hsla, +} + +pub struct PlayerColors(pub Vec); + +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct StatusColors { + pub conflict: Hsla, + pub created: Hsla, + pub deleted: Hsla, + pub error: Hsla, + pub hidden: Hsla, + pub ignored: Hsla, + pub info: Hsla, + pub modified: Hsla, + pub renamed: Hsla, + pub success: Hsla, + pub warning: Hsla, +} + +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct GitStatusColors { + pub conflict: Hsla, + pub created: Hsla, + pub deleted: Hsla, + pub ignored: Hsla, + pub modified: Hsla, + pub renamed: Hsla, +} + +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct ThemeColors { + pub border: Hsla, + pub border_variant: Hsla, + pub border_focused: Hsla, + pub border_transparent: Hsla, + pub elevated_surface: Hsla, + pub surface: Hsla, + pub background: Hsla, + pub element: Hsla, + pub element_hover: Hsla, + pub element_active: Hsla, + pub element_selected: Hsla, + pub element_disabled: Hsla, + pub element_placeholder: Hsla, + pub ghost_element: Hsla, + pub ghost_element_hover: Hsla, + pub ghost_element_active: Hsla, + pub ghost_element_selected: Hsla, + pub ghost_element_disabled: Hsla, + pub text: Hsla, + pub text_muted: Hsla, + pub text_placeholder: Hsla, + pub text_disabled: Hsla, + pub text_accent: Hsla, + pub icon: Hsla, + pub icon_muted: Hsla, + pub icon_disabled: Hsla, + pub icon_placeholder: Hsla, + pub icon_accent: Hsla, + pub status_bar: Hsla, + pub title_bar: Hsla, + pub toolbar: Hsla, + pub tab_bar: Hsla, + pub editor: Hsla, + pub editor_subheader: Hsla, + pub editor_active_line: Hsla, +} + +generate_struct_with_overrides! { + ThemeStyle, + ThemeStyleOverrides, + system: SystemColors, + colors: ThemeColors, + status: StatusColors, + git: GitStatusColors, + player: PlayerColors, + syntax: SyntaxStyles +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn override_a_single_theme_color() { + let mut colors = ThemeColors::default_light(); + + let magenta: Hsla = gpui2::rgb(0xff00ff); + + assert_ne!(colors.text, magenta); + + let overrides = ThemeColorsRefinement { + text: Some(magenta), + ..Default::default() + }; + + colors.refine(&overrides); + + assert_eq!(colors.text, magenta); + } + + #[test] + fn override_multiple_theme_colors() { + let mut colors = ThemeColors::default_light(); + + let magenta: Hsla = gpui2::rgb(0xff00ff); + let green: Hsla = gpui2::rgb(0x00ff00); + + assert_ne!(colors.text, magenta); + assert_ne!(colors.background, green); + + let overrides = ThemeColorsRefinement { + text: Some(magenta), + background: Some(green), + ..Default::default() + }; + + colors.refine(&overrides); + + assert_eq!(colors.text, magenta); + assert_eq!(colors.background, green); + } +} diff --git a/crates/theme2/src/default.rs b/crates/theme2/src/default_colors.rs similarity index 65% rename from crates/theme2/src/default.rs rename to crates/theme2/src/default_colors.rs index 41d408f980..e8146cdeaa 100644 --- a/crates/theme2/src/default.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,10 +1,704 @@ -use gpui2::Rgba; +use gpui2::{hsla, FontWeight, Rgba}; use indexmap::IndexMap; -use crate::scale::{ColorScaleName, ColorScaleSet, ColorScales}; +use crate::{ + colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, + scale::{ColorScaleSet, ColorScales}, + syntax::{SyntaxStyleName, SyntaxStyles}, + SyntaxStyle, +}; + +impl Default for SystemColors { + fn default() -> Self { + Self { + transparent: hsla(0.0, 0.0, 0.0, 0.0), + mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0), + mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0), + mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0), + } + } +} + +impl Default for StatusColors { + fn default() -> Self { + Self { + conflict: gpui2::black(), + created: gpui2::black(), + deleted: gpui2::black(), + error: gpui2::black(), + hidden: gpui2::black(), + ignored: gpui2::black(), + info: gpui2::black(), + modified: gpui2::black(), + renamed: gpui2::black(), + success: gpui2::black(), + warning: gpui2::black(), + } + } +} + +impl Default for GitStatusColors { + fn default() -> Self { + Self { + conflict: gpui2::rgba(0xdec184ff).into(), + created: gpui2::rgba(0xa1c181ff).into(), + deleted: gpui2::rgba(0xd07277ff).into(), + ignored: gpui2::rgba(0x555a63ff).into(), + modified: gpui2::rgba(0x74ade8ff).into(), + renamed: gpui2::rgba(0xdec184ff).into(), + } + } +} + +impl Default for PlayerColors { + fn default() -> Self { + Self(vec![ + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + ]) + } +} + +impl SyntaxStyles { + pub fn default_light() -> Self { + use SyntaxStyleName::*; + + let neutral: ColorScaleSet = slate().into(); + + Self(IndexMap::from_iter([ + ( + Comment, + SyntaxStyle::builder().color(neutral.light(11)).build(), + ), + ( + CommentDoc, + SyntaxStyle::builder().color(neutral.light(11)).build(), + ), + ( + Primary, + SyntaxStyle::builder().color(neutral.light(12)).build(), + ), + ( + Predictive, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + Hint, + SyntaxStyle::builder() + .color(ColorScaleSet::from(cyan()).light(10)) + .build(), + ), + ( + Emphasis, + SyntaxStyle::builder().weight(FontWeight(600.0)).build(), + ), + ( + EmphasisStrong, + SyntaxStyle::builder().weight(FontWeight(800.0)).build(), + ), + ( + Title, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + LinkUri, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).light(12)) + .build(), + ), + ( + LinkText, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).light(12)) + .build(), + ), + ( + TextLiteral, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).light(12)) + .build(), + ), + ( + Punctuation, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + PunctuationBracket, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + PunctuationDelimiter, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + PunctuationSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + PunctuationListMarker, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).light(12)) + .build(), + ), + ( + String, + SyntaxStyle::builder() + .color(ColorScaleSet::from(green()).light(12)) + .build(), + ), + ( + StringSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + StringSpecialSymbol, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + StringEscape, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).light(12)) + .build(), + ), + ( + StringRegex, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).light(12)) + .build(), + ), + ( + Constructor, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).light(12)) + .build(), + ), + // TODO: Continue assigning syntax colors from here + ( + Variant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Type, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + TypeBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Variable, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + VariableSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Label, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Tag, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Attribute, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Property, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Constant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Keyword, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Enum, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Operator, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Number, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Boolean, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + ConstantBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Function, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionSpecialDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionMethod, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionMethodBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Preproc, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Embedded, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ])) + } + + pub fn default_dark() -> Self { + use SyntaxStyleName::*; + + let neutral: ColorScaleSet = slate().into(); + + Self(IndexMap::from_iter([ + ( + Comment, + SyntaxStyle::builder().color(neutral.dark(11)).build(), + ), + ( + CommentDoc, + SyntaxStyle::builder().color(neutral.dark(11)).build(), + ), + ( + Primary, + SyntaxStyle::builder().color(neutral.dark(12)).build(), + ), + ( + Predictive, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + Hint, + SyntaxStyle::builder() + .color(ColorScaleSet::from(cyan()).dark(10)) + .build(), + ), + ( + Emphasis, + SyntaxStyle::builder().weight(FontWeight(600.0)).build(), + ), + ( + EmphasisStrong, + SyntaxStyle::builder().weight(FontWeight(800.0)).build(), + ), + ( + Title, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + LinkUri, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).dark(12)) + .build(), + ), + ( + LinkText, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).dark(12)) + .build(), + ), + ( + TextLiteral, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).dark(12)) + .build(), + ), + ( + Punctuation, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + PunctuationBracket, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + PunctuationDelimiter, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + PunctuationSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + PunctuationListMarker, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).dark(12)) + .build(), + ), + ( + String, + SyntaxStyle::builder() + .color(ColorScaleSet::from(green()).dark(12)) + .build(), + ), + ( + StringSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + StringSpecialSymbol, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + StringEscape, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).dark(12)) + .build(), + ), + ( + StringRegex, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).dark(12)) + .build(), + ), + ( + Constructor, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).dark(12)) + .build(), + ), + // TODO: Continue assigning syntax colors from here + ( + Variant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Type, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + TypeBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Variable, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + VariableSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Label, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Tag, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Attribute, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Property, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Constant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Keyword, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Enum, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Operator, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Number, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Boolean, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + ConstantBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Function, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionSpecialDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionMethod, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionMethodBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Preproc, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Embedded, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ])) + } +} + +impl ThemeColors { + pub fn default_light() -> Self { + Self { + border: gpui2::white(), + border_variant: gpui2::white(), + border_focused: gpui2::white(), + border_transparent: gpui2::white(), + elevated_surface: gpui2::white(), + surface: gpui2::white(), + background: gpui2::white(), + element: gpui2::white(), + element_hover: gpui2::white(), + element_active: gpui2::white(), + element_selected: gpui2::white(), + element_disabled: gpui2::white(), + element_placeholder: gpui2::white(), + ghost_element: gpui2::white(), + ghost_element_hover: gpui2::white(), + ghost_element_active: gpui2::white(), + ghost_element_selected: gpui2::white(), + ghost_element_disabled: gpui2::white(), + text: gpui2::white(), + text_muted: gpui2::white(), + text_placeholder: gpui2::white(), + text_disabled: gpui2::white(), + text_accent: gpui2::white(), + icon: gpui2::white(), + icon_muted: gpui2::white(), + icon_disabled: gpui2::white(), + icon_placeholder: gpui2::white(), + icon_accent: gpui2::white(), + status_bar: gpui2::white(), + title_bar: gpui2::white(), + toolbar: gpui2::white(), + tab_bar: gpui2::white(), + editor: gpui2::white(), + editor_subheader: gpui2::white(), + editor_active_line: gpui2::white(), + } + } + + pub fn default_dark() -> Self { + Self { + border: gpui2::rgba(0x464b57ff).into(), + border_variant: gpui2::rgba(0x464b57ff).into(), + border_focused: gpui2::rgba(0x293b5bff).into(), + border_transparent: gpui2::rgba(0x00000000).into(), + elevated_surface: gpui2::rgba(0x3b414dff).into(), + surface: gpui2::rgba(0x2f343eff).into(), + background: gpui2::rgba(0x3b414dff).into(), + element: gpui2::rgba(0x3b414dff).into(), + element_hover: gpui2::rgba(0xffffff1e).into(), + element_active: gpui2::rgba(0xffffff28).into(), + element_selected: gpui2::rgba(0x18243dff).into(), + element_disabled: gpui2::rgba(0x00000000).into(), + element_placeholder: gpui2::black(), + ghost_element: gpui2::rgba(0x00000000).into(), + ghost_element_hover: gpui2::rgba(0xffffff14).into(), + ghost_element_active: gpui2::rgba(0xffffff1e).into(), + ghost_element_selected: gpui2::rgba(0x18243dff).into(), + ghost_element_disabled: gpui2::rgba(0x00000000).into(), + text: gpui2::rgba(0xc8ccd4ff).into(), + text_muted: gpui2::rgba(0x838994ff).into(), + text_placeholder: gpui2::rgba(0xd07277ff).into(), + text_disabled: gpui2::rgba(0x555a63ff).into(), + text_accent: gpui2::rgba(0x74ade8ff).into(), + icon: gpui2::black(), + icon_muted: gpui2::rgba(0x838994ff).into(), + icon_disabled: gpui2::black(), + icon_placeholder: gpui2::black(), + icon_accent: gpui2::black(), + status_bar: gpui2::rgba(0x3b414dff).into(), + title_bar: gpui2::rgba(0x3b414dff).into(), + toolbar: gpui2::rgba(0x282c33ff).into(), + tab_bar: gpui2::rgba(0x2f343eff).into(), + editor: gpui2::rgba(0x282c33ff).into(), + editor_subheader: gpui2::rgba(0x2f343eff).into(), + editor_active_line: gpui2::rgba(0x2f343eff).into(), + } + } +} struct DefaultColorScaleSet { - scale: ColorScaleName, + scale: &'static str, light: [&'static str; 12], light_alpha: [&'static str; 12], dark: [&'static str; 12], @@ -32,48 +726,46 @@ impl From for ColorScaleSet { } pub fn default_color_scales() -> ColorScales { - use ColorScaleName::*; - - IndexMap::from_iter([ - (Gray, gray().into()), - (Mauve, mauve().into()), - (Slate, slate().into()), - (Sage, sage().into()), - (Olive, olive().into()), - (Sand, sand().into()), - (Gold, gold().into()), - (Bronze, bronze().into()), - (Brown, brown().into()), - (Yellow, yellow().into()), - (Amber, amber().into()), - (Orange, orange().into()), - (Tomato, tomato().into()), - (Red, red().into()), - (Ruby, ruby().into()), - (Crimson, crimson().into()), - (Pink, pink().into()), - (Plum, plum().into()), - (Purple, purple().into()), - (Violet, violet().into()), - (Iris, iris().into()), - (Indigo, indigo().into()), - (Blue, blue().into()), - (Cyan, cyan().into()), - (Teal, teal().into()), - (Jade, jade().into()), - (Green, green().into()), - (Grass, grass().into()), - (Lime, lime().into()), - (Mint, mint().into()), - (Sky, sky().into()), - (Black, black().into()), - (White, white().into()), - ]) + ColorScales { + gray: gray().into(), + mauve: mauve().into(), + slate: slate().into(), + sage: sage().into(), + olive: olive().into(), + sand: sand().into(), + gold: gold().into(), + bronze: bronze().into(), + brown: brown().into(), + yellow: yellow().into(), + amber: amber().into(), + orange: orange().into(), + tomato: tomato().into(), + red: red().into(), + ruby: ruby().into(), + crimson: crimson().into(), + pink: pink().into(), + plum: plum().into(), + purple: purple().into(), + violet: violet().into(), + iris: iris().into(), + indigo: indigo().into(), + blue: blue().into(), + cyan: cyan().into(), + teal: teal().into(), + jade: jade().into(), + green: green().into(), + grass: grass().into(), + lime: lime().into(), + mint: mint().into(), + sky: sky().into(), + black: black().into(), + white: white().into(), + } } fn gray() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Gray, + scale: "Gray", light: [ "#fcfcfcff", "#f9f9f9ff", @@ -135,7 +827,7 @@ fn gray() -> DefaultColorScaleSet { fn mauve() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Mauve, + scale: "Mauve", light: [ "#fdfcfdff", "#faf9fbff", @@ -197,7 +889,7 @@ fn mauve() -> DefaultColorScaleSet { fn slate() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Slate, + scale: "Slate", light: [ "#fcfcfdff", "#f9f9fbff", @@ -259,7 +951,7 @@ fn slate() -> DefaultColorScaleSet { fn sage() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Sage, + scale: "Sage", light: [ "#fbfdfcff", "#f7f9f8ff", @@ -321,7 +1013,7 @@ fn sage() -> DefaultColorScaleSet { fn olive() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Olive, + scale: "Olive", light: [ "#fcfdfcff", "#f8faf8ff", @@ -383,7 +1075,7 @@ fn olive() -> DefaultColorScaleSet { fn sand() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Sand, + scale: "Sand", light: [ "#fdfdfcff", "#f9f9f8ff", @@ -445,7 +1137,7 @@ fn sand() -> DefaultColorScaleSet { fn gold() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Gold, + scale: "Gold", light: [ "#fdfdfcff", "#faf9f2ff", @@ -507,7 +1199,7 @@ fn gold() -> DefaultColorScaleSet { fn bronze() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Bronze, + scale: "Bronze", light: [ "#fdfcfcff", "#fdf7f5ff", @@ -569,7 +1261,7 @@ fn bronze() -> DefaultColorScaleSet { fn brown() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Brown, + scale: "Brown", light: [ "#fefdfcff", "#fcf9f6ff", @@ -631,7 +1323,7 @@ fn brown() -> DefaultColorScaleSet { fn yellow() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Yellow, + scale: "Yellow", light: [ "#fdfdf9ff", "#fefce9ff", @@ -693,7 +1385,7 @@ fn yellow() -> DefaultColorScaleSet { fn amber() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Amber, + scale: "Amber", light: [ "#fefdfbff", "#fefbe9ff", @@ -755,7 +1447,7 @@ fn amber() -> DefaultColorScaleSet { fn orange() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Orange, + scale: "Orange", light: [ "#fefcfbff", "#fff7edff", @@ -817,7 +1509,7 @@ fn orange() -> DefaultColorScaleSet { fn tomato() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Tomato, + scale: "Tomato", light: [ "#fffcfcff", "#fff8f7ff", @@ -879,7 +1571,7 @@ fn tomato() -> DefaultColorScaleSet { fn red() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Red, + scale: "Red", light: [ "#fffcfcff", "#fff7f7ff", @@ -941,7 +1633,7 @@ fn red() -> DefaultColorScaleSet { fn ruby() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Ruby, + scale: "Ruby", light: [ "#fffcfdff", "#fff7f8ff", @@ -1003,7 +1695,7 @@ fn ruby() -> DefaultColorScaleSet { fn crimson() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Crimson, + scale: "Crimson", light: [ "#fffcfdff", "#fef7f9ff", @@ -1065,7 +1757,7 @@ fn crimson() -> DefaultColorScaleSet { fn pink() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Pink, + scale: "Pink", light: [ "#fffcfeff", "#fef7fbff", @@ -1127,7 +1819,7 @@ fn pink() -> DefaultColorScaleSet { fn plum() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Plum, + scale: "Plum", light: [ "#fefcffff", "#fdf7fdff", @@ -1189,7 +1881,7 @@ fn plum() -> DefaultColorScaleSet { fn purple() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Purple, + scale: "Purple", light: [ "#fefcfeff", "#fbf7feff", @@ -1251,7 +1943,7 @@ fn purple() -> DefaultColorScaleSet { fn violet() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Violet, + scale: "Violet", light: [ "#fdfcfeff", "#faf8ffff", @@ -1313,7 +2005,7 @@ fn violet() -> DefaultColorScaleSet { fn iris() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Iris, + scale: "Iris", light: [ "#fdfdffff", "#f8f8ffff", @@ -1375,7 +2067,7 @@ fn iris() -> DefaultColorScaleSet { fn indigo() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Indigo, + scale: "Indigo", light: [ "#fdfdfeff", "#f7f9ffff", @@ -1437,7 +2129,7 @@ fn indigo() -> DefaultColorScaleSet { fn blue() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Blue, + scale: "Blue", light: [ "#fbfdffff", "#f4faffff", @@ -1499,7 +2191,7 @@ fn blue() -> DefaultColorScaleSet { fn cyan() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Cyan, + scale: "Cyan", light: [ "#fafdfeff", "#f2fafbff", @@ -1561,7 +2253,7 @@ fn cyan() -> DefaultColorScaleSet { fn teal() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Teal, + scale: "Teal", light: [ "#fafefdff", "#f3fbf9ff", @@ -1623,7 +2315,7 @@ fn teal() -> DefaultColorScaleSet { fn jade() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Jade, + scale: "Jade", light: [ "#fbfefdff", "#f4fbf7ff", @@ -1685,7 +2377,7 @@ fn jade() -> DefaultColorScaleSet { fn green() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Green, + scale: "Green", light: [ "#fbfefcff", "#f4fbf6ff", @@ -1747,7 +2439,7 @@ fn green() -> DefaultColorScaleSet { fn grass() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Grass, + scale: "Grass", light: [ "#fbfefbff", "#f5fbf5ff", @@ -1809,7 +2501,7 @@ fn grass() -> DefaultColorScaleSet { fn lime() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Lime, + scale: "Lime", light: [ "#fcfdfaff", "#f8faf3ff", @@ -1871,7 +2563,7 @@ fn lime() -> DefaultColorScaleSet { fn mint() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Mint, + scale: "Mint", light: [ "#f9fefdff", "#f2fbf9ff", @@ -1933,7 +2625,7 @@ fn mint() -> DefaultColorScaleSet { fn sky() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Sky, + scale: "Sky", light: [ "#f9feffff", "#f1fafdff", @@ -1995,7 +2687,7 @@ fn sky() -> DefaultColorScaleSet { fn black() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Black, + scale: "Black", light: [ "#0000000d", "#0000001a", @@ -2057,7 +2749,7 @@ fn black() -> DefaultColorScaleSet { fn white() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::White, + scale: "White", light: [ "#ffffff0d", "#ffffff1a", diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs new file mode 100644 index 0000000000..4b47e403d6 --- /dev/null +++ b/crates/theme2/src/default_theme.rs @@ -0,0 +1,58 @@ +use crate::{ + colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyle}, + default_color_scales, Appearance, SyntaxStyles, ThemeFamily, ThemeVariant, +}; + +fn zed_pro_daylight() -> ThemeVariant { + ThemeVariant { + id: "zed_pro_daylight".to_string(), + name: "Zed Pro Daylight".to_string(), + appearance: Appearance::Light, + styles: ThemeStyle { + system: SystemColors::default(), + colors: ThemeColors::default_light(), + status: StatusColors::default(), + git: GitStatusColors::default(), + player: PlayerColors::default(), + syntax: SyntaxStyles::default_light(), + }, + } +} + +pub(crate) fn zed_pro_moonlight() -> ThemeVariant { + ThemeVariant { + id: "zed_pro_moonlight".to_string(), + name: "Zed Pro Moonlight".to_string(), + appearance: Appearance::Light, + styles: ThemeStyle { + system: SystemColors::default(), + colors: ThemeColors::default_dark(), + status: StatusColors::default(), + git: GitStatusColors::default(), + player: PlayerColors::default(), + syntax: SyntaxStyles::default_dark(), + }, + } +} + +pub fn zed_pro_family() -> ThemeFamily { + ThemeFamily { + id: "zed_pro".to_string(), + name: "Zed Pro".to_string(), + author: "Zed Team".to_string(), + themes: vec![zed_pro_daylight(), zed_pro_moonlight()], + scales: default_color_scales(), + } +} + +impl Default for ThemeFamily { + fn default() -> Self { + zed_pro_family() + } +} + +impl Default for ThemeVariant { + fn default() -> Self { + zed_pro_daylight() + } +} diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index 22a607bf07..21c8592d81 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -1,98 +1,95 @@ -use gpui2::{AppContext, Hsla}; -use indexmap::IndexMap; +use gpui2::{AppContext, Hsla, SharedString}; -use crate::{theme, Appearance}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ColorScaleName { - Gray, - Mauve, - Slate, - Sage, - Olive, - Sand, - Gold, - Bronze, - Brown, - Yellow, - Amber, - Orange, - Tomato, - Red, - Ruby, - Crimson, - Pink, - Plum, - Purple, - Violet, - Iris, - Indigo, - Blue, - Cyan, - Teal, - Jade, - Green, - Grass, - Lime, - Mint, - Sky, - Black, - White, -} - -impl std::fmt::Display for ColorScaleName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Gray => "Gray", - Self::Mauve => "Mauve", - Self::Slate => "Slate", - Self::Sage => "Sage", - Self::Olive => "Olive", - Self::Sand => "Sand", - Self::Gold => "Gold", - Self::Bronze => "Bronze", - Self::Brown => "Brown", - Self::Yellow => "Yellow", - Self::Amber => "Amber", - Self::Orange => "Orange", - Self::Tomato => "Tomato", - Self::Red => "Red", - Self::Ruby => "Ruby", - Self::Crimson => "Crimson", - Self::Pink => "Pink", - Self::Plum => "Plum", - Self::Purple => "Purple", - Self::Violet => "Violet", - Self::Iris => "Iris", - Self::Indigo => "Indigo", - Self::Blue => "Blue", - Self::Cyan => "Cyan", - Self::Teal => "Teal", - Self::Jade => "Jade", - Self::Green => "Green", - Self::Grass => "Grass", - Self::Lime => "Lime", - Self::Mint => "Mint", - Self::Sky => "Sky", - Self::Black => "Black", - Self::White => "White", - } - ) - } -} +use crate::{ActiveTheme, Appearance}; pub type ColorScale = [Hsla; 12]; -pub type ColorScales = IndexMap; +pub struct ColorScales { + pub gray: ColorScaleSet, + pub mauve: ColorScaleSet, + pub slate: ColorScaleSet, + pub sage: ColorScaleSet, + pub olive: ColorScaleSet, + pub sand: ColorScaleSet, + pub gold: ColorScaleSet, + pub bronze: ColorScaleSet, + pub brown: ColorScaleSet, + pub yellow: ColorScaleSet, + pub amber: ColorScaleSet, + pub orange: ColorScaleSet, + pub tomato: ColorScaleSet, + pub red: ColorScaleSet, + pub ruby: ColorScaleSet, + pub crimson: ColorScaleSet, + pub pink: ColorScaleSet, + pub plum: ColorScaleSet, + pub purple: ColorScaleSet, + pub violet: ColorScaleSet, + pub iris: ColorScaleSet, + pub indigo: ColorScaleSet, + pub blue: ColorScaleSet, + pub cyan: ColorScaleSet, + pub teal: ColorScaleSet, + pub jade: ColorScaleSet, + pub green: ColorScaleSet, + pub grass: ColorScaleSet, + pub lime: ColorScaleSet, + pub mint: ColorScaleSet, + pub sky: ColorScaleSet, + pub black: ColorScaleSet, + pub white: ColorScaleSet, +} + +impl IntoIterator for ColorScales { + type Item = ColorScaleSet; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + self.gray, + self.mauve, + self.slate, + self.sage, + self.olive, + self.sand, + self.gold, + self.bronze, + self.brown, + self.yellow, + self.amber, + self.orange, + self.tomato, + self.red, + self.ruby, + self.crimson, + self.pink, + self.plum, + self.purple, + self.violet, + self.iris, + self.indigo, + self.blue, + self.cyan, + self.teal, + self.jade, + self.green, + self.grass, + self.lime, + self.mint, + self.sky, + self.black, + self.white, + ] + .into_iter() + } +} /// A one-based step in a [`ColorScale`]. pub type ColorScaleStep = usize; pub struct ColorScaleSet { - name: ColorScaleName, + name: SharedString, light: ColorScale, dark: ColorScale, light_alpha: ColorScale, @@ -101,14 +98,14 @@ pub struct ColorScaleSet { impl ColorScaleSet { pub fn new( - name: ColorScaleName, + name: impl Into, light: ColorScale, light_alpha: ColorScale, dark: ColorScale, dark_alpha: ColorScale, ) -> Self { Self { - name, + name: name.into(), light, light_alpha, dark, @@ -116,8 +113,8 @@ impl ColorScaleSet { } } - pub fn name(&self) -> String { - self.name.to_string() + pub fn name(&self) -> &SharedString { + &self.name } pub fn light(&self, step: ColorScaleStep) -> Hsla { @@ -136,27 +133,15 @@ impl ColorScaleSet { self.dark_alpha[step - 1] } - fn current_appearance(cx: &AppContext) -> Appearance { - let theme = theme(cx); - if theme.metadata.is_light { - Appearance::Light - } else { - Appearance::Dark - } - } - pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { - let appearance = Self::current_appearance(cx); - - match appearance { + match cx.theme().appearance { Appearance::Light => self.light(step), Appearance::Dark => self.dark(step), } } pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { - let appearance = Self::current_appearance(cx); - match appearance { + match cx.theme().appearance { Appearance::Light => self.light_alpha(step), Appearance::Dark => self.dark_alpha(step), } diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index 379b01dd4b..3a61bbbe1e 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -1,4 +1,4 @@ -use crate::{Theme, ThemeRegistry}; +use crate::{zed_pro_moonlight, Theme, ThemeRegistry, ThemeVariant}; use anyhow::Result; use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; use schemars::{ @@ -20,7 +20,8 @@ pub struct ThemeSettings { pub buffer_font: Font, pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, - pub active_theme: Arc, + pub active_theme: Arc, + pub old_active_theme: Arc, } #[derive(Default)] @@ -123,7 +124,8 @@ impl settings2::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + active_theme: Arc::new(zed_pro_moonlight()), + old_active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; for value in user_values.into_iter().copied().cloned() { @@ -136,7 +138,7 @@ impl settings2::Settings for ThemeSettings { if let Some(value) = &value.theme { if let Some(theme) = themes.get(value).log_err() { - this.active_theme = theme; + this.old_active_theme = theme; } } diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs new file mode 100644 index 0000000000..82c4c87796 --- /dev/null +++ b/crates/theme2/src/syntax.rs @@ -0,0 +1,227 @@ +use gpui2::{FontWeight, Hsla, SharedString}; +use indexmap::IndexMap; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SyntaxStyleName { + Comment, + CommentDoc, + Primary, + Predictive, + Hint, + Emphasis, + EmphasisStrong, + Title, + LinkUri, + LinkText, + TextLiteral, + Punctuation, + PunctuationBracket, + PunctuationDelimiter, + PunctuationSpecial, + PunctuationListMarker, + String, + StringSpecial, + StringSpecialSymbol, + StringEscape, + StringRegex, + Constructor, + Variant, + Type, + TypeBuiltin, + Variable, + VariableSpecial, + Label, + Tag, + Attribute, + Property, + Constant, + Keyword, + Enum, + Operator, + Number, + Boolean, + ConstantBuiltin, + Function, + FunctionBuiltin, + FunctionDefinition, + FunctionSpecialDefinition, + FunctionMethod, + FunctionMethodBuiltin, + Preproc, + Embedded, + Custom(SharedString), +} + +impl std::str::FromStr for SyntaxStyleName { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "attribute" => Self::Attribute, + "boolean" => Self::Boolean, + "comment" => Self::Comment, + "comment.doc" => Self::CommentDoc, + "constant" => Self::Constant, + "constructor" => Self::Constructor, + "embedded" => Self::Embedded, + "emphasis" => Self::Emphasis, + "emphasis.strong" => Self::EmphasisStrong, + "enum" => Self::Enum, + "function" => Self::Function, + "function.builtin" => Self::FunctionBuiltin, + "function.definition" => Self::FunctionDefinition, + "function.special_definition" => Self::FunctionSpecialDefinition, + "function.method" => Self::FunctionMethod, + "function.method_builtin" => Self::FunctionMethodBuiltin, + "hint" => Self::Hint, + "keyword" => Self::Keyword, + "label" => Self::Label, + "link_text" => Self::LinkText, + "link_uri" => Self::LinkUri, + "number" => Self::Number, + "operator" => Self::Operator, + "predictive" => Self::Predictive, + "preproc" => Self::Preproc, + "primary" => Self::Primary, + "property" => Self::Property, + "punctuation" => Self::Punctuation, + "punctuation.bracket" => Self::PunctuationBracket, + "punctuation.delimiter" => Self::PunctuationDelimiter, + "punctuation.list_marker" => Self::PunctuationListMarker, + "punctuation.special" => Self::PunctuationSpecial, + "string" => Self::String, + "string.escape" => Self::StringEscape, + "string.regex" => Self::StringRegex, + "string.special" => Self::StringSpecial, + "string.special.symbol" => Self::StringSpecialSymbol, + "tag" => Self::Tag, + "text.literal" => Self::TextLiteral, + "title" => Self::Title, + "type" => Self::Type, + "type.builtin" => Self::TypeBuiltin, + "variable" => Self::Variable, + "variable.special" => Self::VariableSpecial, + "constant.builtin" => Self::ConstantBuiltin, + "variant" => Self::Variant, + name => Self::Custom(name.to_string().into()), + }) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SyntaxStyle { + pub color: Hsla, + pub weight: FontWeight, + pub underline: bool, + pub italic: bool, + // Nate: In the future I'd like to enable using background highlights for syntax highlighting + // pub highlight: Hsla, +} + +impl SyntaxStyle { + pub fn builder() -> SyntaxStyleBuilder { + SyntaxStyleBuilder::new() + } +} + +impl Default for SyntaxStyle { + fn default() -> Self { + Self { + color: gpui2::black(), + weight: FontWeight::default(), + italic: false, + underline: false, + } + } +} + +pub struct SyntaxStyleBuilder { + pub color: Hsla, + pub weight: FontWeight, + pub underline: bool, + pub italic: bool, +} + +impl SyntaxStyleBuilder { + pub fn new() -> Self { + SyntaxStyleBuilder { + color: gpui2::black(), + weight: FontWeight::default(), + underline: false, + italic: false, + } + } + + pub fn color(mut self, color: Hsla) -> Self { + self.color = color; + self + } + + pub fn weight(mut self, weight: FontWeight) -> Self { + self.weight = weight; + self + } + + pub fn underline(mut self, underline: bool) -> Self { + self.underline = underline; + self + } + + pub fn italic(mut self, italic: bool) -> Self { + self.italic = italic; + self + } + + pub fn build(self) -> SyntaxStyle { + SyntaxStyle { + color: self.color, + weight: self.weight, + underline: self.underline, + italic: self.italic, + } + } +} + +pub struct SyntaxStyles(pub IndexMap); + +impl SyntaxStyles { + // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? + pub fn new_test(colors: impl IntoIterator) -> Self { + Self(IndexMap::from_iter(colors.into_iter().map( + |(name, color)| { + ( + name.parse().unwrap(), + SyntaxStyle::builder().color(color).build(), + ) + }, + ))) + } + + pub fn get(&self, name: &str) -> SyntaxStyle { + self.0 + .get(&name.parse::().unwrap()) + .cloned() + .unwrap_or_default() + } + + pub fn color(&self, name: &str) -> Hsla { + self.get(name).color + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_syntax_style_name() { + let name = "comment".parse::().unwrap(); + assert_eq!(name, SyntaxStyleName::Comment); + } + + #[test] + fn create_custom_syntax_style_name() { + let name = "custom".parse::().unwrap(); + assert_eq!(name, SyntaxStyleName::Custom("custom".into())); + } +} diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index b96a23c338..ea1ad5b26c 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -1,17 +1,25 @@ -mod default; +mod colors; +mod default_colors; +mod default_theme; mod registry; mod scale; mod settings; +mod syntax; mod themes; +mod utils; -pub use default::*; +pub use colors::*; +pub use default_colors::*; +pub use default_theme::*; pub use registry::*; pub use scale::*; pub use settings::*; +pub use syntax::*; + +use std::sync::Arc; use gpui2::{AppContext, HighlightStyle, Hsla, SharedString}; use settings2::Settings; -use std::sync::Arc; #[derive(Debug, Clone, PartialEq)] pub enum Appearance { @@ -24,12 +32,53 @@ pub fn init(cx: &mut AppContext) { ThemeSettings::register(cx); } -pub fn active_theme<'a>(cx: &'a AppContext) -> &'a Arc { - &ThemeSettings::get_global(cx).active_theme +pub trait ActiveTheme { + fn theme(&self) -> &ThemeVariant; } -pub fn theme(cx: &AppContext) -> Arc { - active_theme(cx).clone() +impl ActiveTheme for AppContext { + fn theme(&self) -> &ThemeVariant { + &ThemeSettings::get_global(self).active_theme + } +} + +pub struct ThemeFamily { + #[allow(dead_code)] + pub(crate) id: String, + pub name: String, + pub author: String, + pub themes: Vec, + pub scales: ColorScales, +} + +impl ThemeFamily {} + +pub struct ThemeVariant { + #[allow(dead_code)] + pub(crate) id: String, + pub name: String, + pub appearance: Appearance, + pub styles: ThemeStyle, +} + +impl ThemeVariant { + /// Returns the [`ThemeColors`] for the theme. + #[inline(always)] + pub fn colors(&self) -> &ThemeColors { + &self.styles.colors + } + + /// Returns the [`SyntaxStyles`] for the theme. + #[inline(always)] + pub fn syntax(&self) -> &SyntaxStyles { + &self.styles.syntax + } + + /// Returns the color for the syntax node with the given name. + #[inline(always)] + pub fn syntax_color(&self, name: &str) -> Hsla { + self.syntax().color(name) + } } pub struct Theme { diff --git a/crates/theme2/src/utils.rs b/crates/theme2/src/utils.rs new file mode 100644 index 0000000000..ccdcde4274 --- /dev/null +++ b/crates/theme2/src/utils.rs @@ -0,0 +1,43 @@ +/// This macro generates a struct and a corresponding struct with optional fields. +/// +/// It takes as input the name of the struct to be generated, the name of the struct with optional fields, +/// and a list of field names along with their types. +/// +/// # Example +/// ``` +/// generate_struct_with_overrides!( +/// MyStruct, +/// MyStructOverride, +/// field1: i32, +/// field2: String +/// ); +/// ``` +/// This will generate the following structs: +/// ``` +/// pub struct MyStruct { +/// pub field1: i32, +/// pub field2: String, +/// } +/// +/// pub struct MyStructOverride { +/// pub field1: Option, +/// pub field2: Option, +/// } +/// ``` +#[macro_export] +macro_rules! generate_struct_with_overrides { + ($struct_name:ident, $struct_override_name:ident, $($field:ident: $type:ty),*) => { + pub struct $struct_name { + $( + pub $field: $type, + )* + } + + #[allow(dead_code)] + pub struct $struct_override_name { + $( + pub $field: Option<$type>, + )* + } + }; +} diff --git a/crates/ui2/src/components/breadcrumb.rs b/crates/ui2/src/components/breadcrumb.rs index 6b2dfe1cce..163dfabfb0 100644 --- a/crates/ui2/src/components/breadcrumb.rs +++ b/crates/ui2/src/components/breadcrumb.rs @@ -19,24 +19,22 @@ impl Breadcrumb { } fn render_separator(&self, cx: &WindowContext) -> Div { - let theme = theme(cx); - - div().child(" › ").text_color(theme.text_muted) + div() + .child(" › ") + .text_color(cx.theme().colors().text_muted) } fn render(self, view_state: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let symbols_len = self.symbols.len(); h_stack() .id("breadcrumb") .px_1() .text_sm() - .text_color(theme.text_muted) + .text_color(cx.theme().colors().text_muted) .rounded_md() - .hover(|style| style.bg(theme.ghost_element_hover)) - .active(|style| style.bg(theme.ghost_element_active)) + .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .active(|style| style.bg(cx.theme().colors().ghost_element_active)) .child(self.path.clone().to_str().unwrap().to_string()) .child(if !self.symbols.is_empty() { self.render_separator(cx) @@ -84,8 +82,6 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, Breadcrumb>(cx)) .child(Story::label(cx, "Default")) @@ -95,21 +91,21 @@ mod stories { Symbol(vec![ HighlightedText { text: "impl ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "BreadcrumbStory".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), Symbol(vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "render".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), ], diff --git a/crates/ui2/src/components/buffer.rs b/crates/ui2/src/components/buffer.rs index 33a98b6ea9..2b3db676ce 100644 --- a/crates/ui2/src/components/buffer.rs +++ b/crates/ui2/src/components/buffer.rs @@ -155,18 +155,16 @@ impl Buffer { } fn render_row(row: BufferRow, cx: &WindowContext) -> impl Component { - let theme = theme(cx); - let line_background = if row.current { - theme.editor_active_line + cx.theme().colors().editor_active_line } else { - theme.transparent + cx.theme().styles.system.transparent }; let line_number_color = if row.current { - theme.text + cx.theme().colors().text } else { - theme.syntax.get("comment").color.unwrap_or_default() + cx.theme().syntax_color("comment") }; h_stack() @@ -216,14 +214,13 @@ impl Buffer { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let rows = self.render_rows(cx); v_stack() .flex_1() .w_full() .h_full() - .bg(theme.editor) + .bg(cx.theme().colors().editor) .children(rows) } } @@ -246,8 +243,6 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, Buffer>(cx)) .child(Story::label(cx, "Default")) @@ -257,14 +252,14 @@ mod stories { div() .w(rems(64.)) .h_96() - .child(hello_world_rust_buffer_example(&theme)), + .child(hello_world_rust_buffer_example(cx)), ) .child(Story::label(cx, "Hello World (Rust) with Status")) .child( div() .w(rems(64.)) .h_96() - .child(hello_world_rust_buffer_with_status_example(&theme)), + .child(hello_world_rust_buffer_with_status_example(cx)), ) } } diff --git a/crates/ui2/src/components/buffer_search.rs b/crates/ui2/src/components/buffer_search.rs index c5539f0a4a..5d7de1b408 100644 --- a/crates/ui2/src/components/buffer_search.rs +++ b/crates/ui2/src/components/buffer_search.rs @@ -30,9 +30,7 @@ impl Render for BufferSearch { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - let theme = theme(cx); - - h_stack().bg(theme.toolbar).p_2().child( + h_stack().bg(cx.theme().colors().toolbar).p_2().child( h_stack().child(Input::new("Search")).child( IconButton::::new("replace", Icon::Replace) .when(self.is_replace_open, |this| this.color(IconColor::Accent)) diff --git a/crates/ui2/src/components/collab_panel.rs b/crates/ui2/src/components/collab_panel.rs index a8552c0f23..a0e3b55f63 100644 --- a/crates/ui2/src/components/collab_panel.rs +++ b/crates/ui2/src/components/collab_panel.rs @@ -15,27 +15,29 @@ impl CollabPanel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .id(self.id.clone()) .h_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( v_stack() .id("crdb") .w_full() .overflow_y_scroll() .child( - div().pb_1().border_color(theme.border).border_b().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CRDB") - .left_icon(Icon::Hash.into()) - .toggle(ToggleState::Toggled), - ) - .toggle(ToggleState::Toggled), - ), + div() + .pb_1() + .border_color(cx.theme().colors().border) + .border_b() + .child( + List::new(static_collab_panel_current_call()) + .header( + ListHeader::new("CRDB") + .left_icon(Icon::Hash.into()) + .toggle(ToggleState::Toggled), + ) + .toggle(ToggleState::Toggled), + ), ) .child( v_stack().id("channels").py_1().child( @@ -71,13 +73,13 @@ impl CollabPanel { .h_7() .px_2() .border_t() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .flex() .items_center() .child( div() .text_sm() - .text_color(theme.text_placeholder) + .text_color(cx.theme().colors().text_placeholder) .child("Find..."), ), ) diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 812221036a..8345be1b35 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -44,13 +44,11 @@ impl ContextMenu { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .flex() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .border() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .child( List::new( self.items diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 980a1c98aa..06e242b1ef 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -66,8 +66,6 @@ impl IconButton { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let icon_color = match (self.state, self.color) { (InteractionState::Disabled, _) => IconColor::Disabled, _ => self.color, @@ -75,14 +73,14 @@ impl IconButton { let (bg_color, bg_hover_color, bg_active_color) = match self.variant { ButtonVariant::Filled => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), ButtonVariant::Ghost => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), }; diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 455cfe5b59..88cabbdc88 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -60,15 +60,13 @@ impl Key { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() .px_2() .py_0() .rounded_md() .text_sm() - .text_color(theme.text) - .bg(theme.filled_element) + .text_color(cx.theme().colors().text) + .bg(cx.theme().colors().element) .child(self.key.clone()) } } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 9557e68d7f..1668592a38 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -89,8 +89,6 @@ impl ListHeader { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let is_toggleable = self.toggleable != Toggleable::NotToggleable; let is_toggled = self.toggleable.is_toggled(); @@ -99,9 +97,10 @@ impl ListHeader { h_stack() .flex_1() .w_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .when(self.state == InteractionState::Focused, |this| { - this.border().border_color(theme.border_focused) + this.border() + .border_color(cx.theme().colors().border_focused) }) .relative() .child( @@ -363,7 +362,6 @@ impl ListEntry { fn render(mut self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let settings = user_settings(cx); - let theme = theme(cx); let left_content = match self.left_content.clone() { Some(LeftContent::Icon(i)) => Some( @@ -385,9 +383,10 @@ impl ListEntry { div() .relative() .group("") - .bg(theme.surface) + .bg(cx.theme().colors().surface) .when(self.state == InteractionState::Focused, |this| { - this.border().border_color(theme.border_focused) + this.border() + .border_color(cx.theme().colors().border_focused) }) .child( sized_item @@ -399,11 +398,11 @@ impl ListEntry { .h_full() .flex() .justify_center() - .group_hover("", |style| style.bg(theme.border_focused)) + .group_hover("", |style| style.bg(cx.theme().colors().border_focused)) .child( h_stack() .child(div().w_px().h_full()) - .child(div().w_px().h_full().bg(theme.border)), + .child(div().w_px().h_full().bg(cx.theme().colors().border)), ) })) .flex() @@ -472,19 +471,18 @@ impl ListDetailsEntry { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let settings = user_settings(cx); let (item_bg, item_bg_hover, item_bg_active) = match self.seen { true => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), false => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), }; @@ -524,9 +522,7 @@ impl ListSeparator { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - - div().h_px().w_full().bg(theme.border) + div().h_px().w_full().bg(cx.theme().colors().border) } } diff --git a/crates/ui2/src/components/modal.rs b/crates/ui2/src/components/modal.rs index 7c3efe79ba..26986474e0 100644 --- a/crates/ui2/src/components/modal.rs +++ b/crates/ui2/src/components/modal.rs @@ -39,22 +39,20 @@ impl Modal { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .id(self.id.clone()) .w_96() // .rounded_xl() - .bg(theme.background) + .bg(cx.theme().colors().background) .border() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .shadow_2xl() .child( h_stack() .justify_between() .p_1() .border_b() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .child(div().children(self.title.clone().map(|t| Label::new(t)))) .child(IconButton::new("close", Icon::Close)), ) @@ -65,7 +63,7 @@ impl Modal { this.child( h_stack() .border_t() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .p_1() .justify_end() .children(self.secondary_action) diff --git a/crates/ui2/src/components/multi_buffer.rs b/crates/ui2/src/components/multi_buffer.rs index 696fc77a62..ea130f20bd 100644 --- a/crates/ui2/src/components/multi_buffer.rs +++ b/crates/ui2/src/components/multi_buffer.rs @@ -12,8 +12,6 @@ impl MultiBuffer { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .w_full() .h_full() @@ -26,7 +24,7 @@ impl MultiBuffer { .items_center() .justify_between() .p_4() - .bg(theme.editor_subheader) + .bg(cx.theme().colors().editor_subheader) .child(Label::new("main.rs")) .child(IconButton::new("arrow_up_right", Icon::ArrowUpRight)), ) @@ -50,17 +48,15 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, MultiBuffer>(cx)) .child(Story::label(cx, "Default")) .child(MultiBuffer::new(vec![ - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), ])) } } diff --git a/crates/ui2/src/components/notification_toast.rs b/crates/ui2/src/components/notification_toast.rs index f7d280ed16..59078c98f4 100644 --- a/crates/ui2/src/components/notification_toast.rs +++ b/crates/ui2/src/components/notification_toast.rs @@ -1,6 +1,7 @@ use gpui2::rems; -use crate::{h_stack, prelude::*, Icon}; +use crate::prelude::*; +use crate::{h_stack, Icon}; #[derive(Component)] pub struct NotificationToast { @@ -22,8 +23,6 @@ impl NotificationToast { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - h_stack() .z_index(5) .absolute() @@ -35,7 +34,7 @@ impl NotificationToast { .px_1p5() .rounded_lg() .shadow_md() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .child(div().size_full().child(self.label.clone())) } } diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 6872f116e9..10b0e07af6 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -12,15 +12,13 @@ impl NotificationsPanel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() .id(self.id.clone()) .flex() .flex_col() .w_full() .h_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( div() .id("header") diff --git a/crates/ui2/src/components/palette.rs b/crates/ui2/src/components/palette.rs index e47f6a4cea..a1f3eb7e1c 100644 --- a/crates/ui2/src/components/palette.rs +++ b/crates/ui2/src/components/palette.rs @@ -43,22 +43,20 @@ impl Palette { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .id(self.id.clone()) .w_96() .rounded_lg() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .border() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .child( v_stack() .gap_px() .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child( Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder), ))) - .child(div().h_px().w_full().bg(theme.filled_element)) + .child(div().h_px().w_full().bg(cx.theme().colors().element)) .child( v_stack() .id("items") @@ -88,8 +86,12 @@ impl Palette { .px_2() .py_0p5() .rounded_lg() - .hover(|style| style.bg(theme.ghost_element_hover)) - .active(|style| style.bg(theme.ghost_element_active)) + .hover(|style| { + style.bg(cx.theme().colors().ghost_element_hover) + }) + .active(|style| { + style.bg(cx.theme().colors().ghost_element_active) + }) .child(item) })), ), diff --git a/crates/ui2/src/components/panel.rs b/crates/ui2/src/components/panel.rs index 12d2207ffd..c4a9ac5111 100644 --- a/crates/ui2/src/components/panel.rs +++ b/crates/ui2/src/components/panel.rs @@ -93,8 +93,6 @@ impl Panel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let current_size = self.width.unwrap_or(self.initial_width); v_stack() @@ -111,8 +109,8 @@ impl Panel { .when(self.current_side == PanelSide::Bottom, |this| { this.border_b().w_full().h(current_size) }) - .bg(theme.surface) - .border_color(theme.border) + .bg(cx.theme().colors().surface) + .border_color(cx.theme().colors().border) .children(self.children) } } diff --git a/crates/ui2/src/components/panes.rs b/crates/ui2/src/components/panes.rs index 854786ebaa..167e837083 100644 --- a/crates/ui2/src/components/panes.rs +++ b/crates/ui2/src/components/panes.rs @@ -90,8 +90,6 @@ impl PaneGroup { } fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - if !self.panes.is_empty() { let el = div() .flex() @@ -115,7 +113,7 @@ impl PaneGroup { .gap_px() .w_full() .h_full() - .bg(theme.editor) + .bg(cx.theme().colors().editor) .children(self.groups.into_iter().map(|group| group.render(view, cx))); if self.split_direction == SplitDirection::Horizontal { diff --git a/crates/ui2/src/components/player_stack.rs b/crates/ui2/src/components/player_stack.rs index ced761a086..1a1231e6c4 100644 --- a/crates/ui2/src/components/player_stack.rs +++ b/crates/ui2/src/components/player_stack.rs @@ -14,9 +14,7 @@ impl PlayerStack { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let player = self.player_with_call_status.get_player(); - self.player_with_call_status.get_call_status(); let followers = self .player_with_call_status @@ -50,7 +48,7 @@ impl PlayerStack { .pl_1() .rounded_lg() .bg(if followers.is_none() { - theme.transparent + cx.theme().styles.system.transparent } else { player.selection_color(cx) }) diff --git a/crates/ui2/src/components/project_panel.rs b/crates/ui2/src/components/project_panel.rs index 84c68119fe..76fa50d338 100644 --- a/crates/ui2/src/components/project_panel.rs +++ b/crates/ui2/src/components/project_panel.rs @@ -14,15 +14,13 @@ impl ProjectPanel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() .id(self.id.clone()) .flex() .flex_col() .w_full() .h_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( div() .id("project-panel-contents") diff --git a/crates/ui2/src/components/status_bar.rs b/crates/ui2/src/components/status_bar.rs index a23040193f..136472f605 100644 --- a/crates/ui2/src/components/status_bar.rs +++ b/crates/ui2/src/components/status_bar.rs @@ -86,8 +86,6 @@ impl StatusBar { view: &mut Workspace, cx: &mut ViewContext, ) -> impl Component { - let theme = theme(cx); - div() .py_0p5() .px_1() @@ -95,7 +93,7 @@ impl StatusBar { .items_center() .justify_between() .w_full() - .bg(theme.status_bar) + .bg(cx.theme().colors().status_bar) .child(self.left_tools(view, cx)) .child(self.right_tools(view, cx)) } diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index d784ec0174..5f20af0955 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -87,7 +87,6 @@ impl Tab { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; let is_deleted = self.fs_status == FileSystemStatus::Deleted; @@ -110,14 +109,14 @@ impl Tab { let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current { true => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), false => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), }; diff --git a/crates/ui2/src/components/tab_bar.rs b/crates/ui2/src/components/tab_bar.rs index da0a41a1bf..550105b98e 100644 --- a/crates/ui2/src/components/tab_bar.rs +++ b/crates/ui2/src/components/tab_bar.rs @@ -24,15 +24,13 @@ impl TabBar { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let (can_navigate_back, can_navigate_forward) = self.can_navigate; div() .id(self.id.clone()) .w_full() .flex() - .bg(theme.tab_bar) + .bg(cx.theme().colors().tab_bar) // Left Side .child( div() diff --git a/crates/ui2/src/components/terminal.rs b/crates/ui2/src/components/terminal.rs index a751d47dfc..051ebf7315 100644 --- a/crates/ui2/src/components/terminal.rs +++ b/crates/ui2/src/components/terminal.rs @@ -12,8 +12,6 @@ impl Terminal { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let can_navigate_back = true; let can_navigate_forward = false; @@ -26,7 +24,7 @@ impl Terminal { div() .w_full() .flex() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( div().px_1().flex().flex_none().gap_2().child( div() @@ -73,7 +71,7 @@ impl Terminal { height: rems(36.).into(), }, ) - .child(crate::static_data::terminal_buffer(&theme)), + .child(crate::static_data::terminal_buffer(cx)), ) } } diff --git a/crates/ui2/src/components/title_bar.rs b/crates/ui2/src/components/title_bar.rs index 4b3b125dea..2fa201440a 100644 --- a/crates/ui2/src/components/title_bar.rs +++ b/crates/ui2/src/components/title_bar.rs @@ -89,7 +89,6 @@ impl Render for TitleBar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - let theme = theme(cx); let settings = user_settings(cx); // let has_focus = cx.window_is_active(); @@ -106,7 +105,7 @@ impl Render for TitleBar { .items_center() .justify_between() .w_full() - .bg(theme.background) + .bg(cx.theme().colors().background) .py_1() .child( div() diff --git a/crates/ui2/src/components/toast.rs b/crates/ui2/src/components/toast.rs index 814e91c498..3b81ac42b4 100644 --- a/crates/ui2/src/components/toast.rs +++ b/crates/ui2/src/components/toast.rs @@ -37,8 +37,6 @@ impl Toast { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let mut div = div(); if self.origin == ToastOrigin::Bottom { @@ -56,7 +54,7 @@ impl Toast { .rounded_lg() .shadow_md() .overflow_hidden() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .children(self.children) } } diff --git a/crates/ui2/src/components/toolbar.rs b/crates/ui2/src/components/toolbar.rs index 4b35e2d9d2..05a5c991d6 100644 --- a/crates/ui2/src/components/toolbar.rs +++ b/crates/ui2/src/components/toolbar.rs @@ -55,10 +55,8 @@ impl Toolbar { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() - .bg(theme.toolbar) + .bg(cx.theme().colors().toolbar) .p_2() .flex() .justify_between() @@ -87,8 +85,6 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, Toolbar>(cx)) .child(Story::label(cx, "Default")) @@ -100,21 +96,21 @@ mod stories { Symbol(vec![ HighlightedText { text: "impl ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "ToolbarStory".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), Symbol(vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "render".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), ], diff --git a/crates/ui2/src/components/traffic_lights.rs b/crates/ui2/src/components/traffic_lights.rs index 8ee19d26f5..9080276cdd 100644 --- a/crates/ui2/src/components/traffic_lights.rs +++ b/crates/ui2/src/components/traffic_lights.rs @@ -22,13 +22,13 @@ impl TrafficLight { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); + let system_colors = &cx.theme().styles.system; let fill = match (self.window_has_focus, self.color) { - (true, TrafficLightColor::Red) => theme.mac_os_traffic_light_red, - (true, TrafficLightColor::Yellow) => theme.mac_os_traffic_light_yellow, - (true, TrafficLightColor::Green) => theme.mac_os_traffic_light_green, - (false, _) => theme.filled_element, + (true, TrafficLightColor::Red) => system_colors.mac_os_traffic_light_red, + (true, TrafficLightColor::Yellow) => system_colors.mac_os_traffic_light_yellow, + (true, TrafficLightColor::Green) => system_colors.mac_os_traffic_light_green, + (false, _) => cx.theme().colors().element, }; div().w_3().h_3().rounded_full().bg(fill) diff --git a/crates/ui2/src/components/workspace.rs b/crates/ui2/src/components/workspace.rs index 78ab6232a8..0e31c6b9ad 100644 --- a/crates/ui2/src/components/workspace.rs +++ b/crates/ui2/src/components/workspace.rs @@ -179,8 +179,6 @@ impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - let theme = theme(cx); - // HACK: This should happen inside of `debug_toggle_user_settings`, but // we don't have `cx.global::()` in event handlers at the moment. // Need to talk with Nathan/Antonio about this. @@ -216,8 +214,8 @@ impl Render for Workspace { .gap_0() .justify_start() .items_start() - .text_color(theme.text) - .bg(theme.background) + .text_color(cx.theme().colors().text) + .bg(cx.theme().colors().background) .child(self.title_bar.clone()) .child( div() @@ -228,7 +226,7 @@ impl Render for Workspace { .overflow_hidden() .border_t() .border_b() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .children( Some( Panel::new("project-panel-outer", cx) diff --git a/crates/ui2/src/elements/avatar.rs b/crates/ui2/src/elements/avatar.rs index f008eeb479..ff92021b11 100644 --- a/crates/ui2/src/elements/avatar.rs +++ b/crates/ui2/src/elements/avatar.rs @@ -22,8 +22,6 @@ impl Avatar { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let mut img = img(); if self.shape == Shape::Circle { @@ -34,7 +32,8 @@ impl Avatar { img.uri(self.src.clone()) .size_4() - .bg(theme.image_fallback_background) + // todo!(Pull the avatar fallback background from the theme.) + .bg(gpui2::red()) } } diff --git a/crates/ui2/src/elements/button.rs b/crates/ui2/src/elements/button.rs index d27a0537d8..e63269197c 100644 --- a/crates/ui2/src/elements/button.rs +++ b/crates/ui2/src/elements/button.rs @@ -21,29 +21,23 @@ pub enum ButtonVariant { impl ButtonVariant { pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla { - let theme = theme(cx); - match self { - ButtonVariant::Ghost => theme.ghost_element, - ButtonVariant::Filled => theme.filled_element, + ButtonVariant::Ghost => cx.theme().colors().ghost_element, + ButtonVariant::Filled => cx.theme().colors().element, } } pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla { - let theme = theme(cx); - match self { - ButtonVariant::Ghost => theme.ghost_element_hover, - ButtonVariant::Filled => theme.filled_element_hover, + ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover, + ButtonVariant::Filled => cx.theme().colors().element_hover, } } pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla { - let theme = theme(cx); - match self { - ButtonVariant::Ghost => theme.ghost_element_active, - ButtonVariant::Filled => theme.filled_element_active, + ButtonVariant::Ghost => cx.theme().colors().ghost_element_active, + ButtonVariant::Filled => cx.theme().colors().element_active, } } } diff --git a/crates/ui2/src/elements/details.rs b/crates/ui2/src/elements/details.rs index eca7798c82..1d22c81774 100644 --- a/crates/ui2/src/elements/details.rs +++ b/crates/ui2/src/elements/details.rs @@ -1,4 +1,5 @@ -use crate::{prelude::*, v_stack, ButtonGroup}; +use crate::prelude::*; +use crate::{v_stack, ButtonGroup}; #[derive(Component)] pub struct Details { @@ -27,13 +28,11 @@ impl Details { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .p_1() .gap_0p5() .text_xs() - .text_color(theme.text) + .text_color(cx.theme().colors().text) .size_full() .child(self.text) .children(self.meta.map(|m| m)) diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index 4e4ec2bce7..6c1b3a4f08 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -26,13 +26,14 @@ pub enum IconColor { impl IconColor { pub fn color(self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); + let theme_colors = cx.theme().colors(); + match self { - IconColor::Default => gpui2::red(), - IconColor::Muted => gpui2::red(), - IconColor::Disabled => gpui2::red(), - IconColor::Placeholder => gpui2::red(), - IconColor::Accent => gpui2::red(), + IconColor::Default => theme_colors.icon, + IconColor::Muted => theme_colors.icon_muted, + IconColor::Disabled => theme_colors.icon_disabled, + IconColor::Placeholder => theme_colors.icon_placeholder, + IconColor::Accent => theme_colors.icon_accent, IconColor::Error => gpui2::red(), IconColor::Warning => gpui2::red(), IconColor::Success => gpui2::red(), diff --git a/crates/ui2/src/elements/input.rs b/crates/ui2/src/elements/input.rs index e9e92dd0a6..3f82512b84 100644 --- a/crates/ui2/src/elements/input.rs +++ b/crates/ui2/src/elements/input.rs @@ -57,18 +57,16 @@ impl Input { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let (input_bg, input_hover_bg, input_active_bg) = match self.variant { InputVariant::Ghost => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), InputVariant::Filled => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), }; @@ -90,7 +88,7 @@ impl Input { .w_full() .px_2() .border() - .border_color(theme.transparent) + .border_color(cx.theme().styles.system.transparent) .bg(input_bg) .hover(|style| style.bg(input_hover_bg)) .active(|style| style.bg(input_active_bg)) diff --git a/crates/ui2/src/elements/label.rs b/crates/ui2/src/elements/label.rs index 4d336345fb..ee8ac9a636 100644 --- a/crates/ui2/src/elements/label.rs +++ b/crates/ui2/src/elements/label.rs @@ -18,18 +18,16 @@ pub enum LabelColor { impl LabelColor { pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - match self { - Self::Default => theme.text, - Self::Muted => theme.text_muted, + Self::Default => cx.theme().colors().text, + Self::Muted => cx.theme().colors().text_muted, Self::Created => gpui2::red(), Self::Modified => gpui2::red(), Self::Deleted => gpui2::red(), - Self::Disabled => theme.text_disabled, + Self::Disabled => cx.theme().colors().text_disabled, Self::Hidden => gpui2::red(), - Self::Placeholder => theme.text_placeholder, - Self::Accent => gpui2::red(), + Self::Placeholder => cx.theme().colors().text_placeholder, + Self::Accent => cx.theme().colors().text_accent, } } } @@ -126,9 +124,7 @@ impl HighlightedLabel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - - let highlight_color = theme.text_accent; + let highlight_color = cx.theme().colors().text_accent; let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); diff --git a/crates/ui2/src/elements/player.rs b/crates/ui2/src/elements/player.rs index 5bf890b8bb..8e3ad5c3a8 100644 --- a/crates/ui2/src/elements/player.rs +++ b/crates/ui2/src/elements/player.rs @@ -1,6 +1,6 @@ use gpui2::{Hsla, ViewContext}; -use crate::theme; +use crate::prelude::*; #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub enum PlayerStatus { @@ -139,13 +139,11 @@ impl Player { } pub fn cursor_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - theme.players[self.index].cursor + cx.theme().styles.player.0[self.index].cursor } pub fn selection_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - theme.players[self.index].selection + cx.theme().styles.player.0[self.index].selection } pub fn avatar_src(&self) -> &str { diff --git a/crates/ui2/src/elements/tool_divider.rs b/crates/ui2/src/elements/tool_divider.rs index e1ebb294a0..8a9bbad97f 100644 --- a/crates/ui2/src/elements/tool_divider.rs +++ b/crates/ui2/src/elements/tool_divider.rs @@ -9,8 +9,6 @@ impl ToolDivider { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - - div().w_px().h_3().bg(theme.border) + div().w_px().h_3().bg(cx.theme().colors().border) } } diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index 63405fc2cb..b424ce6123 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -6,7 +6,7 @@ pub use gpui2::{ pub use crate::elevation::*; use crate::settings::user_settings; pub use crate::ButtonVariant; -pub use theme2::theme; +pub use theme2::ActiveTheme; use gpui2::{rems, Hsla, Rems}; use strum::EnumIter; @@ -54,15 +54,13 @@ pub enum GitStatus { impl GitStatus { pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - match self { - Self::None => theme.transparent, - Self::Created => theme.git_created, - Self::Modified => theme.git_modified, - Self::Deleted => theme.git_deleted, - Self::Conflict => theme.git_conflict, - Self::Renamed => theme.git_renamed, + Self::None => cx.theme().styles.system.transparent, + Self::Created => cx.theme().styles.git.created, + Self::Modified => cx.theme().styles.git.modified, + Self::Deleted => cx.theme().styles.git.deleted, + Self::Conflict => cx.theme().styles.git.conflict, + Self::Renamed => cx.theme().styles.git.renamed, } } } diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 68f1e36b2c..7062c81954 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; use std::str::FromStr; -use gpui2::ViewContext; +use gpui2::{AppContext, ViewContext}; use rand::Rng; -use theme2::Theme; +use theme2::ActiveTheme; use crate::{ - theme, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, + Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, @@ -643,8 +643,6 @@ pub fn empty_buffer_example() -> Buffer { } pub fn hello_world_rust_editor_example(cx: &mut ViewContext) -> EditorPane { - let theme = theme(cx); - EditorPane::new( cx, static_tabs_example(), @@ -652,29 +650,29 @@ pub fn hello_world_rust_editor_example(cx: &mut ViewContext) -> Edit vec![Symbol(vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "main".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ])], - hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(cx), ) } -pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer { +pub fn hello_world_rust_buffer_example(cx: &AppContext) -> Buffer { Buffer::new("hello-world-rust-buffer") .set_title("hello_world.rs".to_string()) .set_path("src/hello_world.rs".to_string()) .set_language("rust".to_string()) .set_rows(Some(BufferRows { show_line_numbers: true, - rows: hello_world_rust_buffer_rows(theme), + rows: hello_world_rust_buffer_rows(cx), })) } -pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { +pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { let show_line_number = true; vec![ @@ -686,15 +684,15 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "main".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "() {".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -710,7 +708,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![HighlightedText { text: " // Statements here are executed when the compiled binary is called." .to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -733,7 +731,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: " // Print text to the console.".to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -748,15 +746,15 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![ HighlightedText { text: " println!(".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "\"Hello, world!\"".to_string(), - color: theme.syntax.color("string"), + color: cx.theme().syntax_color("string"), }, HighlightedText { text: ");".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -771,7 +769,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "}".to_string(), - color: theme.text, + color: cx.theme().colors().text, }], }), cursors: None, @@ -782,8 +780,6 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { } pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext) -> EditorPane { - let theme = theme(cx); - EditorPane::new( cx, static_tabs_example(), @@ -791,29 +787,29 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext Buffer { +pub fn hello_world_rust_buffer_with_status_example(cx: &AppContext) -> Buffer { Buffer::new("hello-world-rust-buffer-with-status") .set_title("hello_world.rs".to_string()) .set_path("src/hello_world.rs".to_string()) .set_language("rust".to_string()) .set_rows(Some(BufferRows { show_line_numbers: true, - rows: hello_world_rust_with_status_buffer_rows(theme), + rows: hello_world_rust_with_status_buffer_rows(cx), })) } -pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec { +pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec { let show_line_number = true; vec![ @@ -825,15 +821,15 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec highlighted_texts: vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "main".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "() {".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -849,7 +845,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec highlighted_texts: vec![HighlightedText { text: "// Statements here are executed when the compiled binary is called." .to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -872,7 +868,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: " // Print text to the console.".to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -887,15 +883,15 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec highlighted_texts: vec![ HighlightedText { text: " println!(".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "\"Hello, world!\"".to_string(), - color: theme.syntax.color("string"), + color: cx.theme().syntax_color("string"), }, HighlightedText { text: ");".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -910,7 +906,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "}".to_string(), - color: theme.text, + color: cx.theme().colors().text, }], }), cursors: None, @@ -924,7 +920,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "".to_string(), - color: theme.text, + color: cx.theme().colors().text, }], }), cursors: None, @@ -938,7 +934,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "// Marshall and Nate were here".to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -948,16 +944,16 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec ] } -pub fn terminal_buffer(theme: &Theme) -> Buffer { +pub fn terminal_buffer(cx: &AppContext) -> Buffer { Buffer::new("terminal") .set_title("zed — fish".to_string()) .set_rows(Some(BufferRows { show_line_numbers: false, - rows: terminal_buffer_rows(theme), + rows: terminal_buffer_rows(cx), })) } -pub fn terminal_buffer_rows(theme: &Theme) -> Vec { +pub fn terminal_buffer_rows(cx: &AppContext) -> Vec { let show_line_number = false; vec![ @@ -969,31 +965,31 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![ HighlightedText { text: "maxdeviant ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "in ".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "profaned-capital ".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "in ".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "~/p/zed ".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "on ".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: " gpui2-ui ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, ], }), @@ -1008,7 +1004,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "λ ".to_string(), - color: theme.syntax.color("string"), + color: cx.theme().syntax_color("string"), }], }), cursors: None, diff --git a/crates/ui2/src/story.rs b/crates/ui2/src/story.rs index d2813bd174..dea4e342b4 100644 --- a/crates/ui2/src/story.rs +++ b/crates/ui2/src/story.rs @@ -6,8 +6,6 @@ pub struct Story {} impl Story { pub fn container(cx: &mut ViewContext) -> Div { - let theme = theme(cx); - div() .size_full() .flex() @@ -15,15 +13,13 @@ impl Story { .pt_2() .px_4() .font("Zed Mono") - .bg(theme.background) + .bg(cx.theme().colors().background) } pub fn title(cx: &mut ViewContext, title: &str) -> impl Component { - let theme = theme(cx); - div() .text_xl() - .text_color(theme.text) + .text_color(cx.theme().colors().text) .child(title.to_owned()) } @@ -32,13 +28,11 @@ impl Story { } pub fn label(cx: &mut ViewContext, label: &str) -> impl Component { - let theme = theme(cx); - div() .mt_4() .mb_2() .text_xs() - .text_color(theme.text) + .text_color(cx.theme().colors().text) .child(label.to_owned()) } } From 36a73d657a6fe1d53e5d0f81f60bfa83d00b77ff Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 04:05:50 +0100 Subject: [PATCH 039/156] Remove old `Theme` definition (#3195) This PR removes the old `Theme` definition in favor of the new `ThemeVariant`s. The new `SyntaxStyles` have been reverted to the old `SyntaxTheme` that operates by storing the syntax styles as a vector of `gpui2::HighlightStyle`s. This is necessary for the intended usage by `language2`, where we find the longest key in the theme's syntax styles that matches the capture name: https://github.com/zed-industries/zed/blob/18431051d9d750d9e66284a71f7a55a1e31c1374/crates/language2/src/highlight_map.rs#L15-L41 --- Cargo.lock | 15 - Cargo.toml | 1 - crates/language2/src/language2.rs | 10 +- crates/storybook2/src/storybook2.rs | 4 +- crates/theme2/src/colors.rs | 4 +- crates/theme2/src/default_colors.rs | 642 +++--------------- crates/theme2/src/default_theme.rs | 16 +- crates/theme2/src/registry.rs | 63 +- crates/theme2/src/settings.rs | 10 +- crates/theme2/src/syntax.rs | 238 +------ crates/theme2/src/theme2.rs | 137 +--- crates/theme2/src/themes/andromeda.rs | 130 ---- crates/theme2/src/themes/atelier_cave_dark.rs | 136 ---- .../theme2/src/themes/atelier_cave_light.rs | 136 ---- crates/theme2/src/themes/atelier_dune_dark.rs | 136 ---- .../theme2/src/themes/atelier_dune_light.rs | 136 ---- .../theme2/src/themes/atelier_estuary_dark.rs | 136 ---- .../src/themes/atelier_estuary_light.rs | 136 ---- .../theme2/src/themes/atelier_forest_dark.rs | 136 ---- .../theme2/src/themes/atelier_forest_light.rs | 136 ---- .../theme2/src/themes/atelier_heath_dark.rs | 136 ---- .../theme2/src/themes/atelier_heath_light.rs | 136 ---- .../src/themes/atelier_lakeside_dark.rs | 136 ---- .../src/themes/atelier_lakeside_light.rs | 136 ---- .../theme2/src/themes/atelier_plateau_dark.rs | 136 ---- .../src/themes/atelier_plateau_light.rs | 136 ---- .../theme2/src/themes/atelier_savanna_dark.rs | 136 ---- .../src/themes/atelier_savanna_light.rs | 136 ---- .../theme2/src/themes/atelier_seaside_dark.rs | 136 ---- .../src/themes/atelier_seaside_light.rs | 136 ---- .../src/themes/atelier_sulphurpool_dark.rs | 136 ---- .../src/themes/atelier_sulphurpool_light.rs | 136 ---- crates/theme2/src/themes/ayu_dark.rs | 130 ---- crates/theme2/src/themes/ayu_light.rs | 130 ---- crates/theme2/src/themes/ayu_mirage.rs | 130 ---- crates/theme2/src/themes/gruvbox_dark.rs | 131 ---- crates/theme2/src/themes/gruvbox_dark_hard.rs | 131 ---- crates/theme2/src/themes/gruvbox_dark_soft.rs | 131 ---- crates/theme2/src/themes/gruvbox_light.rs | 131 ---- .../theme2/src/themes/gruvbox_light_hard.rs | 131 ---- .../theme2/src/themes/gruvbox_light_soft.rs | 131 ---- crates/theme2/src/themes/mod.rs | 79 --- crates/theme2/src/themes/one_dark.rs | 131 ---- crates/theme2/src/themes/one_light.rs | 131 ---- crates/theme2/src/themes/rose_pine.rs | 132 ---- crates/theme2/src/themes/rose_pine_dawn.rs | 132 ---- crates/theme2/src/themes/rose_pine_moon.rs | 132 ---- crates/theme2/src/themes/sandcastle.rs | 130 ---- crates/theme2/src/themes/solarized_dark.rs | 130 ---- crates/theme2/src/themes/solarized_light.rs | 130 ---- crates/theme2/src/themes/summercamp.rs | 130 ---- crates/theme_converter/Cargo.toml | 18 - crates/theme_converter/src/main.rs | 390 ----------- crates/theme_converter/src/theme_printer.rs | 174 ----- 54 files changed, 173 insertions(+), 6832 deletions(-) delete mode 100644 crates/theme2/src/themes/andromeda.rs delete mode 100644 crates/theme2/src/themes/atelier_cave_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_cave_light.rs delete mode 100644 crates/theme2/src/themes/atelier_dune_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_dune_light.rs delete mode 100644 crates/theme2/src/themes/atelier_estuary_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_estuary_light.rs delete mode 100644 crates/theme2/src/themes/atelier_forest_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_forest_light.rs delete mode 100644 crates/theme2/src/themes/atelier_heath_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_heath_light.rs delete mode 100644 crates/theme2/src/themes/atelier_lakeside_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_lakeside_light.rs delete mode 100644 crates/theme2/src/themes/atelier_plateau_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_plateau_light.rs delete mode 100644 crates/theme2/src/themes/atelier_savanna_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_savanna_light.rs delete mode 100644 crates/theme2/src/themes/atelier_seaside_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_seaside_light.rs delete mode 100644 crates/theme2/src/themes/atelier_sulphurpool_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_sulphurpool_light.rs delete mode 100644 crates/theme2/src/themes/ayu_dark.rs delete mode 100644 crates/theme2/src/themes/ayu_light.rs delete mode 100644 crates/theme2/src/themes/ayu_mirage.rs delete mode 100644 crates/theme2/src/themes/gruvbox_dark.rs delete mode 100644 crates/theme2/src/themes/gruvbox_dark_hard.rs delete mode 100644 crates/theme2/src/themes/gruvbox_dark_soft.rs delete mode 100644 crates/theme2/src/themes/gruvbox_light.rs delete mode 100644 crates/theme2/src/themes/gruvbox_light_hard.rs delete mode 100644 crates/theme2/src/themes/gruvbox_light_soft.rs delete mode 100644 crates/theme2/src/themes/mod.rs delete mode 100644 crates/theme2/src/themes/one_dark.rs delete mode 100644 crates/theme2/src/themes/one_light.rs delete mode 100644 crates/theme2/src/themes/rose_pine.rs delete mode 100644 crates/theme2/src/themes/rose_pine_dawn.rs delete mode 100644 crates/theme2/src/themes/rose_pine_moon.rs delete mode 100644 crates/theme2/src/themes/sandcastle.rs delete mode 100644 crates/theme2/src/themes/solarized_dark.rs delete mode 100644 crates/theme2/src/themes/solarized_light.rs delete mode 100644 crates/theme2/src/themes/summercamp.rs delete mode 100644 crates/theme_converter/Cargo.toml delete mode 100644 crates/theme_converter/src/main.rs delete mode 100644 crates/theme_converter/src/theme_printer.rs diff --git a/Cargo.lock b/Cargo.lock index 272320895d..5691729766 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8769,21 +8769,6 @@ dependencies = [ "util", ] -[[package]] -name = "theme_converter" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap 4.4.4", - "convert_case 0.6.0", - "gpui2", - "log", - "rust-embed", - "serde", - "simplelog", - "theme2", -] - [[package]] name = "theme_selector" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ac490ce935..cb0e12cc40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,6 @@ members = [ "crates/text", "crates/theme", "crates/theme2", - "crates/theme_converter", "crates/theme_selector", "crates/ui2", "crates/util", diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 717a80619b..21746bf43c 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -42,7 +42,7 @@ use std::{ }, }; use syntax_map::SyntaxSnapshot; -use theme2::{SyntaxTheme, Theme}; +use theme2::{SyntaxTheme, ThemeVariant}; use tree_sitter::{self, Query}; use unicase::UniCase; use util::{http::HttpClient, paths::PathExt}; @@ -642,7 +642,7 @@ struct LanguageRegistryState { next_available_language_id: AvailableLanguageId, loading_languages: HashMap>>>>, subscription: (watch::Sender<()>, watch::Receiver<()>), - theme: Option>, + theme: Option>, version: usize, reload_count: usize, } @@ -743,11 +743,11 @@ impl LanguageRegistry { self.state.read().reload_count } - pub fn set_theme(&self, theme: Arc) { + pub fn set_theme(&self, theme: Arc) { let mut state = self.state.write(); state.theme = Some(theme.clone()); for language in &state.languages { - language.set_theme(&theme.syntax); + language.set_theme(&theme.syntax()); } } @@ -1048,7 +1048,7 @@ impl LanguageRegistryState { fn add(&mut self, language: Arc) { if let Some(theme) = self.theme.as_ref() { - language.set_theme(&theme.syntax); + language.set_theme(&theme.syntax()); } self.languages.push(language); self.version += 1; diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 6028695d7f..411fe18071 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -48,7 +48,7 @@ fn main() { let args = Args::parse(); let story_selector = args.story.clone(); - let theme_name = args.theme.unwrap_or("One Dark".to_string()); + let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string()); let asset_source = Arc::new(Assets); gpui2::App::production(asset_source).run(move |cx| { @@ -68,7 +68,7 @@ fn main() { let theme_registry = cx.global::(); let mut theme_settings = ThemeSettings::get_global(cx).clone(); - theme_settings.old_active_theme = theme_registry.get(&theme_name).unwrap(); + theme_settings.active_theme = theme_registry.get(&theme_name).unwrap(); ThemeSettings::override_global(theme_settings, cx); ui::settings::init(cx); diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index d23fde1ee0..2a59fa41bd 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -1,7 +1,7 @@ use gpui2::Hsla; use refineable::Refineable; -use crate::{generate_struct_with_overrides, SyntaxStyles}; +use crate::{generate_struct_with_overrides, SyntaxTheme}; pub struct SystemColors { pub transparent: Hsla, @@ -94,7 +94,7 @@ generate_struct_with_overrides! { status: StatusColors, git: GitStatusColors, player: PlayerColors, - syntax: SyntaxStyles + syntax: SyntaxTheme } #[cfg(test)] diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index e8146cdeaa..5ef93d036f 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,11 +1,9 @@ -use gpui2::{hsla, FontWeight, Rgba}; -use indexmap::IndexMap; +use gpui2::{hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, - syntax::{SyntaxStyleName, SyntaxStyles}, - SyntaxStyle, + syntax::SyntaxTheme, }; impl Default for SystemColors { @@ -77,541 +75,115 @@ impl Default for PlayerColors { } } -impl SyntaxStyles { +impl SyntaxTheme { pub fn default_light() -> Self { - use SyntaxStyleName::*; - - let neutral: ColorScaleSet = slate().into(); - - Self(IndexMap::from_iter([ - ( - Comment, - SyntaxStyle::builder().color(neutral.light(11)).build(), - ), - ( - CommentDoc, - SyntaxStyle::builder().color(neutral.light(11)).build(), - ), - ( - Primary, - SyntaxStyle::builder().color(neutral.light(12)).build(), - ), - ( - Predictive, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - Hint, - SyntaxStyle::builder() - .color(ColorScaleSet::from(cyan()).light(10)) - .build(), - ), - ( - Emphasis, - SyntaxStyle::builder().weight(FontWeight(600.0)).build(), - ), - ( - EmphasisStrong, - SyntaxStyle::builder().weight(FontWeight(800.0)).build(), - ), - ( - Title, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - LinkUri, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).light(12)) - .build(), - ), - ( - LinkText, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).light(12)) - .build(), - ), - ( - TextLiteral, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).light(12)) - .build(), - ), - ( - Punctuation, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - PunctuationBracket, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - PunctuationDelimiter, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - PunctuationSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - PunctuationListMarker, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).light(12)) - .build(), - ), - ( - String, - SyntaxStyle::builder() - .color(ColorScaleSet::from(green()).light(12)) - .build(), - ), - ( - StringSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - StringSpecialSymbol, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - StringEscape, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).light(12)) - .build(), - ), - ( - StringRegex, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).light(12)) - .build(), - ), - ( - Constructor, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).light(12)) - .build(), - ), - // TODO: Continue assigning syntax colors from here - ( - Variant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Type, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - TypeBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Variable, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - VariableSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Label, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Tag, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Attribute, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Property, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Constant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Keyword, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Enum, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Operator, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Number, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Boolean, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - ConstantBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Function, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionSpecialDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionMethod, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionMethodBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Preproc, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Embedded, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ])) + Self { + highlights: vec![ + ( + "string.special.symbol".into(), + gpui2::rgba(0xad6e26ff).into(), + ), + ("hint".into(), gpui2::rgba(0x9294beff).into()), + ("link_uri".into(), gpui2::rgba(0x3882b7ff).into()), + ("type".into(), gpui2::rgba(0x3882b7ff).into()), + ("string.regex".into(), gpui2::rgba(0xad6e26ff).into()), + ("constant".into(), gpui2::rgba(0x669f59ff).into()), + ("function".into(), gpui2::rgba(0x5b79e3ff).into()), + ("string.special".into(), gpui2::rgba(0xad6e26ff).into()), + ("punctuation.bracket".into(), gpui2::rgba(0x4d4f52ff).into()), + ("variable".into(), gpui2::rgba(0x383a41ff).into()), + ("punctuation".into(), gpui2::rgba(0x383a41ff).into()), + ("property".into(), gpui2::rgba(0xd3604fff).into()), + ("string".into(), gpui2::rgba(0x649f57ff).into()), + ("predictive".into(), gpui2::rgba(0x9b9ec6ff).into()), + ("attribute".into(), gpui2::rgba(0x5c78e2ff).into()), + ("number".into(), gpui2::rgba(0xad6e25ff).into()), + ("constructor".into(), gpui2::rgba(0x5c78e2ff).into()), + ("embedded".into(), gpui2::rgba(0x383a41ff).into()), + ("title".into(), gpui2::rgba(0xd3604fff).into()), + ("tag".into(), gpui2::rgba(0x5c78e2ff).into()), + ("boolean".into(), gpui2::rgba(0xad6e25ff).into()), + ( + "punctuation.list_marker".into(), + gpui2::rgba(0xd3604fff).into(), + ), + ("variant".into(), gpui2::rgba(0x5b79e3ff).into()), + ("emphasis".into(), gpui2::rgba(0x5c78e2ff).into()), + ("link_text".into(), gpui2::rgba(0x5b79e3ff).into()), + ("comment".into(), gpui2::rgba(0xa2a3a7ff).into()), + ("punctuation.special".into(), gpui2::rgba(0xb92b46ff).into()), + ("emphasis.strong".into(), gpui2::rgba(0xad6e25ff).into()), + ("primary".into(), gpui2::rgba(0x383a41ff).into()), + ( + "punctuation.delimiter".into(), + gpui2::rgba(0x4d4f52ff).into(), + ), + ("label".into(), gpui2::rgba(0x5c78e2ff).into()), + ("keyword".into(), gpui2::rgba(0xa449abff).into()), + ("string.escape".into(), gpui2::rgba(0x7c7e86ff).into()), + ("text.literal".into(), gpui2::rgba(0x649f57ff).into()), + ("variable.special".into(), gpui2::rgba(0xad6e25ff).into()), + ("comment.doc".into(), gpui2::rgba(0x7c7e86ff).into()), + ("enum".into(), gpui2::rgba(0xd3604fff).into()), + ("operator".into(), gpui2::rgba(0x3882b7ff).into()), + ("preproc".into(), gpui2::rgba(0x383a41ff).into()), + ], + } } pub fn default_dark() -> Self { - use SyntaxStyleName::*; - - let neutral: ColorScaleSet = slate().into(); - - Self(IndexMap::from_iter([ - ( - Comment, - SyntaxStyle::builder().color(neutral.dark(11)).build(), - ), - ( - CommentDoc, - SyntaxStyle::builder().color(neutral.dark(11)).build(), - ), - ( - Primary, - SyntaxStyle::builder().color(neutral.dark(12)).build(), - ), - ( - Predictive, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - Hint, - SyntaxStyle::builder() - .color(ColorScaleSet::from(cyan()).dark(10)) - .build(), - ), - ( - Emphasis, - SyntaxStyle::builder().weight(FontWeight(600.0)).build(), - ), - ( - EmphasisStrong, - SyntaxStyle::builder().weight(FontWeight(800.0)).build(), - ), - ( - Title, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - LinkUri, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).dark(12)) - .build(), - ), - ( - LinkText, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).dark(12)) - .build(), - ), - ( - TextLiteral, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).dark(12)) - .build(), - ), - ( - Punctuation, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - PunctuationBracket, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - PunctuationDelimiter, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - PunctuationSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - PunctuationListMarker, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).dark(12)) - .build(), - ), - ( - String, - SyntaxStyle::builder() - .color(ColorScaleSet::from(green()).dark(12)) - .build(), - ), - ( - StringSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - StringSpecialSymbol, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - StringEscape, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).dark(12)) - .build(), - ), - ( - StringRegex, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).dark(12)) - .build(), - ), - ( - Constructor, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).dark(12)) - .build(), - ), - // TODO: Continue assigning syntax colors from here - ( - Variant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Type, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - TypeBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Variable, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - VariableSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Label, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Tag, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Attribute, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Property, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Constant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Keyword, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Enum, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Operator, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Number, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Boolean, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - ConstantBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Function, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionSpecialDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionMethod, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionMethodBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Preproc, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Embedded, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ])) + Self { + highlights: vec![ + ("keyword".into(), gpui2::rgba(0xb477cfff).into()), + ("comment.doc".into(), gpui2::rgba(0x878e98ff).into()), + ("variant".into(), gpui2::rgba(0x73ade9ff).into()), + ("property".into(), gpui2::rgba(0xd07277ff).into()), + ("function".into(), gpui2::rgba(0x73ade9ff).into()), + ("type".into(), gpui2::rgba(0x6eb4bfff).into()), + ("tag".into(), gpui2::rgba(0x74ade8ff).into()), + ("string.escape".into(), gpui2::rgba(0x878e98ff).into()), + ("punctuation.bracket".into(), gpui2::rgba(0xb2b9c6ff).into()), + ("hint".into(), gpui2::rgba(0x5a6f89ff).into()), + ("punctuation".into(), gpui2::rgba(0xacb2beff).into()), + ("comment".into(), gpui2::rgba(0x5d636fff).into()), + ("emphasis".into(), gpui2::rgba(0x74ade8ff).into()), + ("punctuation.special".into(), gpui2::rgba(0xb1574bff).into()), + ("link_uri".into(), gpui2::rgba(0x6eb4bfff).into()), + ("string.regex".into(), gpui2::rgba(0xbf956aff).into()), + ("constructor".into(), gpui2::rgba(0x73ade9ff).into()), + ("operator".into(), gpui2::rgba(0x6eb4bfff).into()), + ("constant".into(), gpui2::rgba(0xdfc184ff).into()), + ("string.special".into(), gpui2::rgba(0xbf956aff).into()), + ("emphasis.strong".into(), gpui2::rgba(0xbf956aff).into()), + ( + "string.special.symbol".into(), + gpui2::rgba(0xbf956aff).into(), + ), + ("primary".into(), gpui2::rgba(0xacb2beff).into()), + ("preproc".into(), gpui2::rgba(0xc8ccd4ff).into()), + ("string".into(), gpui2::rgba(0xa1c181ff).into()), + ( + "punctuation.delimiter".into(), + gpui2::rgba(0xb2b9c6ff).into(), + ), + ("embedded".into(), gpui2::rgba(0xc8ccd4ff).into()), + ("enum".into(), gpui2::rgba(0xd07277ff).into()), + ("variable.special".into(), gpui2::rgba(0xbf956aff).into()), + ("text.literal".into(), gpui2::rgba(0xa1c181ff).into()), + ("attribute".into(), gpui2::rgba(0x74ade8ff).into()), + ("link_text".into(), gpui2::rgba(0x73ade9ff).into()), + ("title".into(), gpui2::rgba(0xd07277ff).into()), + ("predictive".into(), gpui2::rgba(0x5a6a87ff).into()), + ("number".into(), gpui2::rgba(0xbf956aff).into()), + ("label".into(), gpui2::rgba(0x74ade8ff).into()), + ("variable".into(), gpui2::rgba(0xc8ccd4ff).into()), + ("boolean".into(), gpui2::rgba(0xbf956aff).into()), + ( + "punctuation.list_marker".into(), + gpui2::rgba(0xd07277ff).into(), + ), + ], + } } } diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 4b47e403d6..26a55b5e0d 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,12 +1,12 @@ use crate::{ colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyle}, - default_color_scales, Appearance, SyntaxStyles, ThemeFamily, ThemeVariant, + default_color_scales, Appearance, SyntaxTheme, ThemeFamily, ThemeVariant, }; fn zed_pro_daylight() -> ThemeVariant { ThemeVariant { id: "zed_pro_daylight".to_string(), - name: "Zed Pro Daylight".to_string(), + name: "Zed Pro Daylight".into(), appearance: Appearance::Light, styles: ThemeStyle { system: SystemColors::default(), @@ -14,7 +14,7 @@ fn zed_pro_daylight() -> ThemeVariant { status: StatusColors::default(), git: GitStatusColors::default(), player: PlayerColors::default(), - syntax: SyntaxStyles::default_light(), + syntax: SyntaxTheme::default_light(), }, } } @@ -22,15 +22,15 @@ fn zed_pro_daylight() -> ThemeVariant { pub(crate) fn zed_pro_moonlight() -> ThemeVariant { ThemeVariant { id: "zed_pro_moonlight".to_string(), - name: "Zed Pro Moonlight".to_string(), - appearance: Appearance::Light, + name: "Zed Pro Moonlight".into(), + appearance: Appearance::Dark, styles: ThemeStyle { system: SystemColors::default(), colors: ThemeColors::default_dark(), status: StatusColors::default(), git: GitStatusColors::default(), player: PlayerColors::default(), - syntax: SyntaxStyles::default_dark(), + syntax: SyntaxTheme::default_dark(), }, } } @@ -38,8 +38,8 @@ pub(crate) fn zed_pro_moonlight() -> ThemeVariant { pub fn zed_pro_family() -> ThemeFamily { ThemeFamily { id: "zed_pro".to_string(), - name: "Zed Pro".to_string(), - author: "Zed Team".to_string(), + name: "Zed Pro".into(), + author: "Zed Team".into(), themes: vec![zed_pro_daylight(), zed_pro_moonlight()], scales: default_color_scales(), } diff --git a/crates/theme2/src/registry.rs b/crates/theme2/src/registry.rs index eec82ef5a7..f30f5ead91 100644 --- a/crates/theme2/src/registry.rs +++ b/crates/theme2/src/registry.rs @@ -1,17 +1,22 @@ -use crate::{themes, Theme, ThemeMetadata}; +use crate::{zed_pro_family, ThemeFamily, ThemeVariant}; use anyhow::{anyhow, Result}; use gpui2::SharedString; use std::{collections::HashMap, sync::Arc}; pub struct ThemeRegistry { - themes: HashMap>, + themes: HashMap>, } impl ThemeRegistry { - fn insert_themes(&mut self, themes: impl IntoIterator) { + fn insert_theme_families(&mut self, families: impl IntoIterator) { + for family in families.into_iter() { + self.insert_themes(family.themes); + } + } + + fn insert_themes(&mut self, themes: impl IntoIterator) { for theme in themes.into_iter() { - self.themes - .insert(theme.metadata.name.clone(), Arc::new(theme)); + self.themes.insert(theme.name.clone(), Arc::new(theme)); } } @@ -19,11 +24,11 @@ impl ThemeRegistry { self.themes.keys().cloned() } - pub fn list(&self, _staff: bool) -> impl Iterator + '_ { - self.themes.values().map(|theme| theme.metadata.clone()) + pub fn list(&self, _staff: bool) -> impl Iterator + '_ { + self.themes.values().map(|theme| theme.name.clone()) } - pub fn get(&self, name: &str) -> Result> { + pub fn get(&self, name: &str) -> Result> { self.themes .get(name) .ok_or_else(|| anyhow!("theme not found: {}", name)) @@ -37,47 +42,7 @@ impl Default for ThemeRegistry { themes: HashMap::default(), }; - this.insert_themes([ - themes::andromeda(), - themes::atelier_cave_dark(), - themes::atelier_cave_light(), - themes::atelier_dune_dark(), - themes::atelier_dune_light(), - themes::atelier_estuary_dark(), - themes::atelier_estuary_light(), - themes::atelier_forest_dark(), - themes::atelier_forest_light(), - themes::atelier_heath_dark(), - themes::atelier_heath_light(), - themes::atelier_lakeside_dark(), - themes::atelier_lakeside_light(), - themes::atelier_plateau_dark(), - themes::atelier_plateau_light(), - themes::atelier_savanna_dark(), - themes::atelier_savanna_light(), - themes::atelier_seaside_dark(), - themes::atelier_seaside_light(), - themes::atelier_sulphurpool_dark(), - themes::atelier_sulphurpool_light(), - themes::ayu_dark(), - themes::ayu_light(), - themes::ayu_mirage(), - themes::gruvbox_dark(), - themes::gruvbox_dark_hard(), - themes::gruvbox_dark_soft(), - themes::gruvbox_light(), - themes::gruvbox_light_hard(), - themes::gruvbox_light_soft(), - themes::one_dark(), - themes::one_light(), - themes::rose_pine(), - themes::rose_pine_dawn(), - themes::rose_pine_moon(), - themes::sandcastle(), - themes::solarized_dark(), - themes::solarized_light(), - themes::summercamp(), - ]); + this.insert_theme_families([zed_pro_family()]); this } diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index 3a61bbbe1e..bad00ee196 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -1,4 +1,4 @@ -use crate::{zed_pro_moonlight, Theme, ThemeRegistry, ThemeVariant}; +use crate::{ThemeRegistry, ThemeVariant}; use anyhow::Result; use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; use schemars::{ @@ -21,7 +21,6 @@ pub struct ThemeSettings { pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, pub active_theme: Arc, - pub old_active_theme: Arc, } #[derive(Default)] @@ -124,8 +123,9 @@ impl settings2::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - active_theme: Arc::new(zed_pro_moonlight()), - old_active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + active_theme: themes.get("Zed Pro Moonlight").unwrap(), + // todo!(Read the theme name from the settings) + // active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; for value in user_values.into_iter().copied().cloned() { @@ -138,7 +138,7 @@ impl settings2::Settings for ThemeSettings { if let Some(value) = &value.theme { if let Some(theme) = themes.get(value).log_err() { - this.old_active_theme = theme; + this.active_theme = theme; } } diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs index 82c4c87796..1cf8564bca 100644 --- a/crates/theme2/src/syntax.rs +++ b/crates/theme2/src/syntax.rs @@ -1,227 +1,37 @@ -use gpui2::{FontWeight, Hsla, SharedString}; -use indexmap::IndexMap; +use gpui2::{HighlightStyle, Hsla}; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum SyntaxStyleName { - Comment, - CommentDoc, - Primary, - Predictive, - Hint, - Emphasis, - EmphasisStrong, - Title, - LinkUri, - LinkText, - TextLiteral, - Punctuation, - PunctuationBracket, - PunctuationDelimiter, - PunctuationSpecial, - PunctuationListMarker, - String, - StringSpecial, - StringSpecialSymbol, - StringEscape, - StringRegex, - Constructor, - Variant, - Type, - TypeBuiltin, - Variable, - VariableSpecial, - Label, - Tag, - Attribute, - Property, - Constant, - Keyword, - Enum, - Operator, - Number, - Boolean, - ConstantBuiltin, - Function, - FunctionBuiltin, - FunctionDefinition, - FunctionSpecialDefinition, - FunctionMethod, - FunctionMethodBuiltin, - Preproc, - Embedded, - Custom(SharedString), +#[derive(Clone)] +pub struct SyntaxTheme { + pub highlights: Vec<(String, HighlightStyle)>, } -impl std::str::FromStr for SyntaxStyleName { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(match s { - "attribute" => Self::Attribute, - "boolean" => Self::Boolean, - "comment" => Self::Comment, - "comment.doc" => Self::CommentDoc, - "constant" => Self::Constant, - "constructor" => Self::Constructor, - "embedded" => Self::Embedded, - "emphasis" => Self::Emphasis, - "emphasis.strong" => Self::EmphasisStrong, - "enum" => Self::Enum, - "function" => Self::Function, - "function.builtin" => Self::FunctionBuiltin, - "function.definition" => Self::FunctionDefinition, - "function.special_definition" => Self::FunctionSpecialDefinition, - "function.method" => Self::FunctionMethod, - "function.method_builtin" => Self::FunctionMethodBuiltin, - "hint" => Self::Hint, - "keyword" => Self::Keyword, - "label" => Self::Label, - "link_text" => Self::LinkText, - "link_uri" => Self::LinkUri, - "number" => Self::Number, - "operator" => Self::Operator, - "predictive" => Self::Predictive, - "preproc" => Self::Preproc, - "primary" => Self::Primary, - "property" => Self::Property, - "punctuation" => Self::Punctuation, - "punctuation.bracket" => Self::PunctuationBracket, - "punctuation.delimiter" => Self::PunctuationDelimiter, - "punctuation.list_marker" => Self::PunctuationListMarker, - "punctuation.special" => Self::PunctuationSpecial, - "string" => Self::String, - "string.escape" => Self::StringEscape, - "string.regex" => Self::StringRegex, - "string.special" => Self::StringSpecial, - "string.special.symbol" => Self::StringSpecialSymbol, - "tag" => Self::Tag, - "text.literal" => Self::TextLiteral, - "title" => Self::Title, - "type" => Self::Type, - "type.builtin" => Self::TypeBuiltin, - "variable" => Self::Variable, - "variable.special" => Self::VariableSpecial, - "constant.builtin" => Self::ConstantBuiltin, - "variant" => Self::Variant, - name => Self::Custom(name.to_string().into()), - }) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct SyntaxStyle { - pub color: Hsla, - pub weight: FontWeight, - pub underline: bool, - pub italic: bool, - // Nate: In the future I'd like to enable using background highlights for syntax highlighting - // pub highlight: Hsla, -} - -impl SyntaxStyle { - pub fn builder() -> SyntaxStyleBuilder { - SyntaxStyleBuilder::new() - } -} - -impl Default for SyntaxStyle { - fn default() -> Self { - Self { - color: gpui2::black(), - weight: FontWeight::default(), - italic: false, - underline: false, - } - } -} - -pub struct SyntaxStyleBuilder { - pub color: Hsla, - pub weight: FontWeight, - pub underline: bool, - pub italic: bool, -} - -impl SyntaxStyleBuilder { - pub fn new() -> Self { - SyntaxStyleBuilder { - color: gpui2::black(), - weight: FontWeight::default(), - underline: false, - italic: false, - } - } - - pub fn color(mut self, color: Hsla) -> Self { - self.color = color; - self - } - - pub fn weight(mut self, weight: FontWeight) -> Self { - self.weight = weight; - self - } - - pub fn underline(mut self, underline: bool) -> Self { - self.underline = underline; - self - } - - pub fn italic(mut self, italic: bool) -> Self { - self.italic = italic; - self - } - - pub fn build(self) -> SyntaxStyle { - SyntaxStyle { - color: self.color, - weight: self.weight, - underline: self.underline, - italic: self.italic, - } - } -} - -pub struct SyntaxStyles(pub IndexMap); - -impl SyntaxStyles { +impl SyntaxTheme { // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? pub fn new_test(colors: impl IntoIterator) -> Self { - Self(IndexMap::from_iter(colors.into_iter().map( - |(name, color)| { - ( - name.parse().unwrap(), - SyntaxStyle::builder().color(color).build(), - ) - }, - ))) + SyntaxTheme { + highlights: colors + .into_iter() + .map(|(key, color)| { + ( + key.to_owned(), + HighlightStyle { + color: Some(color), + ..Default::default() + }, + ) + }) + .collect(), + } } - pub fn get(&self, name: &str) -> SyntaxStyle { - self.0 - .get(&name.parse::().unwrap()) - .cloned() + pub fn get(&self, name: &str) -> HighlightStyle { + self.highlights + .iter() + .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None }) .unwrap_or_default() } pub fn color(&self, name: &str) -> Hsla { - self.get(name).color - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_syntax_style_name() { - let name = "comment".parse::().unwrap(); - assert_eq!(name, SyntaxStyleName::Comment); - } - - #[test] - fn create_custom_syntax_style_name() { - let name = "custom".parse::().unwrap(); - assert_eq!(name, SyntaxStyleName::Custom("custom".into())); + self.get(name).color.unwrap_or_default() } } diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index ea1ad5b26c..372e976bd3 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -5,7 +5,6 @@ mod registry; mod scale; mod settings; mod syntax; -mod themes; mod utils; pub use colors::*; @@ -16,9 +15,7 @@ pub use scale::*; pub use settings::*; pub use syntax::*; -use std::sync::Arc; - -use gpui2::{AppContext, HighlightStyle, Hsla, SharedString}; +use gpui2::{AppContext, Hsla, SharedString}; use settings2::Settings; #[derive(Debug, Clone, PartialEq)] @@ -45,8 +42,8 @@ impl ActiveTheme for AppContext { pub struct ThemeFamily { #[allow(dead_code)] pub(crate) id: String, - pub name: String, - pub author: String, + pub name: SharedString, + pub author: SharedString, pub themes: Vec, pub scales: ColorScales, } @@ -56,7 +53,7 @@ impl ThemeFamily {} pub struct ThemeVariant { #[allow(dead_code)] pub(crate) id: String, - pub name: String, + pub name: SharedString, pub appearance: Appearance, pub styles: ThemeStyle, } @@ -68,9 +65,9 @@ impl ThemeVariant { &self.styles.colors } - /// Returns the [`SyntaxStyles`] for the theme. + /// Returns the [`SyntaxTheme`] for the theme. #[inline(always)] - pub fn syntax(&self) -> &SyntaxStyles { + pub fn syntax(&self) -> &SyntaxTheme { &self.styles.syntax } @@ -80,125 +77,3 @@ impl ThemeVariant { self.syntax().color(name) } } - -pub struct Theme { - pub metadata: ThemeMetadata, - - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, - pub border: Hsla, - pub border_variant: Hsla, - pub border_focused: Hsla, - pub border_transparent: Hsla, - /// The background color of an elevated surface, like a modal, tooltip or toast. - pub elevated_surface: Hsla, - pub surface: Hsla, - /// Window background color of the base app - pub background: Hsla, - /// Default background for elements like filled buttons, - /// text fields, checkboxes, radio buttons, etc. - /// - TODO: Map to step 3. - pub filled_element: Hsla, - /// The background color of a hovered element, like a button being hovered - /// with a mouse, or hovered on a touch screen. - /// - TODO: Map to step 4. - pub filled_element_hover: Hsla, - /// The background color of an active element, like a button being pressed, - /// or tapped on a touch screen. - /// - TODO: Map to step 5. - pub filled_element_active: Hsla, - /// The background color of a selected element, like a selected tab, - /// a button toggled on, or a checkbox that is checked. - pub filled_element_selected: Hsla, - pub filled_element_disabled: Hsla, - pub ghost_element: Hsla, - /// The background color of a hovered element with no default background, - /// like a ghost-style button or an interactable list item. - /// - TODO: Map to step 3. - pub ghost_element_hover: Hsla, - /// - TODO: Map to step 4. - pub ghost_element_active: Hsla, - pub ghost_element_selected: Hsla, - pub ghost_element_disabled: Hsla, - pub text: Hsla, - pub text_muted: Hsla, - pub text_placeholder: Hsla, - pub text_disabled: Hsla, - pub text_accent: Hsla, - pub icon_muted: Hsla, - pub syntax: SyntaxTheme, - - pub status_bar: Hsla, - pub title_bar: Hsla, - pub toolbar: Hsla, - pub tab_bar: Hsla, - /// The background of the editor - pub editor: Hsla, - pub editor_subheader: Hsla, - pub editor_active_line: Hsla, - pub terminal: Hsla, - pub image_fallback_background: Hsla, - - pub git_created: Hsla, - pub git_modified: Hsla, - pub git_deleted: Hsla, - pub git_conflict: Hsla, - pub git_ignored: Hsla, - pub git_renamed: Hsla, - - pub players: [PlayerTheme; 8], -} - -#[derive(Clone)] -pub struct SyntaxTheme { - pub highlights: Vec<(String, HighlightStyle)>, -} - -impl SyntaxTheme { - // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? - pub fn new_test(colors: impl IntoIterator) -> Self { - SyntaxTheme { - highlights: colors - .into_iter() - .map(|(key, color)| { - ( - key.to_owned(), - HighlightStyle { - color: Some(color), - ..Default::default() - }, - ) - }) - .collect(), - } - } - - pub fn get(&self, name: &str) -> HighlightStyle { - self.highlights - .iter() - .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None }) - .unwrap_or_default() - } - - pub fn color(&self, name: &str) -> Hsla { - self.get(name).color.unwrap_or_default() - } -} - -#[derive(Clone, Copy)] -pub struct PlayerTheme { - pub cursor: Hsla, - pub selection: Hsla, -} - -#[derive(Clone)] -pub struct ThemeMetadata { - pub name: SharedString, - pub is_light: bool, -} - -pub struct Editor { - pub syntax: Arc, -} diff --git a/crates/theme2/src/themes/andromeda.rs b/crates/theme2/src/themes/andromeda.rs deleted file mode 100644 index 6afd7edd4d..0000000000 --- a/crates/theme2/src/themes/andromeda.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn andromeda() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Andromeda".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x2b2f38ff).into(), - border_variant: rgba(0x2b2f38ff).into(), - border_focused: rgba(0x183934ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x262933ff).into(), - surface: rgba(0x21242bff).into(), - background: rgba(0x262933ff).into(), - filled_element: rgba(0x262933ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x12231fff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x12231fff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf7f7f8ff).into(), - text_muted: rgba(0xaca8aeff).into(), - text_placeholder: rgba(0xf82871ff).into(), - text_disabled: rgba(0x6b6b73ff).into(), - text_accent: rgba(0x10a793ff).into(), - icon_muted: rgba(0xaca8aeff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("emphasis".into(), rgba(0x10a793ff).into()), - ("punctuation.bracket".into(), rgba(0xd8d5dbff).into()), - ("attribute".into(), rgba(0x10a793ff).into()), - ("variable".into(), rgba(0xf7f7f8ff).into()), - ("predictive".into(), rgba(0x315f70ff).into()), - ("property".into(), rgba(0x10a793ff).into()), - ("variant".into(), rgba(0x10a793ff).into()), - ("embedded".into(), rgba(0xf7f7f8ff).into()), - ("string.special".into(), rgba(0xf29c14ff).into()), - ("keyword".into(), rgba(0x10a793ff).into()), - ("tag".into(), rgba(0x10a793ff).into()), - ("enum".into(), rgba(0xf29c14ff).into()), - ("link_text".into(), rgba(0xf29c14ff).into()), - ("primary".into(), rgba(0xf7f7f8ff).into()), - ("punctuation".into(), rgba(0xd8d5dbff).into()), - ("punctuation.special".into(), rgba(0xd8d5dbff).into()), - ("function".into(), rgba(0xfee56cff).into()), - ("number".into(), rgba(0x96df71ff).into()), - ("preproc".into(), rgba(0xf7f7f8ff).into()), - ("operator".into(), rgba(0xf29c14ff).into()), - ("constructor".into(), rgba(0x10a793ff).into()), - ("string.escape".into(), rgba(0xafabb1ff).into()), - ("string.special.symbol".into(), rgba(0xf29c14ff).into()), - ("string".into(), rgba(0xf29c14ff).into()), - ("comment".into(), rgba(0xafabb1ff).into()), - ("hint".into(), rgba(0x618399ff).into()), - ("type".into(), rgba(0x08e7c5ff).into()), - ("label".into(), rgba(0x10a793ff).into()), - ("comment.doc".into(), rgba(0xafabb1ff).into()), - ("text.literal".into(), rgba(0xf29c14ff).into()), - ("constant".into(), rgba(0x96df71ff).into()), - ("string.regex".into(), rgba(0xf29c14ff).into()), - ("emphasis.strong".into(), rgba(0x10a793ff).into()), - ("title".into(), rgba(0xf7f7f8ff).into()), - ("punctuation.delimiter".into(), rgba(0xd8d5dbff).into()), - ("link_uri".into(), rgba(0x96df71ff).into()), - ("boolean".into(), rgba(0x96df71ff).into()), - ("punctuation.list_marker".into(), rgba(0xd8d5dbff).into()), - ], - }, - status_bar: rgba(0x262933ff).into(), - title_bar: rgba(0x262933ff).into(), - toolbar: rgba(0x1e2025ff).into(), - tab_bar: rgba(0x21242bff).into(), - editor: rgba(0x1e2025ff).into(), - editor_subheader: rgba(0x21242bff).into(), - editor_active_line: rgba(0x21242bff).into(), - terminal: rgba(0x1e2025ff).into(), - image_fallback_background: rgba(0x262933ff).into(), - git_created: rgba(0x96df71ff).into(), - git_modified: rgba(0x10a793ff).into(), - git_deleted: rgba(0xf82871ff).into(), - git_conflict: rgba(0xfee56cff).into(), - git_ignored: rgba(0x6b6b73ff).into(), - git_renamed: rgba(0xfee56cff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x10a793ff).into(), - selection: rgba(0x10a7933d).into(), - }, - PlayerTheme { - cursor: rgba(0x96df71ff).into(), - selection: rgba(0x96df713d).into(), - }, - PlayerTheme { - cursor: rgba(0xc74cecff).into(), - selection: rgba(0xc74cec3d).into(), - }, - PlayerTheme { - cursor: rgba(0xf29c14ff).into(), - selection: rgba(0xf29c143d).into(), - }, - PlayerTheme { - cursor: rgba(0x893ea6ff).into(), - selection: rgba(0x893ea63d).into(), - }, - PlayerTheme { - cursor: rgba(0x08e7c5ff).into(), - selection: rgba(0x08e7c53d).into(), - }, - PlayerTheme { - cursor: rgba(0xf82871ff).into(), - selection: rgba(0xf828713d).into(), - }, - PlayerTheme { - cursor: rgba(0xfee56cff).into(), - selection: rgba(0xfee56c3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_cave_dark.rs b/crates/theme2/src/themes/atelier_cave_dark.rs deleted file mode 100644 index c5190f4e98..0000000000 --- a/crates/theme2/src/themes/atelier_cave_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_cave_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Cave Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x56505eff).into(), - border_variant: rgba(0x56505eff).into(), - border_focused: rgba(0x222953ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3a353fff).into(), - surface: rgba(0x221f26ff).into(), - background: rgba(0x3a353fff).into(), - filled_element: rgba(0x3a353fff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x161a35ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x161a35ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xefecf4ff).into(), - text_muted: rgba(0x898591ff).into(), - text_placeholder: rgba(0xbe4677ff).into(), - text_disabled: rgba(0x756f7eff).into(), - text_accent: rgba(0x566ddaff).into(), - icon_muted: rgba(0x898591ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("comment.doc".into(), rgba(0x8b8792ff).into()), - ("tag".into(), rgba(0x566ddaff).into()), - ("link_text".into(), rgba(0xaa563bff).into()), - ("constructor".into(), rgba(0x566ddaff).into()), - ("punctuation".into(), rgba(0xe2dfe7ff).into()), - ("punctuation.special".into(), rgba(0xbf3fbfff).into()), - ("string.special.symbol".into(), rgba(0x299292ff).into()), - ("string.escape".into(), rgba(0x8b8792ff).into()), - ("emphasis".into(), rgba(0x566ddaff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("punctuation.delimiter".into(), rgba(0x8b8792ff).into()), - ("variant".into(), rgba(0xa06d3aff).into()), - ("variable.special".into(), rgba(0x9559e7ff).into()), - ("text.literal".into(), rgba(0xaa563bff).into()), - ("punctuation.list_marker".into(), rgba(0xe2dfe7ff).into()), - ("comment".into(), rgba(0x655f6dff).into()), - ("function.method".into(), rgba(0x576cdbff).into()), - ("property".into(), rgba(0xbe4677ff).into()), - ("operator".into(), rgba(0x8b8792ff).into()), - ("emphasis.strong".into(), rgba(0x566ddaff).into()), - ("label".into(), rgba(0x566ddaff).into()), - ("enum".into(), rgba(0xaa563bff).into()), - ("number".into(), rgba(0xaa563bff).into()), - ("primary".into(), rgba(0xe2dfe7ff).into()), - ("keyword".into(), rgba(0x9559e7ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("punctuation.bracket".into(), rgba(0x8b8792ff).into()), - ("constant".into(), rgba(0x2b9292ff).into()), - ("string.special".into(), rgba(0xbf3fbfff).into()), - ("title".into(), rgba(0xefecf4ff).into()), - ("preproc".into(), rgba(0xefecf4ff).into()), - ("link_uri".into(), rgba(0x2b9292ff).into()), - ("string".into(), rgba(0x299292ff).into()), - ("embedded".into(), rgba(0xefecf4ff).into()), - ("hint".into(), rgba(0x706897ff).into()), - ("boolean".into(), rgba(0x2b9292ff).into()), - ("variable".into(), rgba(0xe2dfe7ff).into()), - ("predictive".into(), rgba(0x615787ff).into()), - ("string.regex".into(), rgba(0x388bc6ff).into()), - ("function".into(), rgba(0x576cdbff).into()), - ("attribute".into(), rgba(0x566ddaff).into()), - ], - }, - status_bar: rgba(0x3a353fff).into(), - title_bar: rgba(0x3a353fff).into(), - toolbar: rgba(0x19171cff).into(), - tab_bar: rgba(0x221f26ff).into(), - editor: rgba(0x19171cff).into(), - editor_subheader: rgba(0x221f26ff).into(), - editor_active_line: rgba(0x221f26ff).into(), - terminal: rgba(0x19171cff).into(), - image_fallback_background: rgba(0x3a353fff).into(), - git_created: rgba(0x2b9292ff).into(), - git_modified: rgba(0x566ddaff).into(), - git_deleted: rgba(0xbe4677ff).into(), - git_conflict: rgba(0xa06d3aff).into(), - git_ignored: rgba(0x756f7eff).into(), - git_renamed: rgba(0xa06d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x566ddaff).into(), - selection: rgba(0x566dda3d).into(), - }, - PlayerTheme { - cursor: rgba(0x2b9292ff).into(), - selection: rgba(0x2b92923d).into(), - }, - PlayerTheme { - cursor: rgba(0xbf41bfff).into(), - selection: rgba(0xbf41bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0xaa563bff).into(), - selection: rgba(0xaa563b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x955ae6ff).into(), - selection: rgba(0x955ae63d).into(), - }, - PlayerTheme { - cursor: rgba(0x3a8bc6ff).into(), - selection: rgba(0x3a8bc63d).into(), - }, - PlayerTheme { - cursor: rgba(0xbe4677ff).into(), - selection: rgba(0xbe46773d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06d3aff).into(), - selection: rgba(0xa06d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_cave_light.rs b/crates/theme2/src/themes/atelier_cave_light.rs deleted file mode 100644 index ae2e912f14..0000000000 --- a/crates/theme2/src/themes/atelier_cave_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_cave_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Cave Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8f8b96ff).into(), - border_variant: rgba(0x8f8b96ff).into(), - border_focused: rgba(0xc8c7f2ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xbfbcc5ff).into(), - surface: rgba(0xe6e3ebff).into(), - background: rgba(0xbfbcc5ff).into(), - filled_element: rgba(0xbfbcc5ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe1e0f9ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe1e0f9ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x19171cff).into(), - text_muted: rgba(0x5a5462ff).into(), - text_placeholder: rgba(0xbd4677ff).into(), - text_disabled: rgba(0x6e6876ff).into(), - text_accent: rgba(0x586cdaff).into(), - icon_muted: rgba(0x5a5462ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("link_text".into(), rgba(0xaa573cff).into()), - ("string".into(), rgba(0x299292ff).into()), - ("emphasis".into(), rgba(0x586cdaff).into()), - ("label".into(), rgba(0x586cdaff).into()), - ("property".into(), rgba(0xbe4677ff).into()), - ("emphasis.strong".into(), rgba(0x586cdaff).into()), - ("constant".into(), rgba(0x2b9292ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("embedded".into(), rgba(0x19171cff).into()), - ("punctuation.special".into(), rgba(0xbf3fbfff).into()), - ("function".into(), rgba(0x576cdbff).into()), - ("tag".into(), rgba(0x586cdaff).into()), - ("number".into(), rgba(0xaa563bff).into()), - ("primary".into(), rgba(0x26232aff).into()), - ("text.literal".into(), rgba(0xaa573cff).into()), - ("variant".into(), rgba(0xa06d3aff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("punctuation".into(), rgba(0x26232aff).into()), - ("string.escape".into(), rgba(0x585260ff).into()), - ("keyword".into(), rgba(0x9559e7ff).into()), - ("title".into(), rgba(0x19171cff).into()), - ("constructor".into(), rgba(0x586cdaff).into()), - ("punctuation.list_marker".into(), rgba(0x26232aff).into()), - ("string.special".into(), rgba(0xbf3fbfff).into()), - ("operator".into(), rgba(0x585260ff).into()), - ("function.method".into(), rgba(0x576cdbff).into()), - ("link_uri".into(), rgba(0x2b9292ff).into()), - ("variable.special".into(), rgba(0x9559e7ff).into()), - ("hint".into(), rgba(0x776d9dff).into()), - ("punctuation.bracket".into(), rgba(0x585260ff).into()), - ("string.special.symbol".into(), rgba(0x299292ff).into()), - ("predictive".into(), rgba(0x887fafff).into()), - ("attribute".into(), rgba(0x586cdaff).into()), - ("enum".into(), rgba(0xaa573cff).into()), - ("preproc".into(), rgba(0x19171cff).into()), - ("boolean".into(), rgba(0x2b9292ff).into()), - ("variable".into(), rgba(0x26232aff).into()), - ("comment.doc".into(), rgba(0x585260ff).into()), - ("string.regex".into(), rgba(0x388bc6ff).into()), - ("punctuation.delimiter".into(), rgba(0x585260ff).into()), - ("comment".into(), rgba(0x7d7787ff).into()), - ], - }, - status_bar: rgba(0xbfbcc5ff).into(), - title_bar: rgba(0xbfbcc5ff).into(), - toolbar: rgba(0xefecf4ff).into(), - tab_bar: rgba(0xe6e3ebff).into(), - editor: rgba(0xefecf4ff).into(), - editor_subheader: rgba(0xe6e3ebff).into(), - editor_active_line: rgba(0xe6e3ebff).into(), - terminal: rgba(0xefecf4ff).into(), - image_fallback_background: rgba(0xbfbcc5ff).into(), - git_created: rgba(0x2b9292ff).into(), - git_modified: rgba(0x586cdaff).into(), - git_deleted: rgba(0xbd4677ff).into(), - git_conflict: rgba(0xa06e3bff).into(), - git_ignored: rgba(0x6e6876ff).into(), - git_renamed: rgba(0xa06e3bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x586cdaff).into(), - selection: rgba(0x586cda3d).into(), - }, - PlayerTheme { - cursor: rgba(0x2b9292ff).into(), - selection: rgba(0x2b92923d).into(), - }, - PlayerTheme { - cursor: rgba(0xbf41bfff).into(), - selection: rgba(0xbf41bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0xaa573cff).into(), - selection: rgba(0xaa573c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x955ae6ff).into(), - selection: rgba(0x955ae63d).into(), - }, - PlayerTheme { - cursor: rgba(0x3a8bc6ff).into(), - selection: rgba(0x3a8bc63d).into(), - }, - PlayerTheme { - cursor: rgba(0xbd4677ff).into(), - selection: rgba(0xbd46773d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06e3bff).into(), - selection: rgba(0xa06e3b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_dune_dark.rs b/crates/theme2/src/themes/atelier_dune_dark.rs deleted file mode 100644 index 03d0c5eea0..0000000000 --- a/crates/theme2/src/themes/atelier_dune_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_dune_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Dune Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x6c695cff).into(), - border_variant: rgba(0x6c695cff).into(), - border_focused: rgba(0x262f56ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x45433bff).into(), - surface: rgba(0x262622ff).into(), - background: rgba(0x45433bff).into(), - filled_element: rgba(0x45433bff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x171e38ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x171e38ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfefbecff).into(), - text_muted: rgba(0xa4a08bff).into(), - text_placeholder: rgba(0xd73837ff).into(), - text_disabled: rgba(0x8f8b77ff).into(), - text_accent: rgba(0x6684e0ff).into(), - icon_muted: rgba(0xa4a08bff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("constructor".into(), rgba(0x6684e0ff).into()), - ("punctuation".into(), rgba(0xe8e4cfff).into()), - ("punctuation.delimiter".into(), rgba(0xa6a28cff).into()), - ("string.special".into(), rgba(0xd43451ff).into()), - ("string.escape".into(), rgba(0xa6a28cff).into()), - ("comment".into(), rgba(0x7d7a68ff).into()), - ("enum".into(), rgba(0xb65611ff).into()), - ("variable.special".into(), rgba(0xb854d4ff).into()), - ("primary".into(), rgba(0xe8e4cfff).into()), - ("comment.doc".into(), rgba(0xa6a28cff).into()), - ("label".into(), rgba(0x6684e0ff).into()), - ("operator".into(), rgba(0xa6a28cff).into()), - ("string".into(), rgba(0x5fac38ff).into()), - ("variant".into(), rgba(0xae9512ff).into()), - ("variable".into(), rgba(0xe8e4cfff).into()), - ("function.method".into(), rgba(0x6583e1ff).into()), - ( - "function.special.definition".into(), - rgba(0xae9512ff).into(), - ), - ("string.regex".into(), rgba(0x1ead82ff).into()), - ("emphasis.strong".into(), rgba(0x6684e0ff).into()), - ("punctuation.special".into(), rgba(0xd43451ff).into()), - ("punctuation.bracket".into(), rgba(0xa6a28cff).into()), - ("link_text".into(), rgba(0xb65611ff).into()), - ("link_uri".into(), rgba(0x5fac39ff).into()), - ("boolean".into(), rgba(0x5fac39ff).into()), - ("hint".into(), rgba(0xb17272ff).into()), - ("tag".into(), rgba(0x6684e0ff).into()), - ("function".into(), rgba(0x6583e1ff).into()), - ("title".into(), rgba(0xfefbecff).into()), - ("property".into(), rgba(0xd73737ff).into()), - ("type".into(), rgba(0xae9512ff).into()), - ("constant".into(), rgba(0x5fac39ff).into()), - ("attribute".into(), rgba(0x6684e0ff).into()), - ("predictive".into(), rgba(0x9c6262ff).into()), - ("string.special.symbol".into(), rgba(0x5fac38ff).into()), - ("punctuation.list_marker".into(), rgba(0xe8e4cfff).into()), - ("emphasis".into(), rgba(0x6684e0ff).into()), - ("keyword".into(), rgba(0xb854d4ff).into()), - ("text.literal".into(), rgba(0xb65611ff).into()), - ("number".into(), rgba(0xb65610ff).into()), - ("preproc".into(), rgba(0xfefbecff).into()), - ("embedded".into(), rgba(0xfefbecff).into()), - ], - }, - status_bar: rgba(0x45433bff).into(), - title_bar: rgba(0x45433bff).into(), - toolbar: rgba(0x20201dff).into(), - tab_bar: rgba(0x262622ff).into(), - editor: rgba(0x20201dff).into(), - editor_subheader: rgba(0x262622ff).into(), - editor_active_line: rgba(0x262622ff).into(), - terminal: rgba(0x20201dff).into(), - image_fallback_background: rgba(0x45433bff).into(), - git_created: rgba(0x5fac39ff).into(), - git_modified: rgba(0x6684e0ff).into(), - git_deleted: rgba(0xd73837ff).into(), - git_conflict: rgba(0xae9414ff).into(), - git_ignored: rgba(0x8f8b77ff).into(), - git_renamed: rgba(0xae9414ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x6684e0ff).into(), - selection: rgba(0x6684e03d).into(), - }, - PlayerTheme { - cursor: rgba(0x5fac39ff).into(), - selection: rgba(0x5fac393d).into(), - }, - PlayerTheme { - cursor: rgba(0xd43651ff).into(), - selection: rgba(0xd436513d).into(), - }, - PlayerTheme { - cursor: rgba(0xb65611ff).into(), - selection: rgba(0xb656113d).into(), - }, - PlayerTheme { - cursor: rgba(0xb854d3ff).into(), - selection: rgba(0xb854d33d).into(), - }, - PlayerTheme { - cursor: rgba(0x20ad83ff).into(), - selection: rgba(0x20ad833d).into(), - }, - PlayerTheme { - cursor: rgba(0xd73837ff).into(), - selection: rgba(0xd738373d).into(), - }, - PlayerTheme { - cursor: rgba(0xae9414ff).into(), - selection: rgba(0xae94143d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_dune_light.rs b/crates/theme2/src/themes/atelier_dune_light.rs deleted file mode 100644 index 1d0f944916..0000000000 --- a/crates/theme2/src/themes/atelier_dune_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_dune_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Dune Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xa8a48eff).into(), - border_variant: rgba(0xa8a48eff).into(), - border_focused: rgba(0xcdd1f5ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xcecab4ff).into(), - surface: rgba(0xeeebd7ff).into(), - background: rgba(0xcecab4ff).into(), - filled_element: rgba(0xcecab4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe3e5faff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe3e5faff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x20201dff).into(), - text_muted: rgba(0x706d5fff).into(), - text_placeholder: rgba(0xd73737ff).into(), - text_disabled: rgba(0x878471ff).into(), - text_accent: rgba(0x6684dfff).into(), - icon_muted: rgba(0x706d5fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("primary".into(), rgba(0x292824ff).into()), - ("comment".into(), rgba(0x999580ff).into()), - ("type".into(), rgba(0xae9512ff).into()), - ("variant".into(), rgba(0xae9512ff).into()), - ("label".into(), rgba(0x6684dfff).into()), - ("function.method".into(), rgba(0x6583e1ff).into()), - ("variable.special".into(), rgba(0xb854d4ff).into()), - ("string.regex".into(), rgba(0x1ead82ff).into()), - ("property".into(), rgba(0xd73737ff).into()), - ("keyword".into(), rgba(0xb854d4ff).into()), - ("number".into(), rgba(0xb65610ff).into()), - ("punctuation.list_marker".into(), rgba(0x292824ff).into()), - ( - "function.special.definition".into(), - rgba(0xae9512ff).into(), - ), - ("punctuation.special".into(), rgba(0xd43451ff).into()), - ("punctuation".into(), rgba(0x292824ff).into()), - ("punctuation.delimiter".into(), rgba(0x6e6b5eff).into()), - ("tag".into(), rgba(0x6684dfff).into()), - ("link_text".into(), rgba(0xb65712ff).into()), - ("boolean".into(), rgba(0x61ac39ff).into()), - ("hint".into(), rgba(0xb37979ff).into()), - ("operator".into(), rgba(0x6e6b5eff).into()), - ("constant".into(), rgba(0x61ac39ff).into()), - ("function".into(), rgba(0x6583e1ff).into()), - ("text.literal".into(), rgba(0xb65712ff).into()), - ("string.special.symbol".into(), rgba(0x5fac38ff).into()), - ("attribute".into(), rgba(0x6684dfff).into()), - ("emphasis".into(), rgba(0x6684dfff).into()), - ("preproc".into(), rgba(0x20201dff).into()), - ("comment.doc".into(), rgba(0x6e6b5eff).into()), - ("punctuation.bracket".into(), rgba(0x6e6b5eff).into()), - ("string".into(), rgba(0x5fac38ff).into()), - ("enum".into(), rgba(0xb65712ff).into()), - ("variable".into(), rgba(0x292824ff).into()), - ("string.special".into(), rgba(0xd43451ff).into()), - ("embedded".into(), rgba(0x20201dff).into()), - ("emphasis.strong".into(), rgba(0x6684dfff).into()), - ("predictive".into(), rgba(0xc88a8aff).into()), - ("title".into(), rgba(0x20201dff).into()), - ("constructor".into(), rgba(0x6684dfff).into()), - ("link_uri".into(), rgba(0x61ac39ff).into()), - ("string.escape".into(), rgba(0x6e6b5eff).into()), - ], - }, - status_bar: rgba(0xcecab4ff).into(), - title_bar: rgba(0xcecab4ff).into(), - toolbar: rgba(0xfefbecff).into(), - tab_bar: rgba(0xeeebd7ff).into(), - editor: rgba(0xfefbecff).into(), - editor_subheader: rgba(0xeeebd7ff).into(), - editor_active_line: rgba(0xeeebd7ff).into(), - terminal: rgba(0xfefbecff).into(), - image_fallback_background: rgba(0xcecab4ff).into(), - git_created: rgba(0x61ac39ff).into(), - git_modified: rgba(0x6684dfff).into(), - git_deleted: rgba(0xd73737ff).into(), - git_conflict: rgba(0xae9414ff).into(), - git_ignored: rgba(0x878471ff).into(), - git_renamed: rgba(0xae9414ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x6684dfff).into(), - selection: rgba(0x6684df3d).into(), - }, - PlayerTheme { - cursor: rgba(0x61ac39ff).into(), - selection: rgba(0x61ac393d).into(), - }, - PlayerTheme { - cursor: rgba(0xd43652ff).into(), - selection: rgba(0xd436523d).into(), - }, - PlayerTheme { - cursor: rgba(0xb65712ff).into(), - selection: rgba(0xb657123d).into(), - }, - PlayerTheme { - cursor: rgba(0xb755d3ff).into(), - selection: rgba(0xb755d33d).into(), - }, - PlayerTheme { - cursor: rgba(0x21ad82ff).into(), - selection: rgba(0x21ad823d).into(), - }, - PlayerTheme { - cursor: rgba(0xd73737ff).into(), - selection: rgba(0xd737373d).into(), - }, - PlayerTheme { - cursor: rgba(0xae9414ff).into(), - selection: rgba(0xae94143d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_estuary_dark.rs b/crates/theme2/src/themes/atelier_estuary_dark.rs deleted file mode 100644 index ad5c9fbc1e..0000000000 --- a/crates/theme2/src/themes/atelier_estuary_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_estuary_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Estuary Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5d5c4cff).into(), - border_variant: rgba(0x5d5c4cff).into(), - border_focused: rgba(0x1c3927ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x424136ff).into(), - surface: rgba(0x2c2b23ff).into(), - background: rgba(0x424136ff).into(), - filled_element: rgba(0x424136ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x142319ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x142319ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf4f3ecff).into(), - text_muted: rgba(0x91907fff).into(), - text_placeholder: rgba(0xba6136ff).into(), - text_disabled: rgba(0x7d7c6aff).into(), - text_accent: rgba(0x36a165ff).into(), - icon_muted: rgba(0x91907fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special.symbol".into(), rgba(0x7c9725ff).into()), - ("comment".into(), rgba(0x6c6b5aff).into()), - ("operator".into(), rgba(0x929181ff).into()), - ("punctuation.delimiter".into(), rgba(0x929181ff).into()), - ("keyword".into(), rgba(0x5f9182ff).into()), - ("punctuation.special".into(), rgba(0x9d6b7bff).into()), - ("preproc".into(), rgba(0xf4f3ecff).into()), - ("title".into(), rgba(0xf4f3ecff).into()), - ("string.escape".into(), rgba(0x929181ff).into()), - ("boolean".into(), rgba(0x7d9726ff).into()), - ("punctuation.bracket".into(), rgba(0x929181ff).into()), - ("emphasis.strong".into(), rgba(0x36a165ff).into()), - ("string".into(), rgba(0x7c9725ff).into()), - ("constant".into(), rgba(0x7d9726ff).into()), - ("link_text".into(), rgba(0xae7214ff).into()), - ("tag".into(), rgba(0x36a165ff).into()), - ("hint".into(), rgba(0x6f815aff).into()), - ("punctuation".into(), rgba(0xe7e6dfff).into()), - ("string.regex".into(), rgba(0x5a9d47ff).into()), - ("variant".into(), rgba(0xa5980cff).into()), - ("type".into(), rgba(0xa5980cff).into()), - ("attribute".into(), rgba(0x36a165ff).into()), - ("emphasis".into(), rgba(0x36a165ff).into()), - ("enum".into(), rgba(0xae7214ff).into()), - ("number".into(), rgba(0xae7312ff).into()), - ("property".into(), rgba(0xba6135ff).into()), - ("predictive".into(), rgba(0x5f724cff).into()), - ( - "function.special.definition".into(), - rgba(0xa5980cff).into(), - ), - ("link_uri".into(), rgba(0x7d9726ff).into()), - ("variable.special".into(), rgba(0x5f9182ff).into()), - ("text.literal".into(), rgba(0xae7214ff).into()), - ("label".into(), rgba(0x36a165ff).into()), - ("primary".into(), rgba(0xe7e6dfff).into()), - ("variable".into(), rgba(0xe7e6dfff).into()), - ("embedded".into(), rgba(0xf4f3ecff).into()), - ("function.method".into(), rgba(0x35a166ff).into()), - ("comment.doc".into(), rgba(0x929181ff).into()), - ("string.special".into(), rgba(0x9d6b7bff).into()), - ("constructor".into(), rgba(0x36a165ff).into()), - ("punctuation.list_marker".into(), rgba(0xe7e6dfff).into()), - ("function".into(), rgba(0x35a166ff).into()), - ], - }, - status_bar: rgba(0x424136ff).into(), - title_bar: rgba(0x424136ff).into(), - toolbar: rgba(0x22221bff).into(), - tab_bar: rgba(0x2c2b23ff).into(), - editor: rgba(0x22221bff).into(), - editor_subheader: rgba(0x2c2b23ff).into(), - editor_active_line: rgba(0x2c2b23ff).into(), - terminal: rgba(0x22221bff).into(), - image_fallback_background: rgba(0x424136ff).into(), - git_created: rgba(0x7d9726ff).into(), - git_modified: rgba(0x36a165ff).into(), - git_deleted: rgba(0xba6136ff).into(), - git_conflict: rgba(0xa5980fff).into(), - git_ignored: rgba(0x7d7c6aff).into(), - git_renamed: rgba(0xa5980fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x36a165ff).into(), - selection: rgba(0x36a1653d).into(), - }, - PlayerTheme { - cursor: rgba(0x7d9726ff).into(), - selection: rgba(0x7d97263d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d6b7bff).into(), - selection: rgba(0x9d6b7b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xae7214ff).into(), - selection: rgba(0xae72143d).into(), - }, - PlayerTheme { - cursor: rgba(0x5f9182ff).into(), - selection: rgba(0x5f91823d).into(), - }, - PlayerTheme { - cursor: rgba(0x5a9d47ff).into(), - selection: rgba(0x5a9d473d).into(), - }, - PlayerTheme { - cursor: rgba(0xba6136ff).into(), - selection: rgba(0xba61363d).into(), - }, - PlayerTheme { - cursor: rgba(0xa5980fff).into(), - selection: rgba(0xa5980f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_estuary_light.rs b/crates/theme2/src/themes/atelier_estuary_light.rs deleted file mode 100644 index 91eaa88fab..0000000000 --- a/crates/theme2/src/themes/atelier_estuary_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_estuary_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Estuary Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x969585ff).into(), - border_variant: rgba(0x969585ff).into(), - border_focused: rgba(0xbbddc6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc5c4b9ff).into(), - surface: rgba(0xebeae3ff).into(), - background: rgba(0xc5c4b9ff).into(), - filled_element: rgba(0xc5c4b9ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd9ecdfff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd9ecdfff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x22221bff).into(), - text_muted: rgba(0x61604fff).into(), - text_placeholder: rgba(0xba6336ff).into(), - text_disabled: rgba(0x767463ff).into(), - text_accent: rgba(0x37a165ff).into(), - icon_muted: rgba(0x61604fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special".into(), rgba(0x9d6b7bff).into()), - ("link_text".into(), rgba(0xae7214ff).into()), - ("emphasis.strong".into(), rgba(0x37a165ff).into()), - ("tag".into(), rgba(0x37a165ff).into()), - ("primary".into(), rgba(0x302f27ff).into()), - ("emphasis".into(), rgba(0x37a165ff).into()), - ("hint".into(), rgba(0x758961ff).into()), - ("title".into(), rgba(0x22221bff).into()), - ("string.regex".into(), rgba(0x5a9d47ff).into()), - ("attribute".into(), rgba(0x37a165ff).into()), - ("string.escape".into(), rgba(0x5f5e4eff).into()), - ("embedded".into(), rgba(0x22221bff).into()), - ("punctuation.bracket".into(), rgba(0x5f5e4eff).into()), - ( - "function.special.definition".into(), - rgba(0xa5980cff).into(), - ), - ("operator".into(), rgba(0x5f5e4eff).into()), - ("constant".into(), rgba(0x7c9728ff).into()), - ("comment.doc".into(), rgba(0x5f5e4eff).into()), - ("label".into(), rgba(0x37a165ff).into()), - ("variable".into(), rgba(0x302f27ff).into()), - ("punctuation".into(), rgba(0x302f27ff).into()), - ("punctuation.delimiter".into(), rgba(0x5f5e4eff).into()), - ("comment".into(), rgba(0x878573ff).into()), - ("punctuation.special".into(), rgba(0x9d6b7bff).into()), - ("string.special.symbol".into(), rgba(0x7c9725ff).into()), - ("enum".into(), rgba(0xae7214ff).into()), - ("variable.special".into(), rgba(0x5f9182ff).into()), - ("link_uri".into(), rgba(0x7c9728ff).into()), - ("punctuation.list_marker".into(), rgba(0x302f27ff).into()), - ("number".into(), rgba(0xae7312ff).into()), - ("function".into(), rgba(0x35a166ff).into()), - ("text.literal".into(), rgba(0xae7214ff).into()), - ("boolean".into(), rgba(0x7c9728ff).into()), - ("predictive".into(), rgba(0x879a72ff).into()), - ("type".into(), rgba(0xa5980cff).into()), - ("constructor".into(), rgba(0x37a165ff).into()), - ("property".into(), rgba(0xba6135ff).into()), - ("keyword".into(), rgba(0x5f9182ff).into()), - ("function.method".into(), rgba(0x35a166ff).into()), - ("variant".into(), rgba(0xa5980cff).into()), - ("string".into(), rgba(0x7c9725ff).into()), - ("preproc".into(), rgba(0x22221bff).into()), - ], - }, - status_bar: rgba(0xc5c4b9ff).into(), - title_bar: rgba(0xc5c4b9ff).into(), - toolbar: rgba(0xf4f3ecff).into(), - tab_bar: rgba(0xebeae3ff).into(), - editor: rgba(0xf4f3ecff).into(), - editor_subheader: rgba(0xebeae3ff).into(), - editor_active_line: rgba(0xebeae3ff).into(), - terminal: rgba(0xf4f3ecff).into(), - image_fallback_background: rgba(0xc5c4b9ff).into(), - git_created: rgba(0x7c9728ff).into(), - git_modified: rgba(0x37a165ff).into(), - git_deleted: rgba(0xba6336ff).into(), - git_conflict: rgba(0xa5980fff).into(), - git_ignored: rgba(0x767463ff).into(), - git_renamed: rgba(0xa5980fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x37a165ff).into(), - selection: rgba(0x37a1653d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c9728ff).into(), - selection: rgba(0x7c97283d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d6b7bff).into(), - selection: rgba(0x9d6b7b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xae7214ff).into(), - selection: rgba(0xae72143d).into(), - }, - PlayerTheme { - cursor: rgba(0x5f9182ff).into(), - selection: rgba(0x5f91823d).into(), - }, - PlayerTheme { - cursor: rgba(0x5c9d49ff).into(), - selection: rgba(0x5c9d493d).into(), - }, - PlayerTheme { - cursor: rgba(0xba6336ff).into(), - selection: rgba(0xba63363d).into(), - }, - PlayerTheme { - cursor: rgba(0xa5980fff).into(), - selection: rgba(0xa5980f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_forest_dark.rs b/crates/theme2/src/themes/atelier_forest_dark.rs deleted file mode 100644 index 83228e671f..0000000000 --- a/crates/theme2/src/themes/atelier_forest_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_forest_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Forest Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x665f5cff).into(), - border_variant: rgba(0x665f5cff).into(), - border_focused: rgba(0x182d5bff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x443c39ff).into(), - surface: rgba(0x27211eff).into(), - background: rgba(0x443c39ff).into(), - filled_element: rgba(0x443c39ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0f1c3dff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0f1c3dff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf0eeedff).into(), - text_muted: rgba(0xa79f9dff).into(), - text_placeholder: rgba(0xf22c3fff).into(), - text_disabled: rgba(0x8e8683ff).into(), - text_accent: rgba(0x407ee6ff).into(), - icon_muted: rgba(0xa79f9dff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("link_uri".into(), rgba(0x7a9726ff).into()), - ("punctuation.list_marker".into(), rgba(0xe6e2e0ff).into()), - ("type".into(), rgba(0xc38417ff).into()), - ("punctuation.bracket".into(), rgba(0xa8a19fff).into()), - ("punctuation".into(), rgba(0xe6e2e0ff).into()), - ("preproc".into(), rgba(0xf0eeedff).into()), - ("punctuation.special".into(), rgba(0xc33ff3ff).into()), - ("variable.special".into(), rgba(0x6666eaff).into()), - ("tag".into(), rgba(0x407ee6ff).into()), - ("constructor".into(), rgba(0x407ee6ff).into()), - ("title".into(), rgba(0xf0eeedff).into()), - ("hint".into(), rgba(0xa77087ff).into()), - ("constant".into(), rgba(0x7a9726ff).into()), - ("number".into(), rgba(0xdf521fff).into()), - ("emphasis.strong".into(), rgba(0x407ee6ff).into()), - ("boolean".into(), rgba(0x7a9726ff).into()), - ("comment".into(), rgba(0x766e6bff).into()), - ("string.special".into(), rgba(0xc33ff3ff).into()), - ("text.literal".into(), rgba(0xdf5321ff).into()), - ("string.regex".into(), rgba(0x3c96b8ff).into()), - ("enum".into(), rgba(0xdf5321ff).into()), - ("operator".into(), rgba(0xa8a19fff).into()), - ("embedded".into(), rgba(0xf0eeedff).into()), - ("string.special.symbol".into(), rgba(0x7a9725ff).into()), - ("predictive".into(), rgba(0x8f5b70ff).into()), - ("comment.doc".into(), rgba(0xa8a19fff).into()), - ("variant".into(), rgba(0xc38417ff).into()), - ("label".into(), rgba(0x407ee6ff).into()), - ("property".into(), rgba(0xf22c40ff).into()), - ("keyword".into(), rgba(0x6666eaff).into()), - ("function".into(), rgba(0x3f7ee7ff).into()), - ("string.escape".into(), rgba(0xa8a19fff).into()), - ("string".into(), rgba(0x7a9725ff).into()), - ("primary".into(), rgba(0xe6e2e0ff).into()), - ("function.method".into(), rgba(0x3f7ee7ff).into()), - ("link_text".into(), rgba(0xdf5321ff).into()), - ("attribute".into(), rgba(0x407ee6ff).into()), - ("emphasis".into(), rgba(0x407ee6ff).into()), - ( - "function.special.definition".into(), - rgba(0xc38417ff).into(), - ), - ("variable".into(), rgba(0xe6e2e0ff).into()), - ("punctuation.delimiter".into(), rgba(0xa8a19fff).into()), - ], - }, - status_bar: rgba(0x443c39ff).into(), - title_bar: rgba(0x443c39ff).into(), - toolbar: rgba(0x1b1918ff).into(), - tab_bar: rgba(0x27211eff).into(), - editor: rgba(0x1b1918ff).into(), - editor_subheader: rgba(0x27211eff).into(), - editor_active_line: rgba(0x27211eff).into(), - terminal: rgba(0x1b1918ff).into(), - image_fallback_background: rgba(0x443c39ff).into(), - git_created: rgba(0x7a9726ff).into(), - git_modified: rgba(0x407ee6ff).into(), - git_deleted: rgba(0xf22c3fff).into(), - git_conflict: rgba(0xc38418ff).into(), - git_ignored: rgba(0x8e8683ff).into(), - git_renamed: rgba(0xc38418ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x407ee6ff).into(), - selection: rgba(0x407ee63d).into(), - }, - PlayerTheme { - cursor: rgba(0x7a9726ff).into(), - selection: rgba(0x7a97263d).into(), - }, - PlayerTheme { - cursor: rgba(0xc340f2ff).into(), - selection: rgba(0xc340f23d).into(), - }, - PlayerTheme { - cursor: rgba(0xdf5321ff).into(), - selection: rgba(0xdf53213d).into(), - }, - PlayerTheme { - cursor: rgba(0x6565e9ff).into(), - selection: rgba(0x6565e93d).into(), - }, - PlayerTheme { - cursor: rgba(0x3d97b8ff).into(), - selection: rgba(0x3d97b83d).into(), - }, - PlayerTheme { - cursor: rgba(0xf22c3fff).into(), - selection: rgba(0xf22c3f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xc38418ff).into(), - selection: rgba(0xc384183d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_forest_light.rs b/crates/theme2/src/themes/atelier_forest_light.rs deleted file mode 100644 index 882d5c2fcb..0000000000 --- a/crates/theme2/src/themes/atelier_forest_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_forest_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Forest Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xaaa3a1ff).into(), - border_variant: rgba(0xaaa3a1ff).into(), - border_focused: rgba(0xc6cef7ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xccc7c5ff).into(), - surface: rgba(0xe9e6e4ff).into(), - background: rgba(0xccc7c5ff).into(), - filled_element: rgba(0xccc7c5ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdfe3fbff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdfe3fbff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x1b1918ff).into(), - text_muted: rgba(0x6a6360ff).into(), - text_placeholder: rgba(0xf22e40ff).into(), - text_disabled: rgba(0x837b78ff).into(), - text_accent: rgba(0x407ee6ff).into(), - icon_muted: rgba(0x6a6360ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.special".into(), rgba(0xc33ff3ff).into()), - ("text.literal".into(), rgba(0xdf5421ff).into()), - ("string.escape".into(), rgba(0x68615eff).into()), - ("string.regex".into(), rgba(0x3c96b8ff).into()), - ("number".into(), rgba(0xdf521fff).into()), - ("preproc".into(), rgba(0x1b1918ff).into()), - ("keyword".into(), rgba(0x6666eaff).into()), - ("variable.special".into(), rgba(0x6666eaff).into()), - ("punctuation.delimiter".into(), rgba(0x68615eff).into()), - ("emphasis.strong".into(), rgba(0x407ee6ff).into()), - ("boolean".into(), rgba(0x7a9728ff).into()), - ("variant".into(), rgba(0xc38417ff).into()), - ("predictive".into(), rgba(0xbe899eff).into()), - ("tag".into(), rgba(0x407ee6ff).into()), - ("property".into(), rgba(0xf22c40ff).into()), - ("enum".into(), rgba(0xdf5421ff).into()), - ("attribute".into(), rgba(0x407ee6ff).into()), - ("function.method".into(), rgba(0x3f7ee7ff).into()), - ("function".into(), rgba(0x3f7ee7ff).into()), - ("emphasis".into(), rgba(0x407ee6ff).into()), - ("primary".into(), rgba(0x2c2421ff).into()), - ("variable".into(), rgba(0x2c2421ff).into()), - ("constant".into(), rgba(0x7a9728ff).into()), - ("title".into(), rgba(0x1b1918ff).into()), - ("comment.doc".into(), rgba(0x68615eff).into()), - ("constructor".into(), rgba(0x407ee6ff).into()), - ("type".into(), rgba(0xc38417ff).into()), - ("punctuation.list_marker".into(), rgba(0x2c2421ff).into()), - ("punctuation".into(), rgba(0x2c2421ff).into()), - ("string".into(), rgba(0x7a9725ff).into()), - ("label".into(), rgba(0x407ee6ff).into()), - ("string.special".into(), rgba(0xc33ff3ff).into()), - ("embedded".into(), rgba(0x1b1918ff).into()), - ("link_text".into(), rgba(0xdf5421ff).into()), - ("punctuation.bracket".into(), rgba(0x68615eff).into()), - ("comment".into(), rgba(0x9c9491ff).into()), - ( - "function.special.definition".into(), - rgba(0xc38417ff).into(), - ), - ("link_uri".into(), rgba(0x7a9728ff).into()), - ("operator".into(), rgba(0x68615eff).into()), - ("hint".into(), rgba(0xa67287ff).into()), - ("string.special.symbol".into(), rgba(0x7a9725ff).into()), - ], - }, - status_bar: rgba(0xccc7c5ff).into(), - title_bar: rgba(0xccc7c5ff).into(), - toolbar: rgba(0xf0eeedff).into(), - tab_bar: rgba(0xe9e6e4ff).into(), - editor: rgba(0xf0eeedff).into(), - editor_subheader: rgba(0xe9e6e4ff).into(), - editor_active_line: rgba(0xe9e6e4ff).into(), - terminal: rgba(0xf0eeedff).into(), - image_fallback_background: rgba(0xccc7c5ff).into(), - git_created: rgba(0x7a9728ff).into(), - git_modified: rgba(0x407ee6ff).into(), - git_deleted: rgba(0xf22e40ff).into(), - git_conflict: rgba(0xc38419ff).into(), - git_ignored: rgba(0x837b78ff).into(), - git_renamed: rgba(0xc38419ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x407ee6ff).into(), - selection: rgba(0x407ee63d).into(), - }, - PlayerTheme { - cursor: rgba(0x7a9728ff).into(), - selection: rgba(0x7a97283d).into(), - }, - PlayerTheme { - cursor: rgba(0xc340f2ff).into(), - selection: rgba(0xc340f23d).into(), - }, - PlayerTheme { - cursor: rgba(0xdf5421ff).into(), - selection: rgba(0xdf54213d).into(), - }, - PlayerTheme { - cursor: rgba(0x6765e9ff).into(), - selection: rgba(0x6765e93d).into(), - }, - PlayerTheme { - cursor: rgba(0x3e96b8ff).into(), - selection: rgba(0x3e96b83d).into(), - }, - PlayerTheme { - cursor: rgba(0xf22e40ff).into(), - selection: rgba(0xf22e403d).into(), - }, - PlayerTheme { - cursor: rgba(0xc38419ff).into(), - selection: rgba(0xc384193d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_heath_dark.rs b/crates/theme2/src/themes/atelier_heath_dark.rs deleted file mode 100644 index 354c98069f..0000000000 --- a/crates/theme2/src/themes/atelier_heath_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_heath_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Heath Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x675b67ff).into(), - border_variant: rgba(0x675b67ff).into(), - border_focused: rgba(0x192961ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x433a43ff).into(), - surface: rgba(0x252025ff).into(), - background: rgba(0x433a43ff).into(), - filled_element: rgba(0x433a43ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0d1a43ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0d1a43ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf7f3f7ff).into(), - text_muted: rgba(0xa899a8ff).into(), - text_placeholder: rgba(0xca3f2bff).into(), - text_disabled: rgba(0x908190ff).into(), - text_accent: rgba(0x5169ebff).into(), - icon_muted: rgba(0xa899a8ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("preproc".into(), rgba(0xf7f3f7ff).into()), - ("number".into(), rgba(0xa65825ff).into()), - ("boolean".into(), rgba(0x918b3aff).into()), - ("embedded".into(), rgba(0xf7f3f7ff).into()), - ("variable.special".into(), rgba(0x7b58bfff).into()), - ("operator".into(), rgba(0xab9babff).into()), - ("punctuation.delimiter".into(), rgba(0xab9babff).into()), - ("primary".into(), rgba(0xd8cad8ff).into()), - ("punctuation.bracket".into(), rgba(0xab9babff).into()), - ("comment.doc".into(), rgba(0xab9babff).into()), - ("variant".into(), rgba(0xbb8a34ff).into()), - ("attribute".into(), rgba(0x5169ebff).into()), - ("property".into(), rgba(0xca3f2aff).into()), - ("keyword".into(), rgba(0x7b58bfff).into()), - ("hint".into(), rgba(0x8d70a8ff).into()), - ("string.special.symbol".into(), rgba(0x918b3aff).into()), - ("punctuation.special".into(), rgba(0xcc32ccff).into()), - ("link_uri".into(), rgba(0x918b3aff).into()), - ("link_text".into(), rgba(0xa65827ff).into()), - ("enum".into(), rgba(0xa65827ff).into()), - ("function".into(), rgba(0x506aecff).into()), - ( - "function.special.definition".into(), - rgba(0xbb8a34ff).into(), - ), - ("constant".into(), rgba(0x918b3aff).into()), - ("title".into(), rgba(0xf7f3f7ff).into()), - ("string.regex".into(), rgba(0x149393ff).into()), - ("variable".into(), rgba(0xd8cad8ff).into()), - ("comment".into(), rgba(0x776977ff).into()), - ("predictive".into(), rgba(0x75588fff).into()), - ("function.method".into(), rgba(0x506aecff).into()), - ("type".into(), rgba(0xbb8a34ff).into()), - ("punctuation".into(), rgba(0xd8cad8ff).into()), - ("emphasis".into(), rgba(0x5169ebff).into()), - ("emphasis.strong".into(), rgba(0x5169ebff).into()), - ("tag".into(), rgba(0x5169ebff).into()), - ("text.literal".into(), rgba(0xa65827ff).into()), - ("string".into(), rgba(0x918b3aff).into()), - ("string.escape".into(), rgba(0xab9babff).into()), - ("constructor".into(), rgba(0x5169ebff).into()), - ("label".into(), rgba(0x5169ebff).into()), - ("punctuation.list_marker".into(), rgba(0xd8cad8ff).into()), - ("string.special".into(), rgba(0xcc32ccff).into()), - ], - }, - status_bar: rgba(0x433a43ff).into(), - title_bar: rgba(0x433a43ff).into(), - toolbar: rgba(0x1b181bff).into(), - tab_bar: rgba(0x252025ff).into(), - editor: rgba(0x1b181bff).into(), - editor_subheader: rgba(0x252025ff).into(), - editor_active_line: rgba(0x252025ff).into(), - terminal: rgba(0x1b181bff).into(), - image_fallback_background: rgba(0x433a43ff).into(), - git_created: rgba(0x918b3aff).into(), - git_modified: rgba(0x5169ebff).into(), - git_deleted: rgba(0xca3f2bff).into(), - git_conflict: rgba(0xbb8a35ff).into(), - git_ignored: rgba(0x908190ff).into(), - git_renamed: rgba(0xbb8a35ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5169ebff).into(), - selection: rgba(0x5169eb3d).into(), - }, - PlayerTheme { - cursor: rgba(0x918b3aff).into(), - selection: rgba(0x918b3a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xcc34ccff).into(), - selection: rgba(0xcc34cc3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa65827ff).into(), - selection: rgba(0xa658273d).into(), - }, - PlayerTheme { - cursor: rgba(0x7b58bfff).into(), - selection: rgba(0x7b58bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0x189393ff).into(), - selection: rgba(0x1893933d).into(), - }, - PlayerTheme { - cursor: rgba(0xca3f2bff).into(), - selection: rgba(0xca3f2b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbb8a35ff).into(), - selection: rgba(0xbb8a353d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_heath_light.rs b/crates/theme2/src/themes/atelier_heath_light.rs deleted file mode 100644 index f1a9e4d8c6..0000000000 --- a/crates/theme2/src/themes/atelier_heath_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_heath_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Heath Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xad9dadff).into(), - border_variant: rgba(0xad9dadff).into(), - border_focused: rgba(0xcac7faff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc6b8c6ff).into(), - surface: rgba(0xe0d5e0ff).into(), - background: rgba(0xc6b8c6ff).into(), - filled_element: rgba(0xc6b8c6ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe2dffcff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe2dffcff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x1b181bff).into(), - text_muted: rgba(0x6b5e6bff).into(), - text_placeholder: rgba(0xca402bff).into(), - text_disabled: rgba(0x857785ff).into(), - text_accent: rgba(0x5169ebff).into(), - icon_muted: rgba(0x6b5e6bff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("enum".into(), rgba(0xa65927ff).into()), - ("string.escape".into(), rgba(0x695d69ff).into()), - ("link_uri".into(), rgba(0x918b3bff).into()), - ("function.method".into(), rgba(0x506aecff).into()), - ("comment.doc".into(), rgba(0x695d69ff).into()), - ("property".into(), rgba(0xca3f2aff).into()), - ("string.special".into(), rgba(0xcc32ccff).into()), - ("tag".into(), rgba(0x5169ebff).into()), - ("embedded".into(), rgba(0x1b181bff).into()), - ("primary".into(), rgba(0x292329ff).into()), - ("punctuation".into(), rgba(0x292329ff).into()), - ("punctuation.special".into(), rgba(0xcc32ccff).into()), - ("type".into(), rgba(0xbb8a34ff).into()), - ("number".into(), rgba(0xa65825ff).into()), - ("function".into(), rgba(0x506aecff).into()), - ("preproc".into(), rgba(0x1b181bff).into()), - ("punctuation.bracket".into(), rgba(0x695d69ff).into()), - ("punctuation.delimiter".into(), rgba(0x695d69ff).into()), - ("variable".into(), rgba(0x292329ff).into()), - ( - "function.special.definition".into(), - rgba(0xbb8a34ff).into(), - ), - ("label".into(), rgba(0x5169ebff).into()), - ("constructor".into(), rgba(0x5169ebff).into()), - ("emphasis.strong".into(), rgba(0x5169ebff).into()), - ("constant".into(), rgba(0x918b3bff).into()), - ("keyword".into(), rgba(0x7b58bfff).into()), - ("variable.special".into(), rgba(0x7b58bfff).into()), - ("variant".into(), rgba(0xbb8a34ff).into()), - ("title".into(), rgba(0x1b181bff).into()), - ("attribute".into(), rgba(0x5169ebff).into()), - ("comment".into(), rgba(0x9e8f9eff).into()), - ("string.special.symbol".into(), rgba(0x918b3aff).into()), - ("predictive".into(), rgba(0xa487bfff).into()), - ("link_text".into(), rgba(0xa65927ff).into()), - ("punctuation.list_marker".into(), rgba(0x292329ff).into()), - ("boolean".into(), rgba(0x918b3bff).into()), - ("text.literal".into(), rgba(0xa65927ff).into()), - ("emphasis".into(), rgba(0x5169ebff).into()), - ("string.regex".into(), rgba(0x149393ff).into()), - ("hint".into(), rgba(0x8c70a6ff).into()), - ("string".into(), rgba(0x918b3aff).into()), - ("operator".into(), rgba(0x695d69ff).into()), - ], - }, - status_bar: rgba(0xc6b8c6ff).into(), - title_bar: rgba(0xc6b8c6ff).into(), - toolbar: rgba(0xf7f3f7ff).into(), - tab_bar: rgba(0xe0d5e0ff).into(), - editor: rgba(0xf7f3f7ff).into(), - editor_subheader: rgba(0xe0d5e0ff).into(), - editor_active_line: rgba(0xe0d5e0ff).into(), - terminal: rgba(0xf7f3f7ff).into(), - image_fallback_background: rgba(0xc6b8c6ff).into(), - git_created: rgba(0x918b3bff).into(), - git_modified: rgba(0x5169ebff).into(), - git_deleted: rgba(0xca402bff).into(), - git_conflict: rgba(0xbb8a35ff).into(), - git_ignored: rgba(0x857785ff).into(), - git_renamed: rgba(0xbb8a35ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5169ebff).into(), - selection: rgba(0x5169eb3d).into(), - }, - PlayerTheme { - cursor: rgba(0x918b3bff).into(), - selection: rgba(0x918b3b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xcc34ccff).into(), - selection: rgba(0xcc34cc3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa65927ff).into(), - selection: rgba(0xa659273d).into(), - }, - PlayerTheme { - cursor: rgba(0x7a5ac0ff).into(), - selection: rgba(0x7a5ac03d).into(), - }, - PlayerTheme { - cursor: rgba(0x189393ff).into(), - selection: rgba(0x1893933d).into(), - }, - PlayerTheme { - cursor: rgba(0xca402bff).into(), - selection: rgba(0xca402b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbb8a35ff).into(), - selection: rgba(0xbb8a353d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_lakeside_dark.rs b/crates/theme2/src/themes/atelier_lakeside_dark.rs deleted file mode 100644 index 61b78864b7..0000000000 --- a/crates/theme2/src/themes/atelier_lakeside_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_lakeside_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Lakeside Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x4f6a78ff).into(), - border_variant: rgba(0x4f6a78ff).into(), - border_focused: rgba(0x1a2f3cff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x33444dff).into(), - surface: rgba(0x1c2529ff).into(), - background: rgba(0x33444dff).into(), - filled_element: rgba(0x33444dff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x121c24ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x121c24ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xebf8ffff).into(), - text_muted: rgba(0x7c9fb3ff).into(), - text_placeholder: rgba(0xd22e72ff).into(), - text_disabled: rgba(0x688c9dff).into(), - text_accent: rgba(0x267eadff).into(), - icon_muted: rgba(0x7c9fb3ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.bracket".into(), rgba(0x7ea2b4ff).into()), - ("punctuation.special".into(), rgba(0xb72cd2ff).into()), - ("property".into(), rgba(0xd22c72ff).into()), - ("function.method".into(), rgba(0x247eadff).into()), - ("comment".into(), rgba(0x5a7b8cff).into()), - ("constructor".into(), rgba(0x267eadff).into()), - ("boolean".into(), rgba(0x558c3aff).into()), - ("hint".into(), rgba(0x52809aff).into()), - ("label".into(), rgba(0x267eadff).into()), - ("string.special".into(), rgba(0xb72cd2ff).into()), - ("title".into(), rgba(0xebf8ffff).into()), - ("punctuation.list_marker".into(), rgba(0xc1e4f6ff).into()), - ("emphasis.strong".into(), rgba(0x267eadff).into()), - ("enum".into(), rgba(0x935b25ff).into()), - ("type".into(), rgba(0x8a8a0eff).into()), - ("tag".into(), rgba(0x267eadff).into()), - ("punctuation.delimiter".into(), rgba(0x7ea2b4ff).into()), - ("primary".into(), rgba(0xc1e4f6ff).into()), - ("link_text".into(), rgba(0x935b25ff).into()), - ("variable".into(), rgba(0xc1e4f6ff).into()), - ("variable.special".into(), rgba(0x6a6ab7ff).into()), - ("string.special.symbol".into(), rgba(0x558c3aff).into()), - ("link_uri".into(), rgba(0x558c3aff).into()), - ("function".into(), rgba(0x247eadff).into()), - ("predictive".into(), rgba(0x426f88ff).into()), - ("punctuation".into(), rgba(0xc1e4f6ff).into()), - ("string.escape".into(), rgba(0x7ea2b4ff).into()), - ("keyword".into(), rgba(0x6a6ab7ff).into()), - ("attribute".into(), rgba(0x267eadff).into()), - ("string.regex".into(), rgba(0x2c8f6eff).into()), - ("embedded".into(), rgba(0xebf8ffff).into()), - ("emphasis".into(), rgba(0x267eadff).into()), - ("string".into(), rgba(0x558c3aff).into()), - ("operator".into(), rgba(0x7ea2b4ff).into()), - ("text.literal".into(), rgba(0x935b25ff).into()), - ("constant".into(), rgba(0x558c3aff).into()), - ("comment.doc".into(), rgba(0x7ea2b4ff).into()), - ("number".into(), rgba(0x935c24ff).into()), - ("preproc".into(), rgba(0xebf8ffff).into()), - ( - "function.special.definition".into(), - rgba(0x8a8a0eff).into(), - ), - ("variant".into(), rgba(0x8a8a0eff).into()), - ], - }, - status_bar: rgba(0x33444dff).into(), - title_bar: rgba(0x33444dff).into(), - toolbar: rgba(0x161b1dff).into(), - tab_bar: rgba(0x1c2529ff).into(), - editor: rgba(0x161b1dff).into(), - editor_subheader: rgba(0x1c2529ff).into(), - editor_active_line: rgba(0x1c2529ff).into(), - terminal: rgba(0x161b1dff).into(), - image_fallback_background: rgba(0x33444dff).into(), - git_created: rgba(0x558c3aff).into(), - git_modified: rgba(0x267eadff).into(), - git_deleted: rgba(0xd22e72ff).into(), - git_conflict: rgba(0x8a8a10ff).into(), - git_ignored: rgba(0x688c9dff).into(), - git_renamed: rgba(0x8a8a10ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x267eadff).into(), - selection: rgba(0x267ead3d).into(), - }, - PlayerTheme { - cursor: rgba(0x558c3aff).into(), - selection: rgba(0x558c3a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xb72ed2ff).into(), - selection: rgba(0xb72ed23d).into(), - }, - PlayerTheme { - cursor: rgba(0x935b25ff).into(), - selection: rgba(0x935b253d).into(), - }, - PlayerTheme { - cursor: rgba(0x6a6ab7ff).into(), - selection: rgba(0x6a6ab73d).into(), - }, - PlayerTheme { - cursor: rgba(0x2d8f6fff).into(), - selection: rgba(0x2d8f6f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd22e72ff).into(), - selection: rgba(0xd22e723d).into(), - }, - PlayerTheme { - cursor: rgba(0x8a8a10ff).into(), - selection: rgba(0x8a8a103d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_lakeside_light.rs b/crates/theme2/src/themes/atelier_lakeside_light.rs deleted file mode 100644 index 64fb70dadb..0000000000 --- a/crates/theme2/src/themes/atelier_lakeside_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_lakeside_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Lakeside Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x80a4b6ff).into(), - border_variant: rgba(0x80a4b6ff).into(), - border_focused: rgba(0xb9cee0ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xa6cadcff).into(), - surface: rgba(0xcdeaf9ff).into(), - background: rgba(0xa6cadcff).into(), - filled_element: rgba(0xa6cadcff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd8e4eeff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd8e4eeff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x161b1dff).into(), - text_muted: rgba(0x526f7dff).into(), - text_placeholder: rgba(0xd22e71ff).into(), - text_disabled: rgba(0x628496ff).into(), - text_accent: rgba(0x267eadff).into(), - icon_muted: rgba(0x526f7dff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("emphasis".into(), rgba(0x267eadff).into()), - ("number".into(), rgba(0x935c24ff).into()), - ("embedded".into(), rgba(0x161b1dff).into()), - ("link_text".into(), rgba(0x935c25ff).into()), - ("string".into(), rgba(0x558c3aff).into()), - ("constructor".into(), rgba(0x267eadff).into()), - ("punctuation.list_marker".into(), rgba(0x1f292eff).into()), - ("string.special".into(), rgba(0xb72cd2ff).into()), - ("title".into(), rgba(0x161b1dff).into()), - ("variant".into(), rgba(0x8a8a0eff).into()), - ("tag".into(), rgba(0x267eadff).into()), - ("attribute".into(), rgba(0x267eadff).into()), - ("keyword".into(), rgba(0x6a6ab7ff).into()), - ("enum".into(), rgba(0x935c25ff).into()), - ("function".into(), rgba(0x247eadff).into()), - ("string.escape".into(), rgba(0x516d7bff).into()), - ("operator".into(), rgba(0x516d7bff).into()), - ("function.method".into(), rgba(0x247eadff).into()), - ( - "function.special.definition".into(), - rgba(0x8a8a0eff).into(), - ), - ("punctuation.delimiter".into(), rgba(0x516d7bff).into()), - ("comment".into(), rgba(0x7094a7ff).into()), - ("primary".into(), rgba(0x1f292eff).into()), - ("punctuation.bracket".into(), rgba(0x516d7bff).into()), - ("variable".into(), rgba(0x1f292eff).into()), - ("emphasis.strong".into(), rgba(0x267eadff).into()), - ("predictive".into(), rgba(0x6a97b2ff).into()), - ("punctuation.special".into(), rgba(0xb72cd2ff).into()), - ("hint".into(), rgba(0x5a87a0ff).into()), - ("text.literal".into(), rgba(0x935c25ff).into()), - ("string.special.symbol".into(), rgba(0x558c3aff).into()), - ("comment.doc".into(), rgba(0x516d7bff).into()), - ("constant".into(), rgba(0x568c3bff).into()), - ("boolean".into(), rgba(0x568c3bff).into()), - ("preproc".into(), rgba(0x161b1dff).into()), - ("variable.special".into(), rgba(0x6a6ab7ff).into()), - ("link_uri".into(), rgba(0x568c3bff).into()), - ("string.regex".into(), rgba(0x2c8f6eff).into()), - ("punctuation".into(), rgba(0x1f292eff).into()), - ("property".into(), rgba(0xd22c72ff).into()), - ("label".into(), rgba(0x267eadff).into()), - ("type".into(), rgba(0x8a8a0eff).into()), - ], - }, - status_bar: rgba(0xa6cadcff).into(), - title_bar: rgba(0xa6cadcff).into(), - toolbar: rgba(0xebf8ffff).into(), - tab_bar: rgba(0xcdeaf9ff).into(), - editor: rgba(0xebf8ffff).into(), - editor_subheader: rgba(0xcdeaf9ff).into(), - editor_active_line: rgba(0xcdeaf9ff).into(), - terminal: rgba(0xebf8ffff).into(), - image_fallback_background: rgba(0xa6cadcff).into(), - git_created: rgba(0x568c3bff).into(), - git_modified: rgba(0x267eadff).into(), - git_deleted: rgba(0xd22e71ff).into(), - git_conflict: rgba(0x8a8a10ff).into(), - git_ignored: rgba(0x628496ff).into(), - git_renamed: rgba(0x8a8a10ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x267eadff).into(), - selection: rgba(0x267ead3d).into(), - }, - PlayerTheme { - cursor: rgba(0x568c3bff).into(), - selection: rgba(0x568c3b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xb72ed2ff).into(), - selection: rgba(0xb72ed23d).into(), - }, - PlayerTheme { - cursor: rgba(0x935c25ff).into(), - selection: rgba(0x935c253d).into(), - }, - PlayerTheme { - cursor: rgba(0x6c6ab7ff).into(), - selection: rgba(0x6c6ab73d).into(), - }, - PlayerTheme { - cursor: rgba(0x2e8f6eff).into(), - selection: rgba(0x2e8f6e3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd22e71ff).into(), - selection: rgba(0xd22e713d).into(), - }, - PlayerTheme { - cursor: rgba(0x8a8a10ff).into(), - selection: rgba(0x8a8a103d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_plateau_dark.rs b/crates/theme2/src/themes/atelier_plateau_dark.rs deleted file mode 100644 index 0ba5a1659d..0000000000 --- a/crates/theme2/src/themes/atelier_plateau_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_plateau_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Plateau Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x564e4eff).into(), - border_variant: rgba(0x564e4eff).into(), - border_focused: rgba(0x2c2b45ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3b3535ff).into(), - surface: rgba(0x252020ff).into(), - background: rgba(0x3b3535ff).into(), - filled_element: rgba(0x3b3535ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1c1b29ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1c1b29ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf4ececff).into(), - text_muted: rgba(0x898383ff).into(), - text_placeholder: rgba(0xca4848ff).into(), - text_disabled: rgba(0x756e6eff).into(), - text_accent: rgba(0x7272caff).into(), - icon_muted: rgba(0x898383ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("variant".into(), rgba(0xa06d3aff).into()), - ("label".into(), rgba(0x7272caff).into()), - ("punctuation.delimiter".into(), rgba(0x8a8585ff).into()), - ("string.regex".into(), rgba(0x5485b6ff).into()), - ("variable.special".into(), rgba(0x8464c4ff).into()), - ("string".into(), rgba(0x4b8b8bff).into()), - ("property".into(), rgba(0xca4848ff).into()), - ("hint".into(), rgba(0x8a647aff).into()), - ("comment.doc".into(), rgba(0x8a8585ff).into()), - ("attribute".into(), rgba(0x7272caff).into()), - ("tag".into(), rgba(0x7272caff).into()), - ("constructor".into(), rgba(0x7272caff).into()), - ("boolean".into(), rgba(0x4b8b8bff).into()), - ("preproc".into(), rgba(0xf4ececff).into()), - ("constant".into(), rgba(0x4b8b8bff).into()), - ("punctuation.special".into(), rgba(0xbd5187ff).into()), - ("function.method".into(), rgba(0x7272caff).into()), - ("comment".into(), rgba(0x655d5dff).into()), - ("variable".into(), rgba(0xe7dfdfff).into()), - ("primary".into(), rgba(0xe7dfdfff).into()), - ("title".into(), rgba(0xf4ececff).into()), - ("emphasis".into(), rgba(0x7272caff).into()), - ("emphasis.strong".into(), rgba(0x7272caff).into()), - ("function".into(), rgba(0x7272caff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("operator".into(), rgba(0x8a8585ff).into()), - ("embedded".into(), rgba(0xf4ececff).into()), - ("predictive".into(), rgba(0x795369ff).into()), - ("punctuation".into(), rgba(0xe7dfdfff).into()), - ("link_text".into(), rgba(0xb4593bff).into()), - ("enum".into(), rgba(0xb4593bff).into()), - ("string.special".into(), rgba(0xbd5187ff).into()), - ("text.literal".into(), rgba(0xb4593bff).into()), - ("string.escape".into(), rgba(0x8a8585ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("keyword".into(), rgba(0x8464c4ff).into()), - ("link_uri".into(), rgba(0x4b8b8bff).into()), - ("number".into(), rgba(0xb4593bff).into()), - ("punctuation.bracket".into(), rgba(0x8a8585ff).into()), - ("string.special.symbol".into(), rgba(0x4b8b8bff).into()), - ("punctuation.list_marker".into(), rgba(0xe7dfdfff).into()), - ], - }, - status_bar: rgba(0x3b3535ff).into(), - title_bar: rgba(0x3b3535ff).into(), - toolbar: rgba(0x1b1818ff).into(), - tab_bar: rgba(0x252020ff).into(), - editor: rgba(0x1b1818ff).into(), - editor_subheader: rgba(0x252020ff).into(), - editor_active_line: rgba(0x252020ff).into(), - terminal: rgba(0x1b1818ff).into(), - image_fallback_background: rgba(0x3b3535ff).into(), - git_created: rgba(0x4b8b8bff).into(), - git_modified: rgba(0x7272caff).into(), - git_deleted: rgba(0xca4848ff).into(), - git_conflict: rgba(0xa06d3aff).into(), - git_ignored: rgba(0x756e6eff).into(), - git_renamed: rgba(0xa06d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x7272caff).into(), - selection: rgba(0x7272ca3d).into(), - }, - PlayerTheme { - cursor: rgba(0x4b8b8bff).into(), - selection: rgba(0x4b8b8b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbd5187ff).into(), - selection: rgba(0xbd51873d).into(), - }, - PlayerTheme { - cursor: rgba(0xb4593bff).into(), - selection: rgba(0xb4593b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8464c4ff).into(), - selection: rgba(0x8464c43d).into(), - }, - PlayerTheme { - cursor: rgba(0x5485b6ff).into(), - selection: rgba(0x5485b63d).into(), - }, - PlayerTheme { - cursor: rgba(0xca4848ff).into(), - selection: rgba(0xca48483d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06d3aff).into(), - selection: rgba(0xa06d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_plateau_light.rs b/crates/theme2/src/themes/atelier_plateau_light.rs deleted file mode 100644 index 68f100dd85..0000000000 --- a/crates/theme2/src/themes/atelier_plateau_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_plateau_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Plateau Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8e8989ff).into(), - border_variant: rgba(0x8e8989ff).into(), - border_focused: rgba(0xcecaecff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc1bbbbff).into(), - surface: rgba(0xebe3e3ff).into(), - background: rgba(0xc1bbbbff).into(), - filled_element: rgba(0xc1bbbbff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe4e1f5ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe4e1f5ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x1b1818ff).into(), - text_muted: rgba(0x5a5252ff).into(), - text_placeholder: rgba(0xca4a4aff).into(), - text_disabled: rgba(0x6e6666ff).into(), - text_accent: rgba(0x7272caff).into(), - icon_muted: rgba(0x5a5252ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("text.literal".into(), rgba(0xb45a3cff).into()), - ("punctuation.special".into(), rgba(0xbd5187ff).into()), - ("variant".into(), rgba(0xa06d3aff).into()), - ("punctuation".into(), rgba(0x292424ff).into()), - ("string.escape".into(), rgba(0x585050ff).into()), - ("emphasis".into(), rgba(0x7272caff).into()), - ("title".into(), rgba(0x1b1818ff).into()), - ("constructor".into(), rgba(0x7272caff).into()), - ("variable".into(), rgba(0x292424ff).into()), - ("predictive".into(), rgba(0xa27a91ff).into()), - ("label".into(), rgba(0x7272caff).into()), - ("function.method".into(), rgba(0x7272caff).into()), - ("link_uri".into(), rgba(0x4c8b8bff).into()), - ("punctuation.delimiter".into(), rgba(0x585050ff).into()), - ("link_text".into(), rgba(0xb45a3cff).into()), - ("hint".into(), rgba(0x91697fff).into()), - ("emphasis.strong".into(), rgba(0x7272caff).into()), - ("attribute".into(), rgba(0x7272caff).into()), - ("boolean".into(), rgba(0x4c8b8bff).into()), - ("string.special.symbol".into(), rgba(0x4b8b8bff).into()), - ("string".into(), rgba(0x4b8b8bff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("string.regex".into(), rgba(0x5485b6ff).into()), - ("comment.doc".into(), rgba(0x585050ff).into()), - ("string.special".into(), rgba(0xbd5187ff).into()), - ("property".into(), rgba(0xca4848ff).into()), - ("preproc".into(), rgba(0x1b1818ff).into()), - ("embedded".into(), rgba(0x1b1818ff).into()), - ("comment".into(), rgba(0x7e7777ff).into()), - ("primary".into(), rgba(0x292424ff).into()), - ("number".into(), rgba(0xb4593bff).into()), - ("function".into(), rgba(0x7272caff).into()), - ("punctuation.bracket".into(), rgba(0x585050ff).into()), - ("tag".into(), rgba(0x7272caff).into()), - ("punctuation.list_marker".into(), rgba(0x292424ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("enum".into(), rgba(0xb45a3cff).into()), - ("keyword".into(), rgba(0x8464c4ff).into()), - ("operator".into(), rgba(0x585050ff).into()), - ("variable.special".into(), rgba(0x8464c4ff).into()), - ("constant".into(), rgba(0x4c8b8bff).into()), - ], - }, - status_bar: rgba(0xc1bbbbff).into(), - title_bar: rgba(0xc1bbbbff).into(), - toolbar: rgba(0xf4ececff).into(), - tab_bar: rgba(0xebe3e3ff).into(), - editor: rgba(0xf4ececff).into(), - editor_subheader: rgba(0xebe3e3ff).into(), - editor_active_line: rgba(0xebe3e3ff).into(), - terminal: rgba(0xf4ececff).into(), - image_fallback_background: rgba(0xc1bbbbff).into(), - git_created: rgba(0x4c8b8bff).into(), - git_modified: rgba(0x7272caff).into(), - git_deleted: rgba(0xca4a4aff).into(), - git_conflict: rgba(0xa06e3bff).into(), - git_ignored: rgba(0x6e6666ff).into(), - git_renamed: rgba(0xa06e3bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x7272caff).into(), - selection: rgba(0x7272ca3d).into(), - }, - PlayerTheme { - cursor: rgba(0x4c8b8bff).into(), - selection: rgba(0x4c8b8b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbd5186ff).into(), - selection: rgba(0xbd51863d).into(), - }, - PlayerTheme { - cursor: rgba(0xb45a3cff).into(), - selection: rgba(0xb45a3c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8464c4ff).into(), - selection: rgba(0x8464c43d).into(), - }, - PlayerTheme { - cursor: rgba(0x5485b5ff).into(), - selection: rgba(0x5485b53d).into(), - }, - PlayerTheme { - cursor: rgba(0xca4a4aff).into(), - selection: rgba(0xca4a4a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06e3bff).into(), - selection: rgba(0xa06e3b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_savanna_dark.rs b/crates/theme2/src/themes/atelier_savanna_dark.rs deleted file mode 100644 index d4040db958..0000000000 --- a/crates/theme2/src/themes/atelier_savanna_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_savanna_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Savanna Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x505e55ff).into(), - border_variant: rgba(0x505e55ff).into(), - border_focused: rgba(0x1f3233ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x353f39ff).into(), - surface: rgba(0x1f2621ff).into(), - background: rgba(0x353f39ff).into(), - filled_element: rgba(0x353f39ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x151e20ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x151e20ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xecf4eeff).into(), - text_muted: rgba(0x859188ff).into(), - text_placeholder: rgba(0xb16038ff).into(), - text_disabled: rgba(0x6f7e74ff).into(), - text_accent: rgba(0x468b8fff).into(), - icon_muted: rgba(0x859188ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("function.method".into(), rgba(0x468b8fff).into()), - ("title".into(), rgba(0xecf4eeff).into()), - ("label".into(), rgba(0x468b8fff).into()), - ("text.literal".into(), rgba(0x9f703bff).into()), - ("boolean".into(), rgba(0x479962ff).into()), - ("punctuation.list_marker".into(), rgba(0xdfe7e2ff).into()), - ("string.escape".into(), rgba(0x87928aff).into()), - ("string.special".into(), rgba(0x857368ff).into()), - ("punctuation.delimiter".into(), rgba(0x87928aff).into()), - ("tag".into(), rgba(0x468b8fff).into()), - ("property".into(), rgba(0xb16038ff).into()), - ("preproc".into(), rgba(0xecf4eeff).into()), - ("primary".into(), rgba(0xdfe7e2ff).into()), - ("link_uri".into(), rgba(0x479962ff).into()), - ("comment".into(), rgba(0x5f6d64ff).into()), - ("type".into(), rgba(0xa07d3aff).into()), - ("hint".into(), rgba(0x607e76ff).into()), - ("punctuation".into(), rgba(0xdfe7e2ff).into()), - ("string.special.symbol".into(), rgba(0x479962ff).into()), - ("emphasis.strong".into(), rgba(0x468b8fff).into()), - ("keyword".into(), rgba(0x55859bff).into()), - ("comment.doc".into(), rgba(0x87928aff).into()), - ("punctuation.bracket".into(), rgba(0x87928aff).into()), - ("constant".into(), rgba(0x479962ff).into()), - ("link_text".into(), rgba(0x9f703bff).into()), - ("number".into(), rgba(0x9f703bff).into()), - ("function".into(), rgba(0x468b8fff).into()), - ("variable".into(), rgba(0xdfe7e2ff).into()), - ("emphasis".into(), rgba(0x468b8fff).into()), - ("punctuation.special".into(), rgba(0x857368ff).into()), - ("constructor".into(), rgba(0x468b8fff).into()), - ("variable.special".into(), rgba(0x55859bff).into()), - ("operator".into(), rgba(0x87928aff).into()), - ("enum".into(), rgba(0x9f703bff).into()), - ("string.regex".into(), rgba(0x1b9aa0ff).into()), - ("attribute".into(), rgba(0x468b8fff).into()), - ("predictive".into(), rgba(0x506d66ff).into()), - ("string".into(), rgba(0x479962ff).into()), - ("embedded".into(), rgba(0xecf4eeff).into()), - ("variant".into(), rgba(0xa07d3aff).into()), - ( - "function.special.definition".into(), - rgba(0xa07d3aff).into(), - ), - ], - }, - status_bar: rgba(0x353f39ff).into(), - title_bar: rgba(0x353f39ff).into(), - toolbar: rgba(0x171c19ff).into(), - tab_bar: rgba(0x1f2621ff).into(), - editor: rgba(0x171c19ff).into(), - editor_subheader: rgba(0x1f2621ff).into(), - editor_active_line: rgba(0x1f2621ff).into(), - terminal: rgba(0x171c19ff).into(), - image_fallback_background: rgba(0x353f39ff).into(), - git_created: rgba(0x479962ff).into(), - git_modified: rgba(0x468b8fff).into(), - git_deleted: rgba(0xb16038ff).into(), - git_conflict: rgba(0xa07d3aff).into(), - git_ignored: rgba(0x6f7e74ff).into(), - git_renamed: rgba(0xa07d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x468b8fff).into(), - selection: rgba(0x468b8f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x479962ff).into(), - selection: rgba(0x4799623d).into(), - }, - PlayerTheme { - cursor: rgba(0x857368ff).into(), - selection: rgba(0x8573683d).into(), - }, - PlayerTheme { - cursor: rgba(0x9f703bff).into(), - selection: rgba(0x9f703b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x55859bff).into(), - selection: rgba(0x55859b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1d9aa0ff).into(), - selection: rgba(0x1d9aa03d).into(), - }, - PlayerTheme { - cursor: rgba(0xb16038ff).into(), - selection: rgba(0xb160383d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3aff).into(), - selection: rgba(0xa07d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_savanna_light.rs b/crates/theme2/src/themes/atelier_savanna_light.rs deleted file mode 100644 index 08722cd91c..0000000000 --- a/crates/theme2/src/themes/atelier_savanna_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_savanna_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Savanna Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8b968eff).into(), - border_variant: rgba(0x8b968eff).into(), - border_focused: rgba(0xbed4d6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xbcc5bfff).into(), - surface: rgba(0xe3ebe6ff).into(), - background: rgba(0xbcc5bfff).into(), - filled_element: rgba(0xbcc5bfff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdae7e8ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdae7e8ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x171c19ff).into(), - text_muted: rgba(0x546259ff).into(), - text_placeholder: rgba(0xb16139ff).into(), - text_disabled: rgba(0x68766dff).into(), - text_accent: rgba(0x488b90ff).into(), - icon_muted: rgba(0x546259ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("text.literal".into(), rgba(0x9f713cff).into()), - ("string".into(), rgba(0x479962ff).into()), - ("punctuation.special".into(), rgba(0x857368ff).into()), - ("type".into(), rgba(0xa07d3aff).into()), - ("enum".into(), rgba(0x9f713cff).into()), - ("title".into(), rgba(0x171c19ff).into()), - ("comment".into(), rgba(0x77877cff).into()), - ("predictive".into(), rgba(0x75958bff).into()), - ("punctuation.list_marker".into(), rgba(0x232a25ff).into()), - ("string.special.symbol".into(), rgba(0x479962ff).into()), - ("constructor".into(), rgba(0x488b90ff).into()), - ("variable".into(), rgba(0x232a25ff).into()), - ("label".into(), rgba(0x488b90ff).into()), - ("attribute".into(), rgba(0x488b90ff).into()), - ("constant".into(), rgba(0x499963ff).into()), - ("function".into(), rgba(0x468b8fff).into()), - ("variable.special".into(), rgba(0x55859bff).into()), - ("keyword".into(), rgba(0x55859bff).into()), - ("number".into(), rgba(0x9f703bff).into()), - ("boolean".into(), rgba(0x499963ff).into()), - ("embedded".into(), rgba(0x171c19ff).into()), - ("string.special".into(), rgba(0x857368ff).into()), - ("emphasis.strong".into(), rgba(0x488b90ff).into()), - ("string.regex".into(), rgba(0x1b9aa0ff).into()), - ("hint".into(), rgba(0x66847cff).into()), - ("preproc".into(), rgba(0x171c19ff).into()), - ("link_uri".into(), rgba(0x499963ff).into()), - ("variant".into(), rgba(0xa07d3aff).into()), - ("function.method".into(), rgba(0x468b8fff).into()), - ("punctuation.bracket".into(), rgba(0x526057ff).into()), - ("punctuation.delimiter".into(), rgba(0x526057ff).into()), - ("punctuation".into(), rgba(0x232a25ff).into()), - ("primary".into(), rgba(0x232a25ff).into()), - ("string.escape".into(), rgba(0x526057ff).into()), - ("property".into(), rgba(0xb16038ff).into()), - ("operator".into(), rgba(0x526057ff).into()), - ("comment.doc".into(), rgba(0x526057ff).into()), - ( - "function.special.definition".into(), - rgba(0xa07d3aff).into(), - ), - ("link_text".into(), rgba(0x9f713cff).into()), - ("tag".into(), rgba(0x488b90ff).into()), - ("emphasis".into(), rgba(0x488b90ff).into()), - ], - }, - status_bar: rgba(0xbcc5bfff).into(), - title_bar: rgba(0xbcc5bfff).into(), - toolbar: rgba(0xecf4eeff).into(), - tab_bar: rgba(0xe3ebe6ff).into(), - editor: rgba(0xecf4eeff).into(), - editor_subheader: rgba(0xe3ebe6ff).into(), - editor_active_line: rgba(0xe3ebe6ff).into(), - terminal: rgba(0xecf4eeff).into(), - image_fallback_background: rgba(0xbcc5bfff).into(), - git_created: rgba(0x499963ff).into(), - git_modified: rgba(0x488b90ff).into(), - git_deleted: rgba(0xb16139ff).into(), - git_conflict: rgba(0xa07d3bff).into(), - git_ignored: rgba(0x68766dff).into(), - git_renamed: rgba(0xa07d3bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x488b90ff).into(), - selection: rgba(0x488b903d).into(), - }, - PlayerTheme { - cursor: rgba(0x499963ff).into(), - selection: rgba(0x4999633d).into(), - }, - PlayerTheme { - cursor: rgba(0x857368ff).into(), - selection: rgba(0x8573683d).into(), - }, - PlayerTheme { - cursor: rgba(0x9f713cff).into(), - selection: rgba(0x9f713c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x55859bff).into(), - selection: rgba(0x55859b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1e9aa0ff).into(), - selection: rgba(0x1e9aa03d).into(), - }, - PlayerTheme { - cursor: rgba(0xb16139ff).into(), - selection: rgba(0xb161393d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3bff).into(), - selection: rgba(0xa07d3b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_seaside_dark.rs b/crates/theme2/src/themes/atelier_seaside_dark.rs deleted file mode 100644 index 475115e0d1..0000000000 --- a/crates/theme2/src/themes/atelier_seaside_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_seaside_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Seaside Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5c6c5cff).into(), - border_variant: rgba(0x5c6c5cff).into(), - border_focused: rgba(0x102667ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3b453bff).into(), - surface: rgba(0x1f231fff).into(), - background: rgba(0x3b453bff).into(), - filled_element: rgba(0x3b453bff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x051949ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x051949ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf3faf3ff).into(), - text_muted: rgba(0x8ba48bff).into(), - text_placeholder: rgba(0xe61c3bff).into(), - text_disabled: rgba(0x778f77ff).into(), - text_accent: rgba(0x3e62f4ff).into(), - icon_muted: rgba(0x8ba48bff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("comment".into(), rgba(0x687d68ff).into()), - ("predictive".into(), rgba(0x00788bff).into()), - ("string.special".into(), rgba(0xe618c3ff).into()), - ("string.regex".into(), rgba(0x1899b3ff).into()), - ("boolean".into(), rgba(0x2aa329ff).into()), - ("string".into(), rgba(0x28a328ff).into()), - ("operator".into(), rgba(0x8ca68cff).into()), - ("primary".into(), rgba(0xcfe8cfff).into()), - ("number".into(), rgba(0x87711cff).into()), - ("punctuation.special".into(), rgba(0xe618c3ff).into()), - ("link_text".into(), rgba(0x87711dff).into()), - ("title".into(), rgba(0xf3faf3ff).into()), - ("comment.doc".into(), rgba(0x8ca68cff).into()), - ("label".into(), rgba(0x3e62f4ff).into()), - ("preproc".into(), rgba(0xf3faf3ff).into()), - ("punctuation.bracket".into(), rgba(0x8ca68cff).into()), - ("punctuation.delimiter".into(), rgba(0x8ca68cff).into()), - ("function.method".into(), rgba(0x3d62f5ff).into()), - ("tag".into(), rgba(0x3e62f4ff).into()), - ("embedded".into(), rgba(0xf3faf3ff).into()), - ("text.literal".into(), rgba(0x87711dff).into()), - ("punctuation".into(), rgba(0xcfe8cfff).into()), - ("string.special.symbol".into(), rgba(0x28a328ff).into()), - ("link_uri".into(), rgba(0x2aa329ff).into()), - ("keyword".into(), rgba(0xac2aeeff).into()), - ("function".into(), rgba(0x3d62f5ff).into()), - ("string.escape".into(), rgba(0x8ca68cff).into()), - ("variant".into(), rgba(0x98981bff).into()), - ( - "function.special.definition".into(), - rgba(0x98981bff).into(), - ), - ("constructor".into(), rgba(0x3e62f4ff).into()), - ("constant".into(), rgba(0x2aa329ff).into()), - ("hint".into(), rgba(0x008b9fff).into()), - ("type".into(), rgba(0x98981bff).into()), - ("emphasis".into(), rgba(0x3e62f4ff).into()), - ("variable".into(), rgba(0xcfe8cfff).into()), - ("emphasis.strong".into(), rgba(0x3e62f4ff).into()), - ("attribute".into(), rgba(0x3e62f4ff).into()), - ("enum".into(), rgba(0x87711dff).into()), - ("property".into(), rgba(0xe6183bff).into()), - ("punctuation.list_marker".into(), rgba(0xcfe8cfff).into()), - ("variable.special".into(), rgba(0xac2aeeff).into()), - ], - }, - status_bar: rgba(0x3b453bff).into(), - title_bar: rgba(0x3b453bff).into(), - toolbar: rgba(0x131513ff).into(), - tab_bar: rgba(0x1f231fff).into(), - editor: rgba(0x131513ff).into(), - editor_subheader: rgba(0x1f231fff).into(), - editor_active_line: rgba(0x1f231fff).into(), - terminal: rgba(0x131513ff).into(), - image_fallback_background: rgba(0x3b453bff).into(), - git_created: rgba(0x2aa329ff).into(), - git_modified: rgba(0x3e62f4ff).into(), - git_deleted: rgba(0xe61c3bff).into(), - git_conflict: rgba(0x98981bff).into(), - git_ignored: rgba(0x778f77ff).into(), - git_renamed: rgba(0x98981bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e62f4ff).into(), - selection: rgba(0x3e62f43d).into(), - }, - PlayerTheme { - cursor: rgba(0x2aa329ff).into(), - selection: rgba(0x2aa3293d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61cc3ff).into(), - selection: rgba(0xe61cc33d).into(), - }, - PlayerTheme { - cursor: rgba(0x87711dff).into(), - selection: rgba(0x87711d3d).into(), - }, - PlayerTheme { - cursor: rgba(0xac2dedff).into(), - selection: rgba(0xac2ded3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1b99b3ff).into(), - selection: rgba(0x1b99b33d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61c3bff).into(), - selection: rgba(0xe61c3b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x98981bff).into(), - selection: rgba(0x98981b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_seaside_light.rs b/crates/theme2/src/themes/atelier_seaside_light.rs deleted file mode 100644 index 557134b540..0000000000 --- a/crates/theme2/src/themes/atelier_seaside_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_seaside_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Seaside Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8ea88eff).into(), - border_variant: rgba(0x8ea88eff).into(), - border_focused: rgba(0xc9c4fdff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xb4ceb4ff).into(), - surface: rgba(0xdaeedaff).into(), - background: rgba(0xb4ceb4ff).into(), - filled_element: rgba(0xb4ceb4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe1ddfeff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe1ddfeff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x131513ff).into(), - text_muted: rgba(0x5f705fff).into(), - text_placeholder: rgba(0xe61c3dff).into(), - text_disabled: rgba(0x718771ff).into(), - text_accent: rgba(0x3e61f4ff).into(), - icon_muted: rgba(0x5f705fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.escape".into(), rgba(0x5e6e5eff).into()), - ("boolean".into(), rgba(0x2aa32aff).into()), - ("string.special".into(), rgba(0xe618c3ff).into()), - ("comment".into(), rgba(0x809980ff).into()), - ("number".into(), rgba(0x87711cff).into()), - ("comment.doc".into(), rgba(0x5e6e5eff).into()), - ("tag".into(), rgba(0x3e61f4ff).into()), - ("string.special.symbol".into(), rgba(0x28a328ff).into()), - ("primary".into(), rgba(0x242924ff).into()), - ("string".into(), rgba(0x28a328ff).into()), - ("enum".into(), rgba(0x87711fff).into()), - ("operator".into(), rgba(0x5e6e5eff).into()), - ("string.regex".into(), rgba(0x1899b3ff).into()), - ("keyword".into(), rgba(0xac2aeeff).into()), - ("emphasis".into(), rgba(0x3e61f4ff).into()), - ("link_uri".into(), rgba(0x2aa32aff).into()), - ("constant".into(), rgba(0x2aa32aff).into()), - ("constructor".into(), rgba(0x3e61f4ff).into()), - ("link_text".into(), rgba(0x87711fff).into()), - ("emphasis.strong".into(), rgba(0x3e61f4ff).into()), - ("punctuation.list_marker".into(), rgba(0x242924ff).into()), - ("punctuation.delimiter".into(), rgba(0x5e6e5eff).into()), - ("punctuation.special".into(), rgba(0xe618c3ff).into()), - ("variant".into(), rgba(0x98981bff).into()), - ("predictive".into(), rgba(0x00a2b5ff).into()), - ("attribute".into(), rgba(0x3e61f4ff).into()), - ("preproc".into(), rgba(0x131513ff).into()), - ("embedded".into(), rgba(0x131513ff).into()), - ("punctuation".into(), rgba(0x242924ff).into()), - ("label".into(), rgba(0x3e61f4ff).into()), - ("function.method".into(), rgba(0x3d62f5ff).into()), - ("property".into(), rgba(0xe6183bff).into()), - ("title".into(), rgba(0x131513ff).into()), - ("variable".into(), rgba(0x242924ff).into()), - ("function".into(), rgba(0x3d62f5ff).into()), - ("variable.special".into(), rgba(0xac2aeeff).into()), - ("type".into(), rgba(0x98981bff).into()), - ("text.literal".into(), rgba(0x87711fff).into()), - ("hint".into(), rgba(0x008fa1ff).into()), - ( - "function.special.definition".into(), - rgba(0x98981bff).into(), - ), - ("punctuation.bracket".into(), rgba(0x5e6e5eff).into()), - ], - }, - status_bar: rgba(0xb4ceb4ff).into(), - title_bar: rgba(0xb4ceb4ff).into(), - toolbar: rgba(0xf3faf3ff).into(), - tab_bar: rgba(0xdaeedaff).into(), - editor: rgba(0xf3faf3ff).into(), - editor_subheader: rgba(0xdaeedaff).into(), - editor_active_line: rgba(0xdaeedaff).into(), - terminal: rgba(0xf3faf3ff).into(), - image_fallback_background: rgba(0xb4ceb4ff).into(), - git_created: rgba(0x2aa32aff).into(), - git_modified: rgba(0x3e61f4ff).into(), - git_deleted: rgba(0xe61c3dff).into(), - git_conflict: rgba(0x98981cff).into(), - git_ignored: rgba(0x718771ff).into(), - git_renamed: rgba(0x98981cff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e61f4ff).into(), - selection: rgba(0x3e61f43d).into(), - }, - PlayerTheme { - cursor: rgba(0x2aa32aff).into(), - selection: rgba(0x2aa32a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61cc2ff).into(), - selection: rgba(0xe61cc23d).into(), - }, - PlayerTheme { - cursor: rgba(0x87711fff).into(), - selection: rgba(0x87711f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xac2dedff).into(), - selection: rgba(0xac2ded3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1c99b3ff).into(), - selection: rgba(0x1c99b33d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61c3dff).into(), - selection: rgba(0xe61c3d3d).into(), - }, - PlayerTheme { - cursor: rgba(0x98981cff).into(), - selection: rgba(0x98981c3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_sulphurpool_dark.rs b/crates/theme2/src/themes/atelier_sulphurpool_dark.rs deleted file mode 100644 index 8be8451740..0000000000 --- a/crates/theme2/src/themes/atelier_sulphurpool_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_sulphurpool_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Sulphurpool Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b6385ff).into(), - border_variant: rgba(0x5b6385ff).into(), - border_focused: rgba(0x203348ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3e4769ff).into(), - surface: rgba(0x262f51ff).into(), - background: rgba(0x3e4769ff).into(), - filled_element: rgba(0x3e4769ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x161f2bff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x161f2bff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf5f7ffff).into(), - text_muted: rgba(0x959bb2ff).into(), - text_placeholder: rgba(0xc94922ff).into(), - text_disabled: rgba(0x7e849eff).into(), - text_accent: rgba(0x3e8ed0ff).into(), - icon_muted: rgba(0x959bb2ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("title".into(), rgba(0xf5f7ffff).into()), - ("constructor".into(), rgba(0x3e8ed0ff).into()), - ("type".into(), rgba(0xc08b2fff).into()), - ("punctuation.list_marker".into(), rgba(0xdfe2f1ff).into()), - ("property".into(), rgba(0xc94821ff).into()), - ("link_uri".into(), rgba(0xac9739ff).into()), - ("string.escape".into(), rgba(0x979db4ff).into()), - ("constant".into(), rgba(0xac9739ff).into()), - ("embedded".into(), rgba(0xf5f7ffff).into()), - ("punctuation.special".into(), rgba(0x9b6279ff).into()), - ("punctuation.bracket".into(), rgba(0x979db4ff).into()), - ("preproc".into(), rgba(0xf5f7ffff).into()), - ("emphasis.strong".into(), rgba(0x3e8ed0ff).into()), - ("emphasis".into(), rgba(0x3e8ed0ff).into()), - ("enum".into(), rgba(0xc76a29ff).into()), - ("boolean".into(), rgba(0xac9739ff).into()), - ("primary".into(), rgba(0xdfe2f1ff).into()), - ("function.method".into(), rgba(0x3d8fd1ff).into()), - ( - "function.special.definition".into(), - rgba(0xc08b2fff).into(), - ), - ("comment.doc".into(), rgba(0x979db4ff).into()), - ("string".into(), rgba(0xac9738ff).into()), - ("text.literal".into(), rgba(0xc76a29ff).into()), - ("operator".into(), rgba(0x979db4ff).into()), - ("number".into(), rgba(0xc76a28ff).into()), - ("string.special".into(), rgba(0x9b6279ff).into()), - ("punctuation.delimiter".into(), rgba(0x979db4ff).into()), - ("tag".into(), rgba(0x3e8ed0ff).into()), - ("string.special.symbol".into(), rgba(0xac9738ff).into()), - ("variable".into(), rgba(0xdfe2f1ff).into()), - ("attribute".into(), rgba(0x3e8ed0ff).into()), - ("punctuation".into(), rgba(0xdfe2f1ff).into()), - ("string.regex".into(), rgba(0x21a2c9ff).into()), - ("keyword".into(), rgba(0x6679ccff).into()), - ("label".into(), rgba(0x3e8ed0ff).into()), - ("hint".into(), rgba(0x6c81a5ff).into()), - ("function".into(), rgba(0x3d8fd1ff).into()), - ("link_text".into(), rgba(0xc76a29ff).into()), - ("variant".into(), rgba(0xc08b2fff).into()), - ("variable.special".into(), rgba(0x6679ccff).into()), - ("predictive".into(), rgba(0x58709aff).into()), - ("comment".into(), rgba(0x6a7293ff).into()), - ], - }, - status_bar: rgba(0x3e4769ff).into(), - title_bar: rgba(0x3e4769ff).into(), - toolbar: rgba(0x202646ff).into(), - tab_bar: rgba(0x262f51ff).into(), - editor: rgba(0x202646ff).into(), - editor_subheader: rgba(0x262f51ff).into(), - editor_active_line: rgba(0x262f51ff).into(), - terminal: rgba(0x202646ff).into(), - image_fallback_background: rgba(0x3e4769ff).into(), - git_created: rgba(0xac9739ff).into(), - git_modified: rgba(0x3e8ed0ff).into(), - git_deleted: rgba(0xc94922ff).into(), - git_conflict: rgba(0xc08b30ff).into(), - git_ignored: rgba(0x7e849eff).into(), - git_renamed: rgba(0xc08b30ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e8ed0ff).into(), - selection: rgba(0x3e8ed03d).into(), - }, - PlayerTheme { - cursor: rgba(0xac9739ff).into(), - selection: rgba(0xac97393d).into(), - }, - PlayerTheme { - cursor: rgba(0x9b6279ff).into(), - selection: rgba(0x9b62793d).into(), - }, - PlayerTheme { - cursor: rgba(0xc76a29ff).into(), - selection: rgba(0xc76a293d).into(), - }, - PlayerTheme { - cursor: rgba(0x6679ccff).into(), - selection: rgba(0x6679cc3d).into(), - }, - PlayerTheme { - cursor: rgba(0x24a1c9ff).into(), - selection: rgba(0x24a1c93d).into(), - }, - PlayerTheme { - cursor: rgba(0xc94922ff).into(), - selection: rgba(0xc949223d).into(), - }, - PlayerTheme { - cursor: rgba(0xc08b30ff).into(), - selection: rgba(0xc08b303d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_sulphurpool_light.rs b/crates/theme2/src/themes/atelier_sulphurpool_light.rs deleted file mode 100644 index dba723331a..0000000000 --- a/crates/theme2/src/themes/atelier_sulphurpool_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_sulphurpool_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Sulphurpool Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x9a9fb6ff).into(), - border_variant: rgba(0x9a9fb6ff).into(), - border_focused: rgba(0xc2d5efff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc1c5d8ff).into(), - surface: rgba(0xe5e8f5ff).into(), - background: rgba(0xc1c5d8ff).into(), - filled_element: rgba(0xc1c5d8ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdde7f6ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdde7f6ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x202646ff).into(), - text_muted: rgba(0x5f6789ff).into(), - text_placeholder: rgba(0xc94922ff).into(), - text_disabled: rgba(0x767d9aff).into(), - text_accent: rgba(0x3e8fd0ff).into(), - icon_muted: rgba(0x5f6789ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special".into(), rgba(0x9b6279ff).into()), - ("string.regex".into(), rgba(0x21a2c9ff).into()), - ("embedded".into(), rgba(0x202646ff).into()), - ("string".into(), rgba(0xac9738ff).into()), - ( - "function.special.definition".into(), - rgba(0xc08b2fff).into(), - ), - ("hint".into(), rgba(0x7087b2ff).into()), - ("function.method".into(), rgba(0x3d8fd1ff).into()), - ("punctuation.list_marker".into(), rgba(0x293256ff).into()), - ("punctuation".into(), rgba(0x293256ff).into()), - ("constant".into(), rgba(0xac9739ff).into()), - ("label".into(), rgba(0x3e8fd0ff).into()), - ("comment.doc".into(), rgba(0x5d6587ff).into()), - ("property".into(), rgba(0xc94821ff).into()), - ("punctuation.bracket".into(), rgba(0x5d6587ff).into()), - ("constructor".into(), rgba(0x3e8fd0ff).into()), - ("variable.special".into(), rgba(0x6679ccff).into()), - ("emphasis".into(), rgba(0x3e8fd0ff).into()), - ("link_text".into(), rgba(0xc76a29ff).into()), - ("keyword".into(), rgba(0x6679ccff).into()), - ("primary".into(), rgba(0x293256ff).into()), - ("comment".into(), rgba(0x898ea4ff).into()), - ("title".into(), rgba(0x202646ff).into()), - ("link_uri".into(), rgba(0xac9739ff).into()), - ("text.literal".into(), rgba(0xc76a29ff).into()), - ("operator".into(), rgba(0x5d6587ff).into()), - ("number".into(), rgba(0xc76a28ff).into()), - ("preproc".into(), rgba(0x202646ff).into()), - ("attribute".into(), rgba(0x3e8fd0ff).into()), - ("emphasis.strong".into(), rgba(0x3e8fd0ff).into()), - ("string.escape".into(), rgba(0x5d6587ff).into()), - ("tag".into(), rgba(0x3e8fd0ff).into()), - ("variable".into(), rgba(0x293256ff).into()), - ("predictive".into(), rgba(0x8599beff).into()), - ("enum".into(), rgba(0xc76a29ff).into()), - ("string.special.symbol".into(), rgba(0xac9738ff).into()), - ("punctuation.delimiter".into(), rgba(0x5d6587ff).into()), - ("function".into(), rgba(0x3d8fd1ff).into()), - ("type".into(), rgba(0xc08b2fff).into()), - ("punctuation.special".into(), rgba(0x9b6279ff).into()), - ("variant".into(), rgba(0xc08b2fff).into()), - ("boolean".into(), rgba(0xac9739ff).into()), - ], - }, - status_bar: rgba(0xc1c5d8ff).into(), - title_bar: rgba(0xc1c5d8ff).into(), - toolbar: rgba(0xf5f7ffff).into(), - tab_bar: rgba(0xe5e8f5ff).into(), - editor: rgba(0xf5f7ffff).into(), - editor_subheader: rgba(0xe5e8f5ff).into(), - editor_active_line: rgba(0xe5e8f5ff).into(), - terminal: rgba(0xf5f7ffff).into(), - image_fallback_background: rgba(0xc1c5d8ff).into(), - git_created: rgba(0xac9739ff).into(), - git_modified: rgba(0x3e8fd0ff).into(), - git_deleted: rgba(0xc94922ff).into(), - git_conflict: rgba(0xc08b30ff).into(), - git_ignored: rgba(0x767d9aff).into(), - git_renamed: rgba(0xc08b30ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e8fd0ff).into(), - selection: rgba(0x3e8fd03d).into(), - }, - PlayerTheme { - cursor: rgba(0xac9739ff).into(), - selection: rgba(0xac97393d).into(), - }, - PlayerTheme { - cursor: rgba(0x9b6279ff).into(), - selection: rgba(0x9b62793d).into(), - }, - PlayerTheme { - cursor: rgba(0xc76a29ff).into(), - selection: rgba(0xc76a293d).into(), - }, - PlayerTheme { - cursor: rgba(0x6679cbff).into(), - selection: rgba(0x6679cb3d).into(), - }, - PlayerTheme { - cursor: rgba(0x24a1c9ff).into(), - selection: rgba(0x24a1c93d).into(), - }, - PlayerTheme { - cursor: rgba(0xc94922ff).into(), - selection: rgba(0xc949223d).into(), - }, - PlayerTheme { - cursor: rgba(0xc08b30ff).into(), - selection: rgba(0xc08b303d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/ayu_dark.rs b/crates/theme2/src/themes/ayu_dark.rs deleted file mode 100644 index 35d3a43154..0000000000 --- a/crates/theme2/src/themes/ayu_dark.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn ayu_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Ayu Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x3f4043ff).into(), - border_variant: rgba(0x3f4043ff).into(), - border_focused: rgba(0x1b4a6eff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x313337ff).into(), - surface: rgba(0x1f2127ff).into(), - background: rgba(0x313337ff).into(), - filled_element: rgba(0x313337ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0d2f4eff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0d2f4eff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xbfbdb6ff).into(), - text_muted: rgba(0x8a8986ff).into(), - text_placeholder: rgba(0xef7177ff).into(), - text_disabled: rgba(0x696a6aff).into(), - text_accent: rgba(0x5ac1feff).into(), - icon_muted: rgba(0x8a8986ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("emphasis".into(), rgba(0x5ac1feff).into()), - ("punctuation.bracket".into(), rgba(0xa6a5a0ff).into()), - ("constructor".into(), rgba(0x5ac1feff).into()), - ("predictive".into(), rgba(0x5a728bff).into()), - ("emphasis.strong".into(), rgba(0x5ac1feff).into()), - ("string.regex".into(), rgba(0x95e6cbff).into()), - ("tag".into(), rgba(0x5ac1feff).into()), - ("punctuation".into(), rgba(0xa6a5a0ff).into()), - ("number".into(), rgba(0xd2a6ffff).into()), - ("punctuation.special".into(), rgba(0xd2a6ffff).into()), - ("primary".into(), rgba(0xbfbdb6ff).into()), - ("boolean".into(), rgba(0xd2a6ffff).into()), - ("variant".into(), rgba(0x5ac1feff).into()), - ("link_uri".into(), rgba(0xaad84cff).into()), - ("comment.doc".into(), rgba(0x8c8b88ff).into()), - ("title".into(), rgba(0xbfbdb6ff).into()), - ("text.literal".into(), rgba(0xfe8f40ff).into()), - ("link_text".into(), rgba(0xfe8f40ff).into()), - ("punctuation.delimiter".into(), rgba(0xa6a5a0ff).into()), - ("string.escape".into(), rgba(0x8c8b88ff).into()), - ("hint".into(), rgba(0x628b80ff).into()), - ("type".into(), rgba(0x59c2ffff).into()), - ("variable".into(), rgba(0xbfbdb6ff).into()), - ("label".into(), rgba(0x5ac1feff).into()), - ("enum".into(), rgba(0xfe8f40ff).into()), - ("operator".into(), rgba(0xf29668ff).into()), - ("function".into(), rgba(0xffb353ff).into()), - ("preproc".into(), rgba(0xbfbdb6ff).into()), - ("embedded".into(), rgba(0xbfbdb6ff).into()), - ("string".into(), rgba(0xa9d94bff).into()), - ("attribute".into(), rgba(0x5ac1feff).into()), - ("keyword".into(), rgba(0xff8f3fff).into()), - ("string.special.symbol".into(), rgba(0xfe8f40ff).into()), - ("comment".into(), rgba(0xabb5be8c).into()), - ("property".into(), rgba(0x5ac1feff).into()), - ("punctuation.list_marker".into(), rgba(0xa6a5a0ff).into()), - ("constant".into(), rgba(0xd2a6ffff).into()), - ("string.special".into(), rgba(0xe5b572ff).into()), - ], - }, - status_bar: rgba(0x313337ff).into(), - title_bar: rgba(0x313337ff).into(), - toolbar: rgba(0x0d1016ff).into(), - tab_bar: rgba(0x1f2127ff).into(), - editor: rgba(0x0d1016ff).into(), - editor_subheader: rgba(0x1f2127ff).into(), - editor_active_line: rgba(0x1f2127ff).into(), - terminal: rgba(0x0d1016ff).into(), - image_fallback_background: rgba(0x313337ff).into(), - git_created: rgba(0xaad84cff).into(), - git_modified: rgba(0x5ac1feff).into(), - git_deleted: rgba(0xef7177ff).into(), - git_conflict: rgba(0xfeb454ff).into(), - git_ignored: rgba(0x696a6aff).into(), - git_renamed: rgba(0xfeb454ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5ac1feff).into(), - selection: rgba(0x5ac1fe3d).into(), - }, - PlayerTheme { - cursor: rgba(0xaad84cff).into(), - selection: rgba(0xaad84c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x39bae5ff).into(), - selection: rgba(0x39bae53d).into(), - }, - PlayerTheme { - cursor: rgba(0xfe8f40ff).into(), - selection: rgba(0xfe8f403d).into(), - }, - PlayerTheme { - cursor: rgba(0xd2a6feff).into(), - selection: rgba(0xd2a6fe3d).into(), - }, - PlayerTheme { - cursor: rgba(0x95e5cbff).into(), - selection: rgba(0x95e5cb3d).into(), - }, - PlayerTheme { - cursor: rgba(0xef7177ff).into(), - selection: rgba(0xef71773d).into(), - }, - PlayerTheme { - cursor: rgba(0xfeb454ff).into(), - selection: rgba(0xfeb4543d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/ayu_light.rs b/crates/theme2/src/themes/ayu_light.rs deleted file mode 100644 index 887282e564..0000000000 --- a/crates/theme2/src/themes/ayu_light.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn ayu_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Ayu Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xcfd1d2ff).into(), - border_variant: rgba(0xcfd1d2ff).into(), - border_focused: rgba(0xc4daf6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xdcdddeff).into(), - surface: rgba(0xececedff).into(), - background: rgba(0xdcdddeff).into(), - filled_element: rgba(0xdcdddeff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdeebfaff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdeebfaff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x5c6166ff).into(), - text_muted: rgba(0x8b8e92ff).into(), - text_placeholder: rgba(0xef7271ff).into(), - text_disabled: rgba(0xa9acaeff).into(), - text_accent: rgba(0x3b9ee5ff).into(), - icon_muted: rgba(0x8b8e92ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string".into(), rgba(0x86b300ff).into()), - ("enum".into(), rgba(0xf98d3fff).into()), - ("comment".into(), rgba(0x787b8099).into()), - ("comment.doc".into(), rgba(0x898d90ff).into()), - ("emphasis".into(), rgba(0x3b9ee5ff).into()), - ("keyword".into(), rgba(0xfa8d3eff).into()), - ("string.regex".into(), rgba(0x4bbf98ff).into()), - ("text.literal".into(), rgba(0xf98d3fff).into()), - ("string.escape".into(), rgba(0x898d90ff).into()), - ("link_text".into(), rgba(0xf98d3fff).into()), - ("punctuation".into(), rgba(0x73777bff).into()), - ("constructor".into(), rgba(0x3b9ee5ff).into()), - ("constant".into(), rgba(0xa37accff).into()), - ("variable".into(), rgba(0x5c6166ff).into()), - ("primary".into(), rgba(0x5c6166ff).into()), - ("emphasis.strong".into(), rgba(0x3b9ee5ff).into()), - ("string.special".into(), rgba(0xe6ba7eff).into()), - ("number".into(), rgba(0xa37accff).into()), - ("preproc".into(), rgba(0x5c6166ff).into()), - ("punctuation.delimiter".into(), rgba(0x73777bff).into()), - ("string.special.symbol".into(), rgba(0xf98d3fff).into()), - ("boolean".into(), rgba(0xa37accff).into()), - ("property".into(), rgba(0x3b9ee5ff).into()), - ("title".into(), rgba(0x5c6166ff).into()), - ("hint".into(), rgba(0x8ca7c2ff).into()), - ("predictive".into(), rgba(0x9eb9d3ff).into()), - ("operator".into(), rgba(0xed9365ff).into()), - ("type".into(), rgba(0x389ee6ff).into()), - ("function".into(), rgba(0xf2ad48ff).into()), - ("variant".into(), rgba(0x3b9ee5ff).into()), - ("label".into(), rgba(0x3b9ee5ff).into()), - ("punctuation.list_marker".into(), rgba(0x73777bff).into()), - ("punctuation.bracket".into(), rgba(0x73777bff).into()), - ("embedded".into(), rgba(0x5c6166ff).into()), - ("punctuation.special".into(), rgba(0xa37accff).into()), - ("attribute".into(), rgba(0x3b9ee5ff).into()), - ("tag".into(), rgba(0x3b9ee5ff).into()), - ("link_uri".into(), rgba(0x85b304ff).into()), - ], - }, - status_bar: rgba(0xdcdddeff).into(), - title_bar: rgba(0xdcdddeff).into(), - toolbar: rgba(0xfcfcfcff).into(), - tab_bar: rgba(0xececedff).into(), - editor: rgba(0xfcfcfcff).into(), - editor_subheader: rgba(0xececedff).into(), - editor_active_line: rgba(0xececedff).into(), - terminal: rgba(0xfcfcfcff).into(), - image_fallback_background: rgba(0xdcdddeff).into(), - git_created: rgba(0x85b304ff).into(), - git_modified: rgba(0x3b9ee5ff).into(), - git_deleted: rgba(0xef7271ff).into(), - git_conflict: rgba(0xf1ad49ff).into(), - git_ignored: rgba(0xa9acaeff).into(), - git_renamed: rgba(0xf1ad49ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3b9ee5ff).into(), - selection: rgba(0x3b9ee53d).into(), - }, - PlayerTheme { - cursor: rgba(0x85b304ff).into(), - selection: rgba(0x85b3043d).into(), - }, - PlayerTheme { - cursor: rgba(0x55b4d3ff).into(), - selection: rgba(0x55b4d33d).into(), - }, - PlayerTheme { - cursor: rgba(0xf98d3fff).into(), - selection: rgba(0xf98d3f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa37accff).into(), - selection: rgba(0xa37acc3d).into(), - }, - PlayerTheme { - cursor: rgba(0x4dbf99ff).into(), - selection: rgba(0x4dbf993d).into(), - }, - PlayerTheme { - cursor: rgba(0xef7271ff).into(), - selection: rgba(0xef72713d).into(), - }, - PlayerTheme { - cursor: rgba(0xf1ad49ff).into(), - selection: rgba(0xf1ad493d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/ayu_mirage.rs b/crates/theme2/src/themes/ayu_mirage.rs deleted file mode 100644 index 2974881a18..0000000000 --- a/crates/theme2/src/themes/ayu_mirage.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn ayu_mirage() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Ayu Mirage".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x53565dff).into(), - border_variant: rgba(0x53565dff).into(), - border_focused: rgba(0x24556fff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x464a52ff).into(), - surface: rgba(0x353944ff).into(), - background: rgba(0x464a52ff).into(), - filled_element: rgba(0x464a52ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x123950ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x123950ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xcccac2ff).into(), - text_muted: rgba(0x9a9a98ff).into(), - text_placeholder: rgba(0xf18779ff).into(), - text_disabled: rgba(0x7b7d7fff).into(), - text_accent: rgba(0x72cffeff).into(), - icon_muted: rgba(0x9a9a98ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("text.literal".into(), rgba(0xfead66ff).into()), - ("link_text".into(), rgba(0xfead66ff).into()), - ("function".into(), rgba(0xffd173ff).into()), - ("punctuation.delimiter".into(), rgba(0xb4b3aeff).into()), - ("property".into(), rgba(0x72cffeff).into()), - ("title".into(), rgba(0xcccac2ff).into()), - ("boolean".into(), rgba(0xdfbfffff).into()), - ("link_uri".into(), rgba(0xd5fe80ff).into()), - ("label".into(), rgba(0x72cffeff).into()), - ("primary".into(), rgba(0xcccac2ff).into()), - ("number".into(), rgba(0xdfbfffff).into()), - ("variant".into(), rgba(0x72cffeff).into()), - ("enum".into(), rgba(0xfead66ff).into()), - ("string.special.symbol".into(), rgba(0xfead66ff).into()), - ("operator".into(), rgba(0xf29e74ff).into()), - ("punctuation.special".into(), rgba(0xdfbfffff).into()), - ("constructor".into(), rgba(0x72cffeff).into()), - ("type".into(), rgba(0x73cfffff).into()), - ("emphasis.strong".into(), rgba(0x72cffeff).into()), - ("embedded".into(), rgba(0xcccac2ff).into()), - ("comment".into(), rgba(0xb8cfe680).into()), - ("tag".into(), rgba(0x72cffeff).into()), - ("keyword".into(), rgba(0xffad65ff).into()), - ("punctuation".into(), rgba(0xb4b3aeff).into()), - ("preproc".into(), rgba(0xcccac2ff).into()), - ("hint".into(), rgba(0x7399a3ff).into()), - ("string.special".into(), rgba(0xffdfb3ff).into()), - ("attribute".into(), rgba(0x72cffeff).into()), - ("string.regex".into(), rgba(0x95e6cbff).into()), - ("predictive".into(), rgba(0x6d839bff).into()), - ("comment.doc".into(), rgba(0x9b9b99ff).into()), - ("emphasis".into(), rgba(0x72cffeff).into()), - ("string".into(), rgba(0xd4fe7fff).into()), - ("constant".into(), rgba(0xdfbfffff).into()), - ("string.escape".into(), rgba(0x9b9b99ff).into()), - ("variable".into(), rgba(0xcccac2ff).into()), - ("punctuation.bracket".into(), rgba(0xb4b3aeff).into()), - ("punctuation.list_marker".into(), rgba(0xb4b3aeff).into()), - ], - }, - status_bar: rgba(0x464a52ff).into(), - title_bar: rgba(0x464a52ff).into(), - toolbar: rgba(0x242835ff).into(), - tab_bar: rgba(0x353944ff).into(), - editor: rgba(0x242835ff).into(), - editor_subheader: rgba(0x353944ff).into(), - editor_active_line: rgba(0x353944ff).into(), - terminal: rgba(0x242835ff).into(), - image_fallback_background: rgba(0x464a52ff).into(), - git_created: rgba(0xd5fe80ff).into(), - git_modified: rgba(0x72cffeff).into(), - git_deleted: rgba(0xf18779ff).into(), - git_conflict: rgba(0xfecf72ff).into(), - git_ignored: rgba(0x7b7d7fff).into(), - git_renamed: rgba(0xfecf72ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x72cffeff).into(), - selection: rgba(0x72cffe3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd5fe80ff).into(), - selection: rgba(0xd5fe803d).into(), - }, - PlayerTheme { - cursor: rgba(0x5bcde5ff).into(), - selection: rgba(0x5bcde53d).into(), - }, - PlayerTheme { - cursor: rgba(0xfead66ff).into(), - selection: rgba(0xfead663d).into(), - }, - PlayerTheme { - cursor: rgba(0xdebffeff).into(), - selection: rgba(0xdebffe3d).into(), - }, - PlayerTheme { - cursor: rgba(0x95e5cbff).into(), - selection: rgba(0x95e5cb3d).into(), - }, - PlayerTheme { - cursor: rgba(0xf18779ff).into(), - selection: rgba(0xf187793d).into(), - }, - PlayerTheme { - cursor: rgba(0xfecf72ff).into(), - selection: rgba(0xfecf723d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_dark.rs b/crates/theme2/src/themes/gruvbox_dark.rs deleted file mode 100644 index 6e982808cf..0000000000 --- a/crates/theme2/src/themes/gruvbox_dark.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b534dff).into(), - border_variant: rgba(0x5b534dff).into(), - border_focused: rgba(0x303a36ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x4c4642ff).into(), - surface: rgba(0x3a3735ff).into(), - background: rgba(0x4c4642ff).into(), - filled_element: rgba(0x4c4642ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1e2321ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1e2321ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfbf1c7ff).into(), - text_muted: rgba(0xc5b597ff).into(), - text_placeholder: rgba(0xfb4a35ff).into(), - text_disabled: rgba(0x998b78ff).into(), - text_accent: rgba(0x83a598ff).into(), - icon_muted: rgba(0xc5b597ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("operator".into(), rgba(0x8ec07cff).into()), - ("string.special.symbol".into(), rgba(0x8ec07cff).into()), - ("emphasis.strong".into(), rgba(0x83a598ff).into()), - ("attribute".into(), rgba(0x83a598ff).into()), - ("property".into(), rgba(0xebdbb2ff).into()), - ("comment.doc".into(), rgba(0xc6b697ff).into()), - ("emphasis".into(), rgba(0x83a598ff).into()), - ("variant".into(), rgba(0x83a598ff).into()), - ("text.literal".into(), rgba(0x83a598ff).into()), - ("keyword".into(), rgba(0xfb4833ff).into()), - ("primary".into(), rgba(0xebdbb2ff).into()), - ("variable".into(), rgba(0x83a598ff).into()), - ("enum".into(), rgba(0xfe7f18ff).into()), - ("constructor".into(), rgba(0x83a598ff).into()), - ("punctuation".into(), rgba(0xd5c4a1ff).into()), - ("link_uri".into(), rgba(0xd3869bff).into()), - ("hint".into(), rgba(0x8c957dff).into()), - ("string.regex".into(), rgba(0xfe7f18ff).into()), - ("punctuation.delimiter".into(), rgba(0xe5d5adff).into()), - ("string".into(), rgba(0xb8bb25ff).into()), - ("punctuation.special".into(), rgba(0xe5d5adff).into()), - ("link_text".into(), rgba(0x8ec07cff).into()), - ("tag".into(), rgba(0x8ec07cff).into()), - ("string.escape".into(), rgba(0xc6b697ff).into()), - ("label".into(), rgba(0x83a598ff).into()), - ("constant".into(), rgba(0xfabd2eff).into()), - ("type".into(), rgba(0xfabd2eff).into()), - ("number".into(), rgba(0xd3869bff).into()), - ("string.special".into(), rgba(0xd3869bff).into()), - ("function.builtin".into(), rgba(0xfb4833ff).into()), - ("boolean".into(), rgba(0xd3869bff).into()), - ("embedded".into(), rgba(0x8ec07cff).into()), - ("title".into(), rgba(0xb8bb25ff).into()), - ("function".into(), rgba(0xb8bb25ff).into()), - ("punctuation.bracket".into(), rgba(0xa89984ff).into()), - ("comment".into(), rgba(0xa89984ff).into()), - ("preproc".into(), rgba(0xfbf1c7ff).into()), - ("predictive".into(), rgba(0x717363ff).into()), - ("punctuation.list_marker".into(), rgba(0xebdbb2ff).into()), - ], - }, - status_bar: rgba(0x4c4642ff).into(), - title_bar: rgba(0x4c4642ff).into(), - toolbar: rgba(0x282828ff).into(), - tab_bar: rgba(0x3a3735ff).into(), - editor: rgba(0x282828ff).into(), - editor_subheader: rgba(0x3a3735ff).into(), - editor_active_line: rgba(0x3a3735ff).into(), - terminal: rgba(0x282828ff).into(), - image_fallback_background: rgba(0x4c4642ff).into(), - git_created: rgba(0xb7bb26ff).into(), - git_modified: rgba(0x83a598ff).into(), - git_deleted: rgba(0xfb4a35ff).into(), - git_conflict: rgba(0xf9bd2fff).into(), - git_ignored: rgba(0x998b78ff).into(), - git_renamed: rgba(0xf9bd2fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb7bb26ff).into(), - selection: rgba(0xb7bb263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa89984ff).into(), - selection: rgba(0xa899843d).into(), - }, - PlayerTheme { - cursor: rgba(0xfd801bff).into(), - selection: rgba(0xfd801b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd3869bff).into(), - selection: rgba(0xd3869b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8ec07cff).into(), - selection: rgba(0x8ec07c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfb4a35ff).into(), - selection: rgba(0xfb4a353d).into(), - }, - PlayerTheme { - cursor: rgba(0xf9bd2fff).into(), - selection: rgba(0xf9bd2f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_dark_hard.rs b/crates/theme2/src/themes/gruvbox_dark_hard.rs deleted file mode 100644 index 159ab28325..0000000000 --- a/crates/theme2/src/themes/gruvbox_dark_hard.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_dark_hard() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Dark Hard".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b534dff).into(), - border_variant: rgba(0x5b534dff).into(), - border_focused: rgba(0x303a36ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x4c4642ff).into(), - surface: rgba(0x393634ff).into(), - background: rgba(0x4c4642ff).into(), - filled_element: rgba(0x4c4642ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1e2321ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1e2321ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfbf1c7ff).into(), - text_muted: rgba(0xc5b597ff).into(), - text_placeholder: rgba(0xfb4a35ff).into(), - text_disabled: rgba(0x998b78ff).into(), - text_accent: rgba(0x83a598ff).into(), - icon_muted: rgba(0xc5b597ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("primary".into(), rgba(0xebdbb2ff).into()), - ("label".into(), rgba(0x83a598ff).into()), - ("punctuation.delimiter".into(), rgba(0xe5d5adff).into()), - ("variant".into(), rgba(0x83a598ff).into()), - ("type".into(), rgba(0xfabd2eff).into()), - ("string.regex".into(), rgba(0xfe7f18ff).into()), - ("function.builtin".into(), rgba(0xfb4833ff).into()), - ("title".into(), rgba(0xb8bb25ff).into()), - ("string".into(), rgba(0xb8bb25ff).into()), - ("operator".into(), rgba(0x8ec07cff).into()), - ("embedded".into(), rgba(0x8ec07cff).into()), - ("punctuation.bracket".into(), rgba(0xa89984ff).into()), - ("string.special".into(), rgba(0xd3869bff).into()), - ("attribute".into(), rgba(0x83a598ff).into()), - ("comment".into(), rgba(0xa89984ff).into()), - ("link_text".into(), rgba(0x8ec07cff).into()), - ("punctuation.special".into(), rgba(0xe5d5adff).into()), - ("punctuation.list_marker".into(), rgba(0xebdbb2ff).into()), - ("comment.doc".into(), rgba(0xc6b697ff).into()), - ("preproc".into(), rgba(0xfbf1c7ff).into()), - ("text.literal".into(), rgba(0x83a598ff).into()), - ("function".into(), rgba(0xb8bb25ff).into()), - ("predictive".into(), rgba(0x717363ff).into()), - ("emphasis.strong".into(), rgba(0x83a598ff).into()), - ("punctuation".into(), rgba(0xd5c4a1ff).into()), - ("string.special.symbol".into(), rgba(0x8ec07cff).into()), - ("property".into(), rgba(0xebdbb2ff).into()), - ("keyword".into(), rgba(0xfb4833ff).into()), - ("constructor".into(), rgba(0x83a598ff).into()), - ("tag".into(), rgba(0x8ec07cff).into()), - ("variable".into(), rgba(0x83a598ff).into()), - ("enum".into(), rgba(0xfe7f18ff).into()), - ("hint".into(), rgba(0x8c957dff).into()), - ("number".into(), rgba(0xd3869bff).into()), - ("constant".into(), rgba(0xfabd2eff).into()), - ("boolean".into(), rgba(0xd3869bff).into()), - ("link_uri".into(), rgba(0xd3869bff).into()), - ("string.escape".into(), rgba(0xc6b697ff).into()), - ("emphasis".into(), rgba(0x83a598ff).into()), - ], - }, - status_bar: rgba(0x4c4642ff).into(), - title_bar: rgba(0x4c4642ff).into(), - toolbar: rgba(0x1d2021ff).into(), - tab_bar: rgba(0x393634ff).into(), - editor: rgba(0x1d2021ff).into(), - editor_subheader: rgba(0x393634ff).into(), - editor_active_line: rgba(0x393634ff).into(), - terminal: rgba(0x1d2021ff).into(), - image_fallback_background: rgba(0x4c4642ff).into(), - git_created: rgba(0xb7bb26ff).into(), - git_modified: rgba(0x83a598ff).into(), - git_deleted: rgba(0xfb4a35ff).into(), - git_conflict: rgba(0xf9bd2fff).into(), - git_ignored: rgba(0x998b78ff).into(), - git_renamed: rgba(0xf9bd2fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb7bb26ff).into(), - selection: rgba(0xb7bb263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa89984ff).into(), - selection: rgba(0xa899843d).into(), - }, - PlayerTheme { - cursor: rgba(0xfd801bff).into(), - selection: rgba(0xfd801b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd3869bff).into(), - selection: rgba(0xd3869b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8ec07cff).into(), - selection: rgba(0x8ec07c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfb4a35ff).into(), - selection: rgba(0xfb4a353d).into(), - }, - PlayerTheme { - cursor: rgba(0xf9bd2fff).into(), - selection: rgba(0xf9bd2f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_dark_soft.rs b/crates/theme2/src/themes/gruvbox_dark_soft.rs deleted file mode 100644 index 6a6423389e..0000000000 --- a/crates/theme2/src/themes/gruvbox_dark_soft.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_dark_soft() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Dark Soft".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b534dff).into(), - border_variant: rgba(0x5b534dff).into(), - border_focused: rgba(0x303a36ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x4c4642ff).into(), - surface: rgba(0x3b3735ff).into(), - background: rgba(0x4c4642ff).into(), - filled_element: rgba(0x4c4642ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1e2321ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1e2321ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfbf1c7ff).into(), - text_muted: rgba(0xc5b597ff).into(), - text_placeholder: rgba(0xfb4a35ff).into(), - text_disabled: rgba(0x998b78ff).into(), - text_accent: rgba(0x83a598ff).into(), - icon_muted: rgba(0xc5b597ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.special".into(), rgba(0xe5d5adff).into()), - ("attribute".into(), rgba(0x83a598ff).into()), - ("preproc".into(), rgba(0xfbf1c7ff).into()), - ("keyword".into(), rgba(0xfb4833ff).into()), - ("emphasis".into(), rgba(0x83a598ff).into()), - ("punctuation.delimiter".into(), rgba(0xe5d5adff).into()), - ("punctuation.bracket".into(), rgba(0xa89984ff).into()), - ("comment".into(), rgba(0xa89984ff).into()), - ("text.literal".into(), rgba(0x83a598ff).into()), - ("predictive".into(), rgba(0x717363ff).into()), - ("link_text".into(), rgba(0x8ec07cff).into()), - ("variant".into(), rgba(0x83a598ff).into()), - ("label".into(), rgba(0x83a598ff).into()), - ("function".into(), rgba(0xb8bb25ff).into()), - ("string.regex".into(), rgba(0xfe7f18ff).into()), - ("boolean".into(), rgba(0xd3869bff).into()), - ("number".into(), rgba(0xd3869bff).into()), - ("string.escape".into(), rgba(0xc6b697ff).into()), - ("constructor".into(), rgba(0x83a598ff).into()), - ("link_uri".into(), rgba(0xd3869bff).into()), - ("string.special.symbol".into(), rgba(0x8ec07cff).into()), - ("type".into(), rgba(0xfabd2eff).into()), - ("function.builtin".into(), rgba(0xfb4833ff).into()), - ("title".into(), rgba(0xb8bb25ff).into()), - ("primary".into(), rgba(0xebdbb2ff).into()), - ("tag".into(), rgba(0x8ec07cff).into()), - ("constant".into(), rgba(0xfabd2eff).into()), - ("emphasis.strong".into(), rgba(0x83a598ff).into()), - ("string.special".into(), rgba(0xd3869bff).into()), - ("hint".into(), rgba(0x8c957dff).into()), - ("comment.doc".into(), rgba(0xc6b697ff).into()), - ("property".into(), rgba(0xebdbb2ff).into()), - ("embedded".into(), rgba(0x8ec07cff).into()), - ("operator".into(), rgba(0x8ec07cff).into()), - ("punctuation".into(), rgba(0xd5c4a1ff).into()), - ("variable".into(), rgba(0x83a598ff).into()), - ("enum".into(), rgba(0xfe7f18ff).into()), - ("punctuation.list_marker".into(), rgba(0xebdbb2ff).into()), - ("string".into(), rgba(0xb8bb25ff).into()), - ], - }, - status_bar: rgba(0x4c4642ff).into(), - title_bar: rgba(0x4c4642ff).into(), - toolbar: rgba(0x32302fff).into(), - tab_bar: rgba(0x3b3735ff).into(), - editor: rgba(0x32302fff).into(), - editor_subheader: rgba(0x3b3735ff).into(), - editor_active_line: rgba(0x3b3735ff).into(), - terminal: rgba(0x32302fff).into(), - image_fallback_background: rgba(0x4c4642ff).into(), - git_created: rgba(0xb7bb26ff).into(), - git_modified: rgba(0x83a598ff).into(), - git_deleted: rgba(0xfb4a35ff).into(), - git_conflict: rgba(0xf9bd2fff).into(), - git_ignored: rgba(0x998b78ff).into(), - git_renamed: rgba(0xf9bd2fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb7bb26ff).into(), - selection: rgba(0xb7bb263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa89984ff).into(), - selection: rgba(0xa899843d).into(), - }, - PlayerTheme { - cursor: rgba(0xfd801bff).into(), - selection: rgba(0xfd801b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd3869bff).into(), - selection: rgba(0xd3869b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8ec07cff).into(), - selection: rgba(0x8ec07c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfb4a35ff).into(), - selection: rgba(0xfb4a353d).into(), - }, - PlayerTheme { - cursor: rgba(0xf9bd2fff).into(), - selection: rgba(0xf9bd2f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_light.rs b/crates/theme2/src/themes/gruvbox_light.rs deleted file mode 100644 index 7582f8bd8a..0000000000 --- a/crates/theme2/src/themes/gruvbox_light.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc8b899ff).into(), - border_variant: rgba(0xc8b899ff).into(), - border_focused: rgba(0xadc5ccff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xd9c8a4ff).into(), - surface: rgba(0xecddb4ff).into(), - background: rgba(0xd9c8a4ff).into(), - filled_element: rgba(0xd9c8a4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd2dee2ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd2dee2ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x282828ff).into(), - text_muted: rgba(0x5f5650ff).into(), - text_placeholder: rgba(0x9d0308ff).into(), - text_disabled: rgba(0x897b6eff).into(), - text_accent: rgba(0x0b6678ff).into(), - icon_muted: rgba(0x5f5650ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("number".into(), rgba(0x8f3e71ff).into()), - ("link_text".into(), rgba(0x427b58ff).into()), - ("string.special".into(), rgba(0x8f3e71ff).into()), - ("string.special.symbol".into(), rgba(0x427b58ff).into()), - ("function".into(), rgba(0x79740eff).into()), - ("title".into(), rgba(0x79740eff).into()), - ("emphasis".into(), rgba(0x0b6678ff).into()), - ("punctuation".into(), rgba(0x3c3836ff).into()), - ("string.escape".into(), rgba(0x5d544eff).into()), - ("type".into(), rgba(0xb57613ff).into()), - ("string".into(), rgba(0x79740eff).into()), - ("keyword".into(), rgba(0x9d0006ff).into()), - ("tag".into(), rgba(0x427b58ff).into()), - ("primary".into(), rgba(0x282828ff).into()), - ("link_uri".into(), rgba(0x8f3e71ff).into()), - ("comment.doc".into(), rgba(0x5d544eff).into()), - ("boolean".into(), rgba(0x8f3e71ff).into()), - ("embedded".into(), rgba(0x427b58ff).into()), - ("hint".into(), rgba(0x677562ff).into()), - ("emphasis.strong".into(), rgba(0x0b6678ff).into()), - ("operator".into(), rgba(0x427b58ff).into()), - ("label".into(), rgba(0x0b6678ff).into()), - ("comment".into(), rgba(0x7c6f64ff).into()), - ("function.builtin".into(), rgba(0x9d0006ff).into()), - ("punctuation.bracket".into(), rgba(0x665c54ff).into()), - ("text.literal".into(), rgba(0x066578ff).into()), - ("string.regex".into(), rgba(0xaf3a02ff).into()), - ("property".into(), rgba(0x282828ff).into()), - ("attribute".into(), rgba(0x0b6678ff).into()), - ("punctuation.delimiter".into(), rgba(0x413d3aff).into()), - ("constructor".into(), rgba(0x0b6678ff).into()), - ("variable".into(), rgba(0x066578ff).into()), - ("constant".into(), rgba(0xb57613ff).into()), - ("preproc".into(), rgba(0x282828ff).into()), - ("punctuation.special".into(), rgba(0x413d3aff).into()), - ("punctuation.list_marker".into(), rgba(0x282828ff).into()), - ("variant".into(), rgba(0x0b6678ff).into()), - ("predictive".into(), rgba(0x7c9780ff).into()), - ("enum".into(), rgba(0xaf3a02ff).into()), - ], - }, - status_bar: rgba(0xd9c8a4ff).into(), - title_bar: rgba(0xd9c8a4ff).into(), - toolbar: rgba(0xfbf1c7ff).into(), - tab_bar: rgba(0xecddb4ff).into(), - editor: rgba(0xfbf1c7ff).into(), - editor_subheader: rgba(0xecddb4ff).into(), - editor_active_line: rgba(0xecddb4ff).into(), - terminal: rgba(0xfbf1c7ff).into(), - image_fallback_background: rgba(0xd9c8a4ff).into(), - git_created: rgba(0x797410ff).into(), - git_modified: rgba(0x0b6678ff).into(), - git_deleted: rgba(0x9d0308ff).into(), - git_conflict: rgba(0xb57615ff).into(), - git_ignored: rgba(0x897b6eff).into(), - git_renamed: rgba(0xb57615ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x0b6678ff).into(), - selection: rgba(0x0b66783d).into(), - }, - PlayerTheme { - cursor: rgba(0x797410ff).into(), - selection: rgba(0x7974103d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c6f64ff).into(), - selection: rgba(0x7c6f643d).into(), - }, - PlayerTheme { - cursor: rgba(0xaf3a04ff).into(), - selection: rgba(0xaf3a043d).into(), - }, - PlayerTheme { - cursor: rgba(0x8f3f70ff).into(), - selection: rgba(0x8f3f703d).into(), - }, - PlayerTheme { - cursor: rgba(0x437b59ff).into(), - selection: rgba(0x437b593d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d0308ff).into(), - selection: rgba(0x9d03083d).into(), - }, - PlayerTheme { - cursor: rgba(0xb57615ff).into(), - selection: rgba(0xb576153d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_light_hard.rs b/crates/theme2/src/themes/gruvbox_light_hard.rs deleted file mode 100644 index e5e3fe54cf..0000000000 --- a/crates/theme2/src/themes/gruvbox_light_hard.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_light_hard() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Light Hard".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc8b899ff).into(), - border_variant: rgba(0xc8b899ff).into(), - border_focused: rgba(0xadc5ccff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xd9c8a4ff).into(), - surface: rgba(0xecddb5ff).into(), - background: rgba(0xd9c8a4ff).into(), - filled_element: rgba(0xd9c8a4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd2dee2ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd2dee2ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x282828ff).into(), - text_muted: rgba(0x5f5650ff).into(), - text_placeholder: rgba(0x9d0308ff).into(), - text_disabled: rgba(0x897b6eff).into(), - text_accent: rgba(0x0b6678ff).into(), - icon_muted: rgba(0x5f5650ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("label".into(), rgba(0x0b6678ff).into()), - ("hint".into(), rgba(0x677562ff).into()), - ("boolean".into(), rgba(0x8f3e71ff).into()), - ("function.builtin".into(), rgba(0x9d0006ff).into()), - ("constant".into(), rgba(0xb57613ff).into()), - ("preproc".into(), rgba(0x282828ff).into()), - ("predictive".into(), rgba(0x7c9780ff).into()), - ("string".into(), rgba(0x79740eff).into()), - ("comment.doc".into(), rgba(0x5d544eff).into()), - ("function".into(), rgba(0x79740eff).into()), - ("title".into(), rgba(0x79740eff).into()), - ("text.literal".into(), rgba(0x066578ff).into()), - ("punctuation.bracket".into(), rgba(0x665c54ff).into()), - ("string.escape".into(), rgba(0x5d544eff).into()), - ("punctuation.delimiter".into(), rgba(0x413d3aff).into()), - ("string.special.symbol".into(), rgba(0x427b58ff).into()), - ("type".into(), rgba(0xb57613ff).into()), - ("constructor".into(), rgba(0x0b6678ff).into()), - ("property".into(), rgba(0x282828ff).into()), - ("comment".into(), rgba(0x7c6f64ff).into()), - ("enum".into(), rgba(0xaf3a02ff).into()), - ("emphasis".into(), rgba(0x0b6678ff).into()), - ("embedded".into(), rgba(0x427b58ff).into()), - ("operator".into(), rgba(0x427b58ff).into()), - ("attribute".into(), rgba(0x0b6678ff).into()), - ("emphasis.strong".into(), rgba(0x0b6678ff).into()), - ("link_text".into(), rgba(0x427b58ff).into()), - ("punctuation.special".into(), rgba(0x413d3aff).into()), - ("punctuation.list_marker".into(), rgba(0x282828ff).into()), - ("variant".into(), rgba(0x0b6678ff).into()), - ("primary".into(), rgba(0x282828ff).into()), - ("number".into(), rgba(0x8f3e71ff).into()), - ("tag".into(), rgba(0x427b58ff).into()), - ("keyword".into(), rgba(0x9d0006ff).into()), - ("link_uri".into(), rgba(0x8f3e71ff).into()), - ("string.regex".into(), rgba(0xaf3a02ff).into()), - ("variable".into(), rgba(0x066578ff).into()), - ("string.special".into(), rgba(0x8f3e71ff).into()), - ("punctuation".into(), rgba(0x3c3836ff).into()), - ], - }, - status_bar: rgba(0xd9c8a4ff).into(), - title_bar: rgba(0xd9c8a4ff).into(), - toolbar: rgba(0xf9f5d7ff).into(), - tab_bar: rgba(0xecddb5ff).into(), - editor: rgba(0xf9f5d7ff).into(), - editor_subheader: rgba(0xecddb5ff).into(), - editor_active_line: rgba(0xecddb5ff).into(), - terminal: rgba(0xf9f5d7ff).into(), - image_fallback_background: rgba(0xd9c8a4ff).into(), - git_created: rgba(0x797410ff).into(), - git_modified: rgba(0x0b6678ff).into(), - git_deleted: rgba(0x9d0308ff).into(), - git_conflict: rgba(0xb57615ff).into(), - git_ignored: rgba(0x897b6eff).into(), - git_renamed: rgba(0xb57615ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x0b6678ff).into(), - selection: rgba(0x0b66783d).into(), - }, - PlayerTheme { - cursor: rgba(0x797410ff).into(), - selection: rgba(0x7974103d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c6f64ff).into(), - selection: rgba(0x7c6f643d).into(), - }, - PlayerTheme { - cursor: rgba(0xaf3a04ff).into(), - selection: rgba(0xaf3a043d).into(), - }, - PlayerTheme { - cursor: rgba(0x8f3f70ff).into(), - selection: rgba(0x8f3f703d).into(), - }, - PlayerTheme { - cursor: rgba(0x437b59ff).into(), - selection: rgba(0x437b593d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d0308ff).into(), - selection: rgba(0x9d03083d).into(), - }, - PlayerTheme { - cursor: rgba(0xb57615ff).into(), - selection: rgba(0xb576153d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_light_soft.rs b/crates/theme2/src/themes/gruvbox_light_soft.rs deleted file mode 100644 index 15574e2960..0000000000 --- a/crates/theme2/src/themes/gruvbox_light_soft.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_light_soft() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Light Soft".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc8b899ff).into(), - border_variant: rgba(0xc8b899ff).into(), - border_focused: rgba(0xadc5ccff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xd9c8a4ff).into(), - surface: rgba(0xecdcb3ff).into(), - background: rgba(0xd9c8a4ff).into(), - filled_element: rgba(0xd9c8a4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd2dee2ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd2dee2ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x282828ff).into(), - text_muted: rgba(0x5f5650ff).into(), - text_placeholder: rgba(0x9d0308ff).into(), - text_disabled: rgba(0x897b6eff).into(), - text_accent: rgba(0x0b6678ff).into(), - icon_muted: rgba(0x5f5650ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("preproc".into(), rgba(0x282828ff).into()), - ("punctuation.list_marker".into(), rgba(0x282828ff).into()), - ("string".into(), rgba(0x79740eff).into()), - ("constant".into(), rgba(0xb57613ff).into()), - ("keyword".into(), rgba(0x9d0006ff).into()), - ("string.special.symbol".into(), rgba(0x427b58ff).into()), - ("comment.doc".into(), rgba(0x5d544eff).into()), - ("hint".into(), rgba(0x677562ff).into()), - ("number".into(), rgba(0x8f3e71ff).into()), - ("enum".into(), rgba(0xaf3a02ff).into()), - ("emphasis".into(), rgba(0x0b6678ff).into()), - ("operator".into(), rgba(0x427b58ff).into()), - ("comment".into(), rgba(0x7c6f64ff).into()), - ("embedded".into(), rgba(0x427b58ff).into()), - ("type".into(), rgba(0xb57613ff).into()), - ("title".into(), rgba(0x79740eff).into()), - ("constructor".into(), rgba(0x0b6678ff).into()), - ("punctuation.delimiter".into(), rgba(0x413d3aff).into()), - ("function".into(), rgba(0x79740eff).into()), - ("link_uri".into(), rgba(0x8f3e71ff).into()), - ("emphasis.strong".into(), rgba(0x0b6678ff).into()), - ("boolean".into(), rgba(0x8f3e71ff).into()), - ("function.builtin".into(), rgba(0x9d0006ff).into()), - ("predictive".into(), rgba(0x7c9780ff).into()), - ("string.regex".into(), rgba(0xaf3a02ff).into()), - ("tag".into(), rgba(0x427b58ff).into()), - ("text.literal".into(), rgba(0x066578ff).into()), - ("punctuation".into(), rgba(0x3c3836ff).into()), - ("punctuation.bracket".into(), rgba(0x665c54ff).into()), - ("variable".into(), rgba(0x066578ff).into()), - ("attribute".into(), rgba(0x0b6678ff).into()), - ("string.special".into(), rgba(0x8f3e71ff).into()), - ("label".into(), rgba(0x0b6678ff).into()), - ("string.escape".into(), rgba(0x5d544eff).into()), - ("link_text".into(), rgba(0x427b58ff).into()), - ("punctuation.special".into(), rgba(0x413d3aff).into()), - ("property".into(), rgba(0x282828ff).into()), - ("variant".into(), rgba(0x0b6678ff).into()), - ("primary".into(), rgba(0x282828ff).into()), - ], - }, - status_bar: rgba(0xd9c8a4ff).into(), - title_bar: rgba(0xd9c8a4ff).into(), - toolbar: rgba(0xf2e5bcff).into(), - tab_bar: rgba(0xecdcb3ff).into(), - editor: rgba(0xf2e5bcff).into(), - editor_subheader: rgba(0xecdcb3ff).into(), - editor_active_line: rgba(0xecdcb3ff).into(), - terminal: rgba(0xf2e5bcff).into(), - image_fallback_background: rgba(0xd9c8a4ff).into(), - git_created: rgba(0x797410ff).into(), - git_modified: rgba(0x0b6678ff).into(), - git_deleted: rgba(0x9d0308ff).into(), - git_conflict: rgba(0xb57615ff).into(), - git_ignored: rgba(0x897b6eff).into(), - git_renamed: rgba(0xb57615ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x0b6678ff).into(), - selection: rgba(0x0b66783d).into(), - }, - PlayerTheme { - cursor: rgba(0x797410ff).into(), - selection: rgba(0x7974103d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c6f64ff).into(), - selection: rgba(0x7c6f643d).into(), - }, - PlayerTheme { - cursor: rgba(0xaf3a04ff).into(), - selection: rgba(0xaf3a043d).into(), - }, - PlayerTheme { - cursor: rgba(0x8f3f70ff).into(), - selection: rgba(0x8f3f703d).into(), - }, - PlayerTheme { - cursor: rgba(0x437b59ff).into(), - selection: rgba(0x437b593d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d0308ff).into(), - selection: rgba(0x9d03083d).into(), - }, - PlayerTheme { - cursor: rgba(0xb57615ff).into(), - selection: rgba(0xb576153d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/mod.rs b/crates/theme2/src/themes/mod.rs deleted file mode 100644 index 17cd5ac6e0..0000000000 --- a/crates/theme2/src/themes/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -mod andromeda; -mod atelier_cave_dark; -mod atelier_cave_light; -mod atelier_dune_dark; -mod atelier_dune_light; -mod atelier_estuary_dark; -mod atelier_estuary_light; -mod atelier_forest_dark; -mod atelier_forest_light; -mod atelier_heath_dark; -mod atelier_heath_light; -mod atelier_lakeside_dark; -mod atelier_lakeside_light; -mod atelier_plateau_dark; -mod atelier_plateau_light; -mod atelier_savanna_dark; -mod atelier_savanna_light; -mod atelier_seaside_dark; -mod atelier_seaside_light; -mod atelier_sulphurpool_dark; -mod atelier_sulphurpool_light; -mod ayu_dark; -mod ayu_light; -mod ayu_mirage; -mod gruvbox_dark; -mod gruvbox_dark_hard; -mod gruvbox_dark_soft; -mod gruvbox_light; -mod gruvbox_light_hard; -mod gruvbox_light_soft; -mod one_dark; -mod one_light; -mod rose_pine; -mod rose_pine_dawn; -mod rose_pine_moon; -mod sandcastle; -mod solarized_dark; -mod solarized_light; -mod summercamp; - -pub use andromeda::*; -pub use atelier_cave_dark::*; -pub use atelier_cave_light::*; -pub use atelier_dune_dark::*; -pub use atelier_dune_light::*; -pub use atelier_estuary_dark::*; -pub use atelier_estuary_light::*; -pub use atelier_forest_dark::*; -pub use atelier_forest_light::*; -pub use atelier_heath_dark::*; -pub use atelier_heath_light::*; -pub use atelier_lakeside_dark::*; -pub use atelier_lakeside_light::*; -pub use atelier_plateau_dark::*; -pub use atelier_plateau_light::*; -pub use atelier_savanna_dark::*; -pub use atelier_savanna_light::*; -pub use atelier_seaside_dark::*; -pub use atelier_seaside_light::*; -pub use atelier_sulphurpool_dark::*; -pub use atelier_sulphurpool_light::*; -pub use ayu_dark::*; -pub use ayu_light::*; -pub use ayu_mirage::*; -pub use gruvbox_dark::*; -pub use gruvbox_dark_hard::*; -pub use gruvbox_dark_soft::*; -pub use gruvbox_light::*; -pub use gruvbox_light_hard::*; -pub use gruvbox_light_soft::*; -pub use one_dark::*; -pub use one_light::*; -pub use rose_pine::*; -pub use rose_pine_dawn::*; -pub use rose_pine_moon::*; -pub use sandcastle::*; -pub use solarized_dark::*; -pub use solarized_light::*; -pub use summercamp::*; diff --git a/crates/theme2/src/themes/one_dark.rs b/crates/theme2/src/themes/one_dark.rs deleted file mode 100644 index c7408d1820..0000000000 --- a/crates/theme2/src/themes/one_dark.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn one_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "One Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x464b57ff).into(), - border_variant: rgba(0x464b57ff).into(), - border_focused: rgba(0x293b5bff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3b414dff).into(), - surface: rgba(0x2f343eff).into(), - background: rgba(0x3b414dff).into(), - filled_element: rgba(0x3b414dff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x18243dff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x18243dff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xc8ccd4ff).into(), - text_muted: rgba(0x838994ff).into(), - text_placeholder: rgba(0xd07277ff).into(), - text_disabled: rgba(0x555a63ff).into(), - text_accent: rgba(0x74ade8ff).into(), - icon_muted: rgba(0x838994ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("keyword".into(), rgba(0xb477cfff).into()), - ("comment.doc".into(), rgba(0x878e98ff).into()), - ("variant".into(), rgba(0x73ade9ff).into()), - ("property".into(), rgba(0xd07277ff).into()), - ("function".into(), rgba(0x73ade9ff).into()), - ("type".into(), rgba(0x6eb4bfff).into()), - ("tag".into(), rgba(0x74ade8ff).into()), - ("string.escape".into(), rgba(0x878e98ff).into()), - ("punctuation.bracket".into(), rgba(0xb2b9c6ff).into()), - ("hint".into(), rgba(0x5a6f89ff).into()), - ("punctuation".into(), rgba(0xacb2beff).into()), - ("comment".into(), rgba(0x5d636fff).into()), - ("emphasis".into(), rgba(0x74ade8ff).into()), - ("punctuation.special".into(), rgba(0xb1574bff).into()), - ("link_uri".into(), rgba(0x6eb4bfff).into()), - ("string.regex".into(), rgba(0xbf956aff).into()), - ("constructor".into(), rgba(0x73ade9ff).into()), - ("operator".into(), rgba(0x6eb4bfff).into()), - ("constant".into(), rgba(0xdfc184ff).into()), - ("string.special".into(), rgba(0xbf956aff).into()), - ("emphasis.strong".into(), rgba(0xbf956aff).into()), - ("string.special.symbol".into(), rgba(0xbf956aff).into()), - ("primary".into(), rgba(0xacb2beff).into()), - ("preproc".into(), rgba(0xc8ccd4ff).into()), - ("string".into(), rgba(0xa1c181ff).into()), - ("punctuation.delimiter".into(), rgba(0xb2b9c6ff).into()), - ("embedded".into(), rgba(0xc8ccd4ff).into()), - ("enum".into(), rgba(0xd07277ff).into()), - ("variable.special".into(), rgba(0xbf956aff).into()), - ("text.literal".into(), rgba(0xa1c181ff).into()), - ("attribute".into(), rgba(0x74ade8ff).into()), - ("link_text".into(), rgba(0x73ade9ff).into()), - ("title".into(), rgba(0xd07277ff).into()), - ("predictive".into(), rgba(0x5a6a87ff).into()), - ("number".into(), rgba(0xbf956aff).into()), - ("label".into(), rgba(0x74ade8ff).into()), - ("variable".into(), rgba(0xc8ccd4ff).into()), - ("boolean".into(), rgba(0xbf956aff).into()), - ("punctuation.list_marker".into(), rgba(0xd07277ff).into()), - ], - }, - status_bar: rgba(0x3b414dff).into(), - title_bar: rgba(0x3b414dff).into(), - toolbar: rgba(0x282c33ff).into(), - tab_bar: rgba(0x2f343eff).into(), - editor: rgba(0x282c33ff).into(), - editor_subheader: rgba(0x2f343eff).into(), - editor_active_line: rgba(0x2f343eff).into(), - terminal: rgba(0x282c33ff).into(), - image_fallback_background: rgba(0x3b414dff).into(), - git_created: rgba(0xa1c181ff).into(), - git_modified: rgba(0x74ade8ff).into(), - git_deleted: rgba(0xd07277ff).into(), - git_conflict: rgba(0xdec184ff).into(), - git_ignored: rgba(0x555a63ff).into(), - git_renamed: rgba(0xdec184ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x74ade8ff).into(), - selection: rgba(0x74ade83d).into(), - }, - PlayerTheme { - cursor: rgba(0xa1c181ff).into(), - selection: rgba(0xa1c1813d).into(), - }, - PlayerTheme { - cursor: rgba(0xbe5046ff).into(), - selection: rgba(0xbe50463d).into(), - }, - PlayerTheme { - cursor: rgba(0xbf956aff).into(), - selection: rgba(0xbf956a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xb477cfff).into(), - selection: rgba(0xb477cf3d).into(), - }, - PlayerTheme { - cursor: rgba(0x6eb4bfff).into(), - selection: rgba(0x6eb4bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd07277ff).into(), - selection: rgba(0xd072773d).into(), - }, - PlayerTheme { - cursor: rgba(0xdec184ff).into(), - selection: rgba(0xdec1843d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/one_light.rs b/crates/theme2/src/themes/one_light.rs deleted file mode 100644 index ee802d57d3..0000000000 --- a/crates/theme2/src/themes/one_light.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn one_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "One Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc9c9caff).into(), - border_variant: rgba(0xc9c9caff).into(), - border_focused: rgba(0xcbcdf6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xdcdcddff).into(), - surface: rgba(0xebebecff).into(), - background: rgba(0xdcdcddff).into(), - filled_element: rgba(0xdcdcddff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe2e2faff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe2e2faff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x383a41ff).into(), - text_muted: rgba(0x7e8087ff).into(), - text_placeholder: rgba(0xd36151ff).into(), - text_disabled: rgba(0xa1a1a3ff).into(), - text_accent: rgba(0x5c78e2ff).into(), - icon_muted: rgba(0x7e8087ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special.symbol".into(), rgba(0xad6e26ff).into()), - ("hint".into(), rgba(0x9294beff).into()), - ("link_uri".into(), rgba(0x3882b7ff).into()), - ("type".into(), rgba(0x3882b7ff).into()), - ("string.regex".into(), rgba(0xad6e26ff).into()), - ("constant".into(), rgba(0x669f59ff).into()), - ("function".into(), rgba(0x5b79e3ff).into()), - ("string.special".into(), rgba(0xad6e26ff).into()), - ("punctuation.bracket".into(), rgba(0x4d4f52ff).into()), - ("variable".into(), rgba(0x383a41ff).into()), - ("punctuation".into(), rgba(0x383a41ff).into()), - ("property".into(), rgba(0xd3604fff).into()), - ("string".into(), rgba(0x649f57ff).into()), - ("predictive".into(), rgba(0x9b9ec6ff).into()), - ("attribute".into(), rgba(0x5c78e2ff).into()), - ("number".into(), rgba(0xad6e25ff).into()), - ("constructor".into(), rgba(0x5c78e2ff).into()), - ("embedded".into(), rgba(0x383a41ff).into()), - ("title".into(), rgba(0xd3604fff).into()), - ("tag".into(), rgba(0x5c78e2ff).into()), - ("boolean".into(), rgba(0xad6e25ff).into()), - ("punctuation.list_marker".into(), rgba(0xd3604fff).into()), - ("variant".into(), rgba(0x5b79e3ff).into()), - ("emphasis".into(), rgba(0x5c78e2ff).into()), - ("link_text".into(), rgba(0x5b79e3ff).into()), - ("comment".into(), rgba(0xa2a3a7ff).into()), - ("punctuation.special".into(), rgba(0xb92b46ff).into()), - ("emphasis.strong".into(), rgba(0xad6e25ff).into()), - ("primary".into(), rgba(0x383a41ff).into()), - ("punctuation.delimiter".into(), rgba(0x4d4f52ff).into()), - ("label".into(), rgba(0x5c78e2ff).into()), - ("keyword".into(), rgba(0xa449abff).into()), - ("string.escape".into(), rgba(0x7c7e86ff).into()), - ("text.literal".into(), rgba(0x649f57ff).into()), - ("variable.special".into(), rgba(0xad6e25ff).into()), - ("comment.doc".into(), rgba(0x7c7e86ff).into()), - ("enum".into(), rgba(0xd3604fff).into()), - ("operator".into(), rgba(0x3882b7ff).into()), - ("preproc".into(), rgba(0x383a41ff).into()), - ], - }, - status_bar: rgba(0xdcdcddff).into(), - title_bar: rgba(0xdcdcddff).into(), - toolbar: rgba(0xfafafaff).into(), - tab_bar: rgba(0xebebecff).into(), - editor: rgba(0xfafafaff).into(), - editor_subheader: rgba(0xebebecff).into(), - editor_active_line: rgba(0xebebecff).into(), - terminal: rgba(0xfafafaff).into(), - image_fallback_background: rgba(0xdcdcddff).into(), - git_created: rgba(0x669f59ff).into(), - git_modified: rgba(0x5c78e2ff).into(), - git_deleted: rgba(0xd36151ff).into(), - git_conflict: rgba(0xdec184ff).into(), - git_ignored: rgba(0xa1a1a3ff).into(), - git_renamed: rgba(0xdec184ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5c78e2ff).into(), - selection: rgba(0x5c78e23d).into(), - }, - PlayerTheme { - cursor: rgba(0x669f59ff).into(), - selection: rgba(0x669f593d).into(), - }, - PlayerTheme { - cursor: rgba(0x984ea5ff).into(), - selection: rgba(0x984ea53d).into(), - }, - PlayerTheme { - cursor: rgba(0xad6e26ff).into(), - selection: rgba(0xad6e263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa349abff).into(), - selection: rgba(0xa349ab3d).into(), - }, - PlayerTheme { - cursor: rgba(0x3a82b7ff).into(), - selection: rgba(0x3a82b73d).into(), - }, - PlayerTheme { - cursor: rgba(0xd36151ff).into(), - selection: rgba(0xd361513d).into(), - }, - PlayerTheme { - cursor: rgba(0xdec184ff).into(), - selection: rgba(0xdec1843d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/rose_pine.rs b/crates/theme2/src/themes/rose_pine.rs deleted file mode 100644 index f3bd454cdc..0000000000 --- a/crates/theme2/src/themes/rose_pine.rs +++ /dev/null @@ -1,132 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn rose_pine() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Rosé Pine".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x423f55ff).into(), - border_variant: rgba(0x423f55ff).into(), - border_focused: rgba(0x435255ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x292738ff).into(), - surface: rgba(0x1c1b2aff).into(), - background: rgba(0x292738ff).into(), - filled_element: rgba(0x292738ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x2f3639ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x2f3639ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xe0def4ff).into(), - text_muted: rgba(0x74708dff).into(), - text_placeholder: rgba(0xea6e92ff).into(), - text_disabled: rgba(0x2f2b43ff).into(), - text_accent: rgba(0x9bced6ff).into(), - icon_muted: rgba(0x74708dff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.delimiter".into(), rgba(0x9d99b6ff).into()), - ("number".into(), rgba(0x5cc1a3ff).into()), - ("punctuation.special".into(), rgba(0x9d99b6ff).into()), - ("string.escape".into(), rgba(0x76728fff).into()), - ("title".into(), rgba(0xf5c177ff).into()), - ("constant".into(), rgba(0x5cc1a3ff).into()), - ("string.regex".into(), rgba(0xc4a7e6ff).into()), - ("type.builtin".into(), rgba(0x9ccfd8ff).into()), - ("comment.doc".into(), rgba(0x76728fff).into()), - ("primary".into(), rgba(0xe0def4ff).into()), - ("string.special".into(), rgba(0xc4a7e6ff).into()), - ("punctuation".into(), rgba(0x908caaff).into()), - ("string.special.symbol".into(), rgba(0xc4a7e6ff).into()), - ("variant".into(), rgba(0x9bced6ff).into()), - ("function.method".into(), rgba(0xebbcbaff).into()), - ("comment".into(), rgba(0x6e6a86ff).into()), - ("boolean".into(), rgba(0xebbcbaff).into()), - ("preproc".into(), rgba(0xe0def4ff).into()), - ("link_uri".into(), rgba(0xebbcbaff).into()), - ("hint".into(), rgba(0x5e768cff).into()), - ("attribute".into(), rgba(0x9bced6ff).into()), - ("text.literal".into(), rgba(0xc4a7e6ff).into()), - ("punctuation.list_marker".into(), rgba(0x9d99b6ff).into()), - ("operator".into(), rgba(0x30738fff).into()), - ("emphasis.strong".into(), rgba(0x9bced6ff).into()), - ("keyword".into(), rgba(0x30738fff).into()), - ("enum".into(), rgba(0xc4a7e6ff).into()), - ("tag".into(), rgba(0x9ccfd8ff).into()), - ("constructor".into(), rgba(0x9bced6ff).into()), - ("function".into(), rgba(0xebbcbaff).into()), - ("string".into(), rgba(0xf5c177ff).into()), - ("type".into(), rgba(0x9ccfd8ff).into()), - ("emphasis".into(), rgba(0x9bced6ff).into()), - ("link_text".into(), rgba(0x9ccfd8ff).into()), - ("property".into(), rgba(0x9bced6ff).into()), - ("predictive".into(), rgba(0x556b81ff).into()), - ("punctuation.bracket".into(), rgba(0x9d99b6ff).into()), - ("embedded".into(), rgba(0xe0def4ff).into()), - ("variable".into(), rgba(0xe0def4ff).into()), - ("label".into(), rgba(0x9bced6ff).into()), - ], - }, - status_bar: rgba(0x292738ff).into(), - title_bar: rgba(0x292738ff).into(), - toolbar: rgba(0x191724ff).into(), - tab_bar: rgba(0x1c1b2aff).into(), - editor: rgba(0x191724ff).into(), - editor_subheader: rgba(0x1c1b2aff).into(), - editor_active_line: rgba(0x1c1b2aff).into(), - terminal: rgba(0x191724ff).into(), - image_fallback_background: rgba(0x292738ff).into(), - git_created: rgba(0x5cc1a3ff).into(), - git_modified: rgba(0x9bced6ff).into(), - git_deleted: rgba(0xea6e92ff).into(), - git_conflict: rgba(0xf5c177ff).into(), - git_ignored: rgba(0x2f2b43ff).into(), - git_renamed: rgba(0xf5c177ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x9bced6ff).into(), - selection: rgba(0x9bced63d).into(), - }, - PlayerTheme { - cursor: rgba(0x5cc1a3ff).into(), - selection: rgba(0x5cc1a33d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d7591ff).into(), - selection: rgba(0x9d75913d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0x31738fff).into(), - selection: rgba(0x31738f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xea6e92ff).into(), - selection: rgba(0xea6e923d).into(), - }, - PlayerTheme { - cursor: rgba(0xf5c177ff).into(), - selection: rgba(0xf5c1773d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/rose_pine_dawn.rs b/crates/theme2/src/themes/rose_pine_dawn.rs deleted file mode 100644 index ba64bf9d99..0000000000 --- a/crates/theme2/src/themes/rose_pine_dawn.rs +++ /dev/null @@ -1,132 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn rose_pine_dawn() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Rosé Pine Dawn".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xdcd6d5ff).into(), - border_variant: rgba(0xdcd6d5ff).into(), - border_focused: rgba(0xc3d7dbff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xdcd8d8ff).into(), - surface: rgba(0xfef9f2ff).into(), - background: rgba(0xdcd8d8ff).into(), - filled_element: rgba(0xdcd8d8ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdde9ebff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdde9ebff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x575279ff).into(), - text_muted: rgba(0x706c8cff).into(), - text_placeholder: rgba(0xb4647aff).into(), - text_disabled: rgba(0x938fa3ff).into(), - text_accent: rgba(0x57949fff).into(), - icon_muted: rgba(0x706c8cff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("primary".into(), rgba(0x575279ff).into()), - ("attribute".into(), rgba(0x57949fff).into()), - ("operator".into(), rgba(0x276983ff).into()), - ("boolean".into(), rgba(0xd7827dff).into()), - ("tag".into(), rgba(0x55949fff).into()), - ("enum".into(), rgba(0x9079a9ff).into()), - ("embedded".into(), rgba(0x575279ff).into()), - ("label".into(), rgba(0x57949fff).into()), - ("function.method".into(), rgba(0xd7827dff).into()), - ("punctuation.list_marker".into(), rgba(0x635e82ff).into()), - ("punctuation.delimiter".into(), rgba(0x635e82ff).into()), - ("string".into(), rgba(0xea9d34ff).into()), - ("type".into(), rgba(0x55949fff).into()), - ("string.regex".into(), rgba(0x9079a9ff).into()), - ("variable".into(), rgba(0x575279ff).into()), - ("constructor".into(), rgba(0x57949fff).into()), - ("punctuation.bracket".into(), rgba(0x635e82ff).into()), - ("emphasis".into(), rgba(0x57949fff).into()), - ("comment.doc".into(), rgba(0x6e6a8bff).into()), - ("comment".into(), rgba(0x9893a5ff).into()), - ("keyword".into(), rgba(0x276983ff).into()), - ("preproc".into(), rgba(0x575279ff).into()), - ("string.special".into(), rgba(0x9079a9ff).into()), - ("string.escape".into(), rgba(0x6e6a8bff).into()), - ("constant".into(), rgba(0x3daa8eff).into()), - ("property".into(), rgba(0x57949fff).into()), - ("punctuation.special".into(), rgba(0x635e82ff).into()), - ("text.literal".into(), rgba(0x9079a9ff).into()), - ("type.builtin".into(), rgba(0x55949fff).into()), - ("string.special.symbol".into(), rgba(0x9079a9ff).into()), - ("link_uri".into(), rgba(0xd7827dff).into()), - ("number".into(), rgba(0x3daa8eff).into()), - ("emphasis.strong".into(), rgba(0x57949fff).into()), - ("function".into(), rgba(0xd7827dff).into()), - ("title".into(), rgba(0xea9d34ff).into()), - ("punctuation".into(), rgba(0x797593ff).into()), - ("link_text".into(), rgba(0x55949fff).into()), - ("variant".into(), rgba(0x57949fff).into()), - ("predictive".into(), rgba(0xa2acbeff).into()), - ("hint".into(), rgba(0x7a92aaff).into()), - ], - }, - status_bar: rgba(0xdcd8d8ff).into(), - title_bar: rgba(0xdcd8d8ff).into(), - toolbar: rgba(0xfaf4edff).into(), - tab_bar: rgba(0xfef9f2ff).into(), - editor: rgba(0xfaf4edff).into(), - editor_subheader: rgba(0xfef9f2ff).into(), - editor_active_line: rgba(0xfef9f2ff).into(), - terminal: rgba(0xfaf4edff).into(), - image_fallback_background: rgba(0xdcd8d8ff).into(), - git_created: rgba(0x3daa8eff).into(), - git_modified: rgba(0x57949fff).into(), - git_deleted: rgba(0xb4647aff).into(), - git_conflict: rgba(0xe99d35ff).into(), - git_ignored: rgba(0x938fa3ff).into(), - git_renamed: rgba(0xe99d35ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x57949fff).into(), - selection: rgba(0x57949f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x3daa8eff).into(), - selection: rgba(0x3daa8e3d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c697fff).into(), - selection: rgba(0x7c697f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x9079a9ff).into(), - selection: rgba(0x9079a93d).into(), - }, - PlayerTheme { - cursor: rgba(0x9079a9ff).into(), - selection: rgba(0x9079a93d).into(), - }, - PlayerTheme { - cursor: rgba(0x296983ff).into(), - selection: rgba(0x2969833d).into(), - }, - PlayerTheme { - cursor: rgba(0xb4647aff).into(), - selection: rgba(0xb4647a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xe99d35ff).into(), - selection: rgba(0xe99d353d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/rose_pine_moon.rs b/crates/theme2/src/themes/rose_pine_moon.rs deleted file mode 100644 index 167b78afb5..0000000000 --- a/crates/theme2/src/themes/rose_pine_moon.rs +++ /dev/null @@ -1,132 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn rose_pine_moon() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Rosé Pine Moon".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x504c68ff).into(), - border_variant: rgba(0x504c68ff).into(), - border_focused: rgba(0x435255ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x38354eff).into(), - surface: rgba(0x28253cff).into(), - background: rgba(0x38354eff).into(), - filled_element: rgba(0x38354eff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x2f3639ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x2f3639ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xe0def4ff).into(), - text_muted: rgba(0x85819eff).into(), - text_placeholder: rgba(0xea6e92ff).into(), - text_disabled: rgba(0x605d7aff).into(), - text_accent: rgba(0x9bced6ff).into(), - icon_muted: rgba(0x85819eff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("type.builtin".into(), rgba(0x9ccfd8ff).into()), - ("variable".into(), rgba(0xe0def4ff).into()), - ("punctuation".into(), rgba(0x908caaff).into()), - ("number".into(), rgba(0x5cc1a3ff).into()), - ("comment".into(), rgba(0x6e6a86ff).into()), - ("string.special".into(), rgba(0xc4a7e6ff).into()), - ("string.escape".into(), rgba(0x8682a0ff).into()), - ("function.method".into(), rgba(0xea9a97ff).into()), - ("predictive".into(), rgba(0x516b83ff).into()), - ("punctuation.delimiter".into(), rgba(0xaeabc6ff).into()), - ("primary".into(), rgba(0xe0def4ff).into()), - ("link_text".into(), rgba(0x9ccfd8ff).into()), - ("string.regex".into(), rgba(0xc4a7e6ff).into()), - ("constructor".into(), rgba(0x9bced6ff).into()), - ("constant".into(), rgba(0x5cc1a3ff).into()), - ("emphasis.strong".into(), rgba(0x9bced6ff).into()), - ("function".into(), rgba(0xea9a97ff).into()), - ("hint".into(), rgba(0x728aa2ff).into()), - ("preproc".into(), rgba(0xe0def4ff).into()), - ("property".into(), rgba(0x9bced6ff).into()), - ("punctuation.list_marker".into(), rgba(0xaeabc6ff).into()), - ("emphasis".into(), rgba(0x9bced6ff).into()), - ("attribute".into(), rgba(0x9bced6ff).into()), - ("title".into(), rgba(0xf5c177ff).into()), - ("keyword".into(), rgba(0x3d8fb0ff).into()), - ("string".into(), rgba(0xf5c177ff).into()), - ("text.literal".into(), rgba(0xc4a7e6ff).into()), - ("embedded".into(), rgba(0xe0def4ff).into()), - ("comment.doc".into(), rgba(0x8682a0ff).into()), - ("variant".into(), rgba(0x9bced6ff).into()), - ("label".into(), rgba(0x9bced6ff).into()), - ("punctuation.special".into(), rgba(0xaeabc6ff).into()), - ("string.special.symbol".into(), rgba(0xc4a7e6ff).into()), - ("tag".into(), rgba(0x9ccfd8ff).into()), - ("enum".into(), rgba(0xc4a7e6ff).into()), - ("boolean".into(), rgba(0xea9a97ff).into()), - ("punctuation.bracket".into(), rgba(0xaeabc6ff).into()), - ("operator".into(), rgba(0x3d8fb0ff).into()), - ("type".into(), rgba(0x9ccfd8ff).into()), - ("link_uri".into(), rgba(0xea9a97ff).into()), - ], - }, - status_bar: rgba(0x38354eff).into(), - title_bar: rgba(0x38354eff).into(), - toolbar: rgba(0x232136ff).into(), - tab_bar: rgba(0x28253cff).into(), - editor: rgba(0x232136ff).into(), - editor_subheader: rgba(0x28253cff).into(), - editor_active_line: rgba(0x28253cff).into(), - terminal: rgba(0x232136ff).into(), - image_fallback_background: rgba(0x38354eff).into(), - git_created: rgba(0x5cc1a3ff).into(), - git_modified: rgba(0x9bced6ff).into(), - git_deleted: rgba(0xea6e92ff).into(), - git_conflict: rgba(0xf5c177ff).into(), - git_ignored: rgba(0x605d7aff).into(), - git_renamed: rgba(0xf5c177ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x9bced6ff).into(), - selection: rgba(0x9bced63d).into(), - }, - PlayerTheme { - cursor: rgba(0x5cc1a3ff).into(), - selection: rgba(0x5cc1a33d).into(), - }, - PlayerTheme { - cursor: rgba(0xa683a0ff).into(), - selection: rgba(0xa683a03d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0x3e8fb0ff).into(), - selection: rgba(0x3e8fb03d).into(), - }, - PlayerTheme { - cursor: rgba(0xea6e92ff).into(), - selection: rgba(0xea6e923d).into(), - }, - PlayerTheme { - cursor: rgba(0xf5c177ff).into(), - selection: rgba(0xf5c1773d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/sandcastle.rs b/crates/theme2/src/themes/sandcastle.rs deleted file mode 100644 index 7fa0a27fb3..0000000000 --- a/crates/theme2/src/themes/sandcastle.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn sandcastle() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Sandcastle".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x3d4350ff).into(), - border_variant: rgba(0x3d4350ff).into(), - border_focused: rgba(0x223131ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x333944ff).into(), - surface: rgba(0x2b3038ff).into(), - background: rgba(0x333944ff).into(), - filled_element: rgba(0x333944ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x171e1eff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x171e1eff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfdf4c1ff).into(), - text_muted: rgba(0xa69782ff).into(), - text_placeholder: rgba(0xb3627aff).into(), - text_disabled: rgba(0x827568ff).into(), - text_accent: rgba(0x518b8bff).into(), - icon_muted: rgba(0xa69782ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("comment".into(), rgba(0xa89984ff).into()), - ("type".into(), rgba(0x83a598ff).into()), - ("preproc".into(), rgba(0xfdf4c1ff).into()), - ("punctuation.bracket".into(), rgba(0xd5c5a1ff).into()), - ("hint".into(), rgba(0x727d68ff).into()), - ("link_uri".into(), rgba(0x83a598ff).into()), - ("text.literal".into(), rgba(0xa07d3aff).into()), - ("enum".into(), rgba(0xa07d3aff).into()), - ("string.special".into(), rgba(0xa07d3aff).into()), - ("string".into(), rgba(0xa07d3aff).into()), - ("punctuation.special".into(), rgba(0xd5c5a1ff).into()), - ("keyword".into(), rgba(0x518b8bff).into()), - ("constructor".into(), rgba(0x518b8bff).into()), - ("predictive".into(), rgba(0x5c6152ff).into()), - ("title".into(), rgba(0xfdf4c1ff).into()), - ("variable".into(), rgba(0xfdf4c1ff).into()), - ("emphasis.strong".into(), rgba(0x518b8bff).into()), - ("primary".into(), rgba(0xfdf4c1ff).into()), - ("emphasis".into(), rgba(0x518b8bff).into()), - ("punctuation".into(), rgba(0xd5c5a1ff).into()), - ("constant".into(), rgba(0x83a598ff).into()), - ("link_text".into(), rgba(0xa07d3aff).into()), - ("punctuation.delimiter".into(), rgba(0xd5c5a1ff).into()), - ("embedded".into(), rgba(0xfdf4c1ff).into()), - ("string.special.symbol".into(), rgba(0xa07d3aff).into()), - ("tag".into(), rgba(0x518b8bff).into()), - ("punctuation.list_marker".into(), rgba(0xd5c5a1ff).into()), - ("operator".into(), rgba(0xa07d3aff).into()), - ("boolean".into(), rgba(0x83a598ff).into()), - ("function".into(), rgba(0xa07d3aff).into()), - ("attribute".into(), rgba(0x518b8bff).into()), - ("number".into(), rgba(0x83a598ff).into()), - ("string.escape".into(), rgba(0xa89984ff).into()), - ("comment.doc".into(), rgba(0xa89984ff).into()), - ("label".into(), rgba(0x518b8bff).into()), - ("string.regex".into(), rgba(0xa07d3aff).into()), - ("property".into(), rgba(0x518b8bff).into()), - ("variant".into(), rgba(0x518b8bff).into()), - ], - }, - status_bar: rgba(0x333944ff).into(), - title_bar: rgba(0x333944ff).into(), - toolbar: rgba(0x282c33ff).into(), - tab_bar: rgba(0x2b3038ff).into(), - editor: rgba(0x282c33ff).into(), - editor_subheader: rgba(0x2b3038ff).into(), - editor_active_line: rgba(0x2b3038ff).into(), - terminal: rgba(0x282c33ff).into(), - image_fallback_background: rgba(0x333944ff).into(), - git_created: rgba(0x83a598ff).into(), - git_modified: rgba(0x518b8bff).into(), - git_deleted: rgba(0xb3627aff).into(), - git_conflict: rgba(0xa07d3aff).into(), - git_ignored: rgba(0x827568ff).into(), - git_renamed: rgba(0xa07d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x518b8bff).into(), - selection: rgba(0x518b8b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xa87222ff).into(), - selection: rgba(0xa872223d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3aff).into(), - selection: rgba(0xa07d3a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd75f5fff).into(), - selection: rgba(0xd75f5f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb3627aff).into(), - selection: rgba(0xb3627a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3aff).into(), - selection: rgba(0xa07d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/solarized_dark.rs b/crates/theme2/src/themes/solarized_dark.rs deleted file mode 100644 index 2e381a6e95..0000000000 --- a/crates/theme2/src/themes/solarized_dark.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn solarized_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Solarized Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x2b4e58ff).into(), - border_variant: rgba(0x2b4e58ff).into(), - border_focused: rgba(0x1b3149ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x073743ff).into(), - surface: rgba(0x04313bff).into(), - background: rgba(0x073743ff).into(), - filled_element: rgba(0x073743ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x141f2cff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x141f2cff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfdf6e3ff).into(), - text_muted: rgba(0x93a1a1ff).into(), - text_placeholder: rgba(0xdc3330ff).into(), - text_disabled: rgba(0x6f8389ff).into(), - text_accent: rgba(0x278ad1ff).into(), - icon_muted: rgba(0x93a1a1ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.special".into(), rgba(0xefe9d6ff).into()), - ("string".into(), rgba(0xcb4b16ff).into()), - ("variant".into(), rgba(0x278ad1ff).into()), - ("variable".into(), rgba(0xfdf6e3ff).into()), - ("string.special.symbol".into(), rgba(0xcb4b16ff).into()), - ("primary".into(), rgba(0xfdf6e3ff).into()), - ("type".into(), rgba(0x2ba198ff).into()), - ("boolean".into(), rgba(0x849903ff).into()), - ("string.special".into(), rgba(0xcb4b16ff).into()), - ("label".into(), rgba(0x278ad1ff).into()), - ("link_uri".into(), rgba(0x849903ff).into()), - ("constructor".into(), rgba(0x278ad1ff).into()), - ("hint".into(), rgba(0x4f8297ff).into()), - ("preproc".into(), rgba(0xfdf6e3ff).into()), - ("text.literal".into(), rgba(0xcb4b16ff).into()), - ("string.escape".into(), rgba(0x99a5a4ff).into()), - ("link_text".into(), rgba(0xcb4b16ff).into()), - ("comment".into(), rgba(0x99a5a4ff).into()), - ("enum".into(), rgba(0xcb4b16ff).into()), - ("constant".into(), rgba(0x849903ff).into()), - ("comment.doc".into(), rgba(0x99a5a4ff).into()), - ("emphasis".into(), rgba(0x278ad1ff).into()), - ("predictive".into(), rgba(0x3f718bff).into()), - ("attribute".into(), rgba(0x278ad1ff).into()), - ("punctuation.delimiter".into(), rgba(0xefe9d6ff).into()), - ("function".into(), rgba(0xb58902ff).into()), - ("emphasis.strong".into(), rgba(0x278ad1ff).into()), - ("tag".into(), rgba(0x278ad1ff).into()), - ("string.regex".into(), rgba(0xcb4b16ff).into()), - ("property".into(), rgba(0x278ad1ff).into()), - ("keyword".into(), rgba(0x278ad1ff).into()), - ("number".into(), rgba(0x849903ff).into()), - ("embedded".into(), rgba(0xfdf6e3ff).into()), - ("operator".into(), rgba(0xcb4b16ff).into()), - ("punctuation".into(), rgba(0xefe9d6ff).into()), - ("punctuation.bracket".into(), rgba(0xefe9d6ff).into()), - ("title".into(), rgba(0xfdf6e3ff).into()), - ("punctuation.list_marker".into(), rgba(0xefe9d6ff).into()), - ], - }, - status_bar: rgba(0x073743ff).into(), - title_bar: rgba(0x073743ff).into(), - toolbar: rgba(0x002a35ff).into(), - tab_bar: rgba(0x04313bff).into(), - editor: rgba(0x002a35ff).into(), - editor_subheader: rgba(0x04313bff).into(), - editor_active_line: rgba(0x04313bff).into(), - terminal: rgba(0x002a35ff).into(), - image_fallback_background: rgba(0x073743ff).into(), - git_created: rgba(0x849903ff).into(), - git_modified: rgba(0x278ad1ff).into(), - git_deleted: rgba(0xdc3330ff).into(), - git_conflict: rgba(0xb58902ff).into(), - git_ignored: rgba(0x6f8389ff).into(), - git_renamed: rgba(0xb58902ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x278ad1ff).into(), - selection: rgba(0x278ad13d).into(), - }, - PlayerTheme { - cursor: rgba(0x849903ff).into(), - selection: rgba(0x8499033d).into(), - }, - PlayerTheme { - cursor: rgba(0xd33781ff).into(), - selection: rgba(0xd337813d).into(), - }, - PlayerTheme { - cursor: rgba(0xcb4b16ff).into(), - selection: rgba(0xcb4b163d).into(), - }, - PlayerTheme { - cursor: rgba(0x6c71c4ff).into(), - selection: rgba(0x6c71c43d).into(), - }, - PlayerTheme { - cursor: rgba(0x2ba198ff).into(), - selection: rgba(0x2ba1983d).into(), - }, - PlayerTheme { - cursor: rgba(0xdc3330ff).into(), - selection: rgba(0xdc33303d).into(), - }, - PlayerTheme { - cursor: rgba(0xb58902ff).into(), - selection: rgba(0xb589023d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/solarized_light.rs b/crates/theme2/src/themes/solarized_light.rs deleted file mode 100644 index a959a0a9d1..0000000000 --- a/crates/theme2/src/themes/solarized_light.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn solarized_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Solarized Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x9faaa8ff).into(), - border_variant: rgba(0x9faaa8ff).into(), - border_focused: rgba(0xbfd3efff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xcfd0c4ff).into(), - surface: rgba(0xf3eddaff).into(), - background: rgba(0xcfd0c4ff).into(), - filled_element: rgba(0xcfd0c4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdbe6f6ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdbe6f6ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x002a35ff).into(), - text_muted: rgba(0x34555eff).into(), - text_placeholder: rgba(0xdc3330ff).into(), - text_disabled: rgba(0x6a7f86ff).into(), - text_accent: rgba(0x288bd1ff).into(), - icon_muted: rgba(0x34555eff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.escape".into(), rgba(0x30525bff).into()), - ("boolean".into(), rgba(0x849903ff).into()), - ("comment.doc".into(), rgba(0x30525bff).into()), - ("string.special".into(), rgba(0xcb4b17ff).into()), - ("punctuation".into(), rgba(0x04333eff).into()), - ("emphasis".into(), rgba(0x288bd1ff).into()), - ("type".into(), rgba(0x2ba198ff).into()), - ("preproc".into(), rgba(0x002a35ff).into()), - ("emphasis.strong".into(), rgba(0x288bd1ff).into()), - ("constant".into(), rgba(0x849903ff).into()), - ("title".into(), rgba(0x002a35ff).into()), - ("operator".into(), rgba(0xcb4b17ff).into()), - ("punctuation.bracket".into(), rgba(0x04333eff).into()), - ("link_uri".into(), rgba(0x849903ff).into()), - ("label".into(), rgba(0x288bd1ff).into()), - ("enum".into(), rgba(0xcb4b17ff).into()), - ("property".into(), rgba(0x288bd1ff).into()), - ("predictive".into(), rgba(0x679aafff).into()), - ("punctuation.special".into(), rgba(0x04333eff).into()), - ("text.literal".into(), rgba(0xcb4b17ff).into()), - ("string".into(), rgba(0xcb4b17ff).into()), - ("string.regex".into(), rgba(0xcb4b17ff).into()), - ("variable".into(), rgba(0x002a35ff).into()), - ("tag".into(), rgba(0x288bd1ff).into()), - ("string.special.symbol".into(), rgba(0xcb4b17ff).into()), - ("link_text".into(), rgba(0xcb4b17ff).into()), - ("punctuation.list_marker".into(), rgba(0x04333eff).into()), - ("keyword".into(), rgba(0x288bd1ff).into()), - ("constructor".into(), rgba(0x288bd1ff).into()), - ("attribute".into(), rgba(0x288bd1ff).into()), - ("variant".into(), rgba(0x288bd1ff).into()), - ("function".into(), rgba(0xb58903ff).into()), - ("primary".into(), rgba(0x002a35ff).into()), - ("hint".into(), rgba(0x5789a3ff).into()), - ("comment".into(), rgba(0x30525bff).into()), - ("number".into(), rgba(0x849903ff).into()), - ("punctuation.delimiter".into(), rgba(0x04333eff).into()), - ("embedded".into(), rgba(0x002a35ff).into()), - ], - }, - status_bar: rgba(0xcfd0c4ff).into(), - title_bar: rgba(0xcfd0c4ff).into(), - toolbar: rgba(0xfdf6e3ff).into(), - tab_bar: rgba(0xf3eddaff).into(), - editor: rgba(0xfdf6e3ff).into(), - editor_subheader: rgba(0xf3eddaff).into(), - editor_active_line: rgba(0xf3eddaff).into(), - terminal: rgba(0xfdf6e3ff).into(), - image_fallback_background: rgba(0xcfd0c4ff).into(), - git_created: rgba(0x849903ff).into(), - git_modified: rgba(0x288bd1ff).into(), - git_deleted: rgba(0xdc3330ff).into(), - git_conflict: rgba(0xb58903ff).into(), - git_ignored: rgba(0x6a7f86ff).into(), - git_renamed: rgba(0xb58903ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x288bd1ff).into(), - selection: rgba(0x288bd13d).into(), - }, - PlayerTheme { - cursor: rgba(0x849903ff).into(), - selection: rgba(0x8499033d).into(), - }, - PlayerTheme { - cursor: rgba(0xd33781ff).into(), - selection: rgba(0xd337813d).into(), - }, - PlayerTheme { - cursor: rgba(0xcb4b17ff).into(), - selection: rgba(0xcb4b173d).into(), - }, - PlayerTheme { - cursor: rgba(0x6c71c3ff).into(), - selection: rgba(0x6c71c33d).into(), - }, - PlayerTheme { - cursor: rgba(0x2ba198ff).into(), - selection: rgba(0x2ba1983d).into(), - }, - PlayerTheme { - cursor: rgba(0xdc3330ff).into(), - selection: rgba(0xdc33303d).into(), - }, - PlayerTheme { - cursor: rgba(0xb58903ff).into(), - selection: rgba(0xb589033d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/summercamp.rs b/crates/theme2/src/themes/summercamp.rs deleted file mode 100644 index c1e66aedd1..0000000000 --- a/crates/theme2/src/themes/summercamp.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn summercamp() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Summercamp".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x302c21ff).into(), - border_variant: rgba(0x302c21ff).into(), - border_focused: rgba(0x193760ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x2a261cff).into(), - surface: rgba(0x231f16ff).into(), - background: rgba(0x2a261cff).into(), - filled_element: rgba(0x2a261cff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0e2242ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0e2242ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf8f5deff).into(), - text_muted: rgba(0x736e55ff).into(), - text_placeholder: rgba(0xe35041ff).into(), - text_disabled: rgba(0x4c4735ff).into(), - text_accent: rgba(0x499befff).into(), - icon_muted: rgba(0x736e55ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("predictive".into(), rgba(0x78434aff).into()), - ("title".into(), rgba(0xf8f5deff).into()), - ("primary".into(), rgba(0xf8f5deff).into()), - ("punctuation.special".into(), rgba(0xbfbb9bff).into()), - ("constant".into(), rgba(0x5dea5aff).into()), - ("string.regex".into(), rgba(0xfaa11cff).into()), - ("tag".into(), rgba(0x499befff).into()), - ("preproc".into(), rgba(0xf8f5deff).into()), - ("comment".into(), rgba(0x777159ff).into()), - ("punctuation.bracket".into(), rgba(0xbfbb9bff).into()), - ("constructor".into(), rgba(0x499befff).into()), - ("type".into(), rgba(0x5aeabbff).into()), - ("variable".into(), rgba(0xf8f5deff).into()), - ("operator".into(), rgba(0xfaa11cff).into()), - ("boolean".into(), rgba(0x5dea5aff).into()), - ("attribute".into(), rgba(0x499befff).into()), - ("link_text".into(), rgba(0xfaa11cff).into()), - ("string.escape".into(), rgba(0x777159ff).into()), - ("string.special".into(), rgba(0xfaa11cff).into()), - ("string.special.symbol".into(), rgba(0xfaa11cff).into()), - ("hint".into(), rgba(0x246e61ff).into()), - ("link_uri".into(), rgba(0x5dea5aff).into()), - ("comment.doc".into(), rgba(0x777159ff).into()), - ("emphasis".into(), rgba(0x499befff).into()), - ("punctuation".into(), rgba(0xbfbb9bff).into()), - ("text.literal".into(), rgba(0xfaa11cff).into()), - ("number".into(), rgba(0x5dea5aff).into()), - ("punctuation.delimiter".into(), rgba(0xbfbb9bff).into()), - ("label".into(), rgba(0x499befff).into()), - ("function".into(), rgba(0xf1fe28ff).into()), - ("property".into(), rgba(0x499befff).into()), - ("keyword".into(), rgba(0x499befff).into()), - ("embedded".into(), rgba(0xf8f5deff).into()), - ("string".into(), rgba(0xfaa11cff).into()), - ("punctuation.list_marker".into(), rgba(0xbfbb9bff).into()), - ("enum".into(), rgba(0xfaa11cff).into()), - ("emphasis.strong".into(), rgba(0x499befff).into()), - ("variant".into(), rgba(0x499befff).into()), - ], - }, - status_bar: rgba(0x2a261cff).into(), - title_bar: rgba(0x2a261cff).into(), - toolbar: rgba(0x1b1810ff).into(), - tab_bar: rgba(0x231f16ff).into(), - editor: rgba(0x1b1810ff).into(), - editor_subheader: rgba(0x231f16ff).into(), - editor_active_line: rgba(0x231f16ff).into(), - terminal: rgba(0x1b1810ff).into(), - image_fallback_background: rgba(0x2a261cff).into(), - git_created: rgba(0x5dea5aff).into(), - git_modified: rgba(0x499befff).into(), - git_deleted: rgba(0xe35041ff).into(), - git_conflict: rgba(0xf1fe28ff).into(), - git_ignored: rgba(0x4c4735ff).into(), - git_renamed: rgba(0xf1fe28ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x499befff).into(), - selection: rgba(0x499bef3d).into(), - }, - PlayerTheme { - cursor: rgba(0x5dea5aff).into(), - selection: rgba(0x5dea5a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xf59be6ff).into(), - selection: rgba(0xf59be63d).into(), - }, - PlayerTheme { - cursor: rgba(0xfaa11cff).into(), - selection: rgba(0xfaa11c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfe8080ff).into(), - selection: rgba(0xfe80803d).into(), - }, - PlayerTheme { - cursor: rgba(0x5aeabbff).into(), - selection: rgba(0x5aeabb3d).into(), - }, - PlayerTheme { - cursor: rgba(0xe35041ff).into(), - selection: rgba(0xe350413d).into(), - }, - PlayerTheme { - cursor: rgba(0xf1fe28ff).into(), - selection: rgba(0xf1fe283d).into(), - }, - ], - } -} diff --git a/crates/theme_converter/Cargo.toml b/crates/theme_converter/Cargo.toml deleted file mode 100644 index 0ec692b7cc..0000000000 --- a/crates/theme_converter/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "theme_converter" -version = "0.1.0" -edition = "2021" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow.workspace = true -clap = { version = "4.4", features = ["derive", "string"] } -convert_case = "0.6.0" -gpui2 = { path = "../gpui2" } -log.workspace = true -rust-embed.workspace = true -serde.workspace = true -simplelog = "0.9" -theme2 = { path = "../theme2" } diff --git a/crates/theme_converter/src/main.rs b/crates/theme_converter/src/main.rs deleted file mode 100644 index cc0cdf9c99..0000000000 --- a/crates/theme_converter/src/main.rs +++ /dev/null @@ -1,390 +0,0 @@ -mod theme_printer; - -use std::borrow::Cow; -use std::collections::HashMap; -use std::fmt::{self, Debug}; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; -use std::str::FromStr; - -use anyhow::{anyhow, Context, Result}; -use clap::Parser; -use convert_case::{Case, Casing}; -use gpui2::{hsla, rgb, serde_json, AssetSource, Hsla, SharedString}; -use log::LevelFilter; -use rust_embed::RustEmbed; -use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; -use simplelog::SimpleLogger; -use theme2::{PlayerTheme, SyntaxTheme}; - -use crate::theme_printer::ThemePrinter; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - /// The name of the theme to convert. - theme: String, -} - -fn main() -> Result<()> { - SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - - // let args = Args::parse(); - - let themes_path = PathBuf::from_str("crates/theme2/src/themes")?; - - let mut theme_modules = Vec::new(); - - for theme_path in Assets.list("themes/")? { - let (_, theme_name) = theme_path.split_once("themes/").unwrap(); - - if theme_name == ".gitkeep" { - continue; - } - - let (json_theme, legacy_theme) = load_theme(&theme_path)?; - - let theme = convert_theme(json_theme, legacy_theme)?; - - let theme_slug = theme - .metadata - .name - .as_ref() - .replace("é", "e") - .to_case(Case::Snake); - - let mut output_file = File::create(themes_path.join(format!("{theme_slug}.rs")))?; - - let theme_module = format!( - r#" - use gpui2::rgba; - - use crate::{{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}}; - - pub fn {theme_slug}() -> Theme {{ - {theme_definition} - }} - "#, - theme_definition = format!("{:#?}", ThemePrinter::new(theme)) - ); - - output_file.write_all(theme_module.as_bytes())?; - - theme_modules.push(theme_slug); - } - - let mut mod_rs_file = File::create(themes_path.join(format!("mod.rs")))?; - - let mod_rs_contents = format!( - r#" - {mod_statements} - - {use_statements} - "#, - mod_statements = theme_modules - .iter() - .map(|module| format!("mod {module};")) - .collect::>() - .join("\n"), - use_statements = theme_modules - .iter() - .map(|module| format!("pub use {module}::*;")) - .collect::>() - .join("\n") - ); - - mod_rs_file.write_all(mod_rs_contents.as_bytes())?; - - Ok(()) -} - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "fonts/**/*"] -#[include = "icons/**/*"] -#[include = "themes/**/*"] -#[include = "sounds/**/*"] -#[include = "*.md"] -#[exclude = "*.DS_Store"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { - Self::get(path) - .map(|f| f.data) - .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) - } - - fn list(&self, path: &str) -> Result> { - Ok(Self::iter() - .filter(|p| p.starts_with(path)) - .map(SharedString::from) - .collect()) - } -} - -#[derive(Clone, Copy)] -pub struct PlayerThemeColors { - pub cursor: Hsla, - pub selection: Hsla, -} - -impl PlayerThemeColors { - pub fn new(theme: &LegacyTheme, ix: usize) -> Self { - if ix < theme.players.len() { - Self { - cursor: theme.players[ix].cursor, - selection: theme.players[ix].selection, - } - } else { - Self { - cursor: rgb::(0xff00ff), - selection: rgb::(0xff00ff), - } - } - } -} - -impl From for PlayerTheme { - fn from(value: PlayerThemeColors) -> Self { - Self { - cursor: value.cursor, - selection: value.selection, - } - } -} - -fn convert_theme(json_theme: JsonTheme, legacy_theme: LegacyTheme) -> Result { - let transparent = hsla(0.0, 0.0, 0.0, 0.0); - - let players: [PlayerTheme; 8] = [ - PlayerThemeColors::new(&legacy_theme, 0).into(), - PlayerThemeColors::new(&legacy_theme, 1).into(), - PlayerThemeColors::new(&legacy_theme, 2).into(), - PlayerThemeColors::new(&legacy_theme, 3).into(), - PlayerThemeColors::new(&legacy_theme, 4).into(), - PlayerThemeColors::new(&legacy_theme, 5).into(), - PlayerThemeColors::new(&legacy_theme, 6).into(), - PlayerThemeColors::new(&legacy_theme, 7).into(), - ]; - - let theme = theme2::Theme { - metadata: theme2::ThemeMetadata { - name: legacy_theme.name.clone().into(), - is_light: legacy_theme.is_light, - }, - transparent, - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - border: legacy_theme.lowest.base.default.border, - border_variant: legacy_theme.lowest.variant.default.border, - border_focused: legacy_theme.lowest.accent.default.border, - border_transparent: transparent, - elevated_surface: legacy_theme.lowest.base.default.background, - surface: legacy_theme.middle.base.default.background, - background: legacy_theme.lowest.base.default.background, - filled_element: legacy_theme.lowest.base.default.background, - filled_element_hover: hsla(0.0, 0.0, 100.0, 0.12), - filled_element_active: hsla(0.0, 0.0, 100.0, 0.16), - filled_element_selected: legacy_theme.lowest.accent.default.background, - filled_element_disabled: transparent, - ghost_element: transparent, - ghost_element_hover: hsla(0.0, 0.0, 100.0, 0.08), - ghost_element_active: hsla(0.0, 0.0, 100.0, 0.12), - ghost_element_selected: legacy_theme.lowest.accent.default.background, - ghost_element_disabled: transparent, - text: legacy_theme.lowest.base.default.foreground, - text_muted: legacy_theme.lowest.variant.default.foreground, - /// TODO: map this to a real value - text_placeholder: legacy_theme.lowest.negative.default.foreground, - text_disabled: legacy_theme.lowest.base.disabled.foreground, - text_accent: legacy_theme.lowest.accent.default.foreground, - icon_muted: legacy_theme.lowest.variant.default.foreground, - syntax: SyntaxTheme { - highlights: json_theme - .editor - .syntax - .iter() - .map(|(token, style)| (token.clone(), style.color.clone().into())) - .collect(), - }, - status_bar: legacy_theme.lowest.base.default.background, - title_bar: legacy_theme.lowest.base.default.background, - toolbar: legacy_theme.highest.base.default.background, - tab_bar: legacy_theme.middle.base.default.background, - editor: legacy_theme.highest.base.default.background, - editor_subheader: legacy_theme.middle.base.default.background, - terminal: legacy_theme.highest.base.default.background, - editor_active_line: legacy_theme.highest.on.default.background, - image_fallback_background: legacy_theme.lowest.base.default.background, - - git_created: legacy_theme.lowest.positive.default.foreground, - git_modified: legacy_theme.lowest.accent.default.foreground, - git_deleted: legacy_theme.lowest.negative.default.foreground, - git_conflict: legacy_theme.lowest.warning.default.foreground, - git_ignored: legacy_theme.lowest.base.disabled.foreground, - git_renamed: legacy_theme.lowest.warning.default.foreground, - - players, - }; - - Ok(theme) -} - -#[derive(Deserialize)] -struct JsonTheme { - pub editor: JsonEditorTheme, - pub base_theme: serde_json::Value, -} - -#[derive(Deserialize)] -struct JsonEditorTheme { - pub syntax: HashMap, -} - -#[derive(Deserialize)] -struct JsonSyntaxStyle { - pub color: Hsla, -} - -/// Loads the [`Theme`] with the given name. -fn load_theme(theme_path: &str) -> Result<(JsonTheme, LegacyTheme)> { - let theme_contents = - Assets::get(theme_path).with_context(|| format!("theme file not found: '{theme_path}'"))?; - - let json_theme: JsonTheme = serde_json::from_str(std::str::from_utf8(&theme_contents.data)?) - .context("failed to parse legacy theme")?; - - let legacy_theme: LegacyTheme = serde_json::from_value(json_theme.base_theme.clone()) - .context("failed to parse `base_theme`")?; - - Ok((json_theme, legacy_theme)) -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct LegacyTheme { - pub name: String, - pub is_light: bool, - pub lowest: Layer, - pub middle: Layer, - pub highest: Layer, - pub popover_shadow: Shadow, - pub modal_shadow: Shadow, - #[serde(deserialize_with = "deserialize_player_colors")] - pub players: Vec, - #[serde(deserialize_with = "deserialize_syntax_colors")] - pub syntax: HashMap, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Layer { - pub base: StyleSet, - pub variant: StyleSet, - pub on: StyleSet, - pub accent: StyleSet, - pub positive: StyleSet, - pub warning: StyleSet, - pub negative: StyleSet, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct StyleSet { - #[serde(rename = "default")] - pub default: ContainerColors, - pub hovered: ContainerColors, - pub pressed: ContainerColors, - pub active: ContainerColors, - pub disabled: ContainerColors, - pub inverted: ContainerColors, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct ContainerColors { - pub background: Hsla, - pub foreground: Hsla, - pub border: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct PlayerColors { - pub selection: Hsla, - pub cursor: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Shadow { - pub blur: u8, - pub color: Hsla, - pub offset: Vec, -} - -fn deserialize_player_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct PlayerArrayVisitor; - - impl<'de> Visitor<'de> for PlayerArrayVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an object with integer keys") - } - - fn visit_map>( - self, - mut map: A, - ) -> Result { - let mut players = Vec::with_capacity(8); - while let Some((key, value)) = map.next_entry::()? { - if key < 8 { - players.push(value); - } else { - return Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(key as u64), - &"a key in range 0..7", - )); - } - } - Ok(players) - } - } - - deserializer.deserialize_map(PlayerArrayVisitor) -} - -fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - struct ColorWrapper { - color: Hsla, - } - - struct SyntaxVisitor; - - impl<'de> Visitor<'de> for SyntaxVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map with keys and objects with a single color field as values") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: serde::de::MapAccess<'de>, - { - let mut result = HashMap::new(); - while let Some(key) = map.next_key()? { - let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla - result.insert(key, wrapper.color); - } - Ok(result) - } - } - deserializer.deserialize_map(SyntaxVisitor) -} diff --git a/crates/theme_converter/src/theme_printer.rs b/crates/theme_converter/src/theme_printer.rs deleted file mode 100644 index 3a9bdb159b..0000000000 --- a/crates/theme_converter/src/theme_printer.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::fmt::{self, Debug}; - -use gpui2::{Hsla, Rgba}; -use theme2::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub struct ThemePrinter(Theme); - -impl ThemePrinter { - pub fn new(theme: Theme) -> Self { - Self(theme) - } -} - -struct HslaPrinter(Hsla); - -impl Debug for HslaPrinter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", IntoPrinter(&Rgba::from(self.0))) - } -} - -struct IntoPrinter<'a, D: Debug>(&'a D); - -impl<'a, D: Debug> Debug for IntoPrinter<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}.into()", self.0) - } -} - -impl Debug for ThemePrinter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Theme") - .field("metadata", &ThemeMetadataPrinter(self.0.metadata.clone())) - .field("transparent", &HslaPrinter(self.0.transparent)) - .field( - "mac_os_traffic_light_red", - &HslaPrinter(self.0.mac_os_traffic_light_red), - ) - .field( - "mac_os_traffic_light_yellow", - &HslaPrinter(self.0.mac_os_traffic_light_yellow), - ) - .field( - "mac_os_traffic_light_green", - &HslaPrinter(self.0.mac_os_traffic_light_green), - ) - .field("border", &HslaPrinter(self.0.border)) - .field("border_variant", &HslaPrinter(self.0.border_variant)) - .field("border_focused", &HslaPrinter(self.0.border_focused)) - .field( - "border_transparent", - &HslaPrinter(self.0.border_transparent), - ) - .field("elevated_surface", &HslaPrinter(self.0.elevated_surface)) - .field("surface", &HslaPrinter(self.0.surface)) - .field("background", &HslaPrinter(self.0.background)) - .field("filled_element", &HslaPrinter(self.0.filled_element)) - .field( - "filled_element_hover", - &HslaPrinter(self.0.filled_element_hover), - ) - .field( - "filled_element_active", - &HslaPrinter(self.0.filled_element_active), - ) - .field( - "filled_element_selected", - &HslaPrinter(self.0.filled_element_selected), - ) - .field( - "filled_element_disabled", - &HslaPrinter(self.0.filled_element_disabled), - ) - .field("ghost_element", &HslaPrinter(self.0.ghost_element)) - .field( - "ghost_element_hover", - &HslaPrinter(self.0.ghost_element_hover), - ) - .field( - "ghost_element_active", - &HslaPrinter(self.0.ghost_element_active), - ) - .field( - "ghost_element_selected", - &HslaPrinter(self.0.ghost_element_selected), - ) - .field( - "ghost_element_disabled", - &HslaPrinter(self.0.ghost_element_disabled), - ) - .field("text", &HslaPrinter(self.0.text)) - .field("text_muted", &HslaPrinter(self.0.text_muted)) - .field("text_placeholder", &HslaPrinter(self.0.text_placeholder)) - .field("text_disabled", &HslaPrinter(self.0.text_disabled)) - .field("text_accent", &HslaPrinter(self.0.text_accent)) - .field("icon_muted", &HslaPrinter(self.0.icon_muted)) - .field("syntax", &SyntaxThemePrinter(self.0.syntax.clone())) - .field("status_bar", &HslaPrinter(self.0.status_bar)) - .field("title_bar", &HslaPrinter(self.0.title_bar)) - .field("toolbar", &HslaPrinter(self.0.toolbar)) - .field("tab_bar", &HslaPrinter(self.0.tab_bar)) - .field("editor", &HslaPrinter(self.0.editor)) - .field("editor_subheader", &HslaPrinter(self.0.editor_subheader)) - .field( - "editor_active_line", - &HslaPrinter(self.0.editor_active_line), - ) - .field("terminal", &HslaPrinter(self.0.terminal)) - .field( - "image_fallback_background", - &HslaPrinter(self.0.image_fallback_background), - ) - .field("git_created", &HslaPrinter(self.0.git_created)) - .field("git_modified", &HslaPrinter(self.0.git_modified)) - .field("git_deleted", &HslaPrinter(self.0.git_deleted)) - .field("git_conflict", &HslaPrinter(self.0.git_conflict)) - .field("git_ignored", &HslaPrinter(self.0.git_ignored)) - .field("git_renamed", &HslaPrinter(self.0.git_renamed)) - .field("players", &self.0.players.map(PlayerThemePrinter)) - .finish() - } -} - -pub struct ThemeMetadataPrinter(ThemeMetadata); - -impl Debug for ThemeMetadataPrinter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ThemeMetadata") - .field("name", &IntoPrinter(&self.0.name)) - .field("is_light", &self.0.is_light) - .finish() - } -} - -pub struct SyntaxThemePrinter(SyntaxTheme); - -impl Debug for SyntaxThemePrinter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SyntaxTheme") - .field( - "highlights", - &VecPrinter( - &self - .0 - .highlights - .iter() - .map(|(token, highlight)| { - (IntoPrinter(token), HslaPrinter(highlight.color.unwrap())) - }) - .collect(), - ), - ) - .finish() - } -} - -pub struct VecPrinter<'a, T>(&'a Vec); - -impl<'a, T: Debug> Debug for VecPrinter<'a, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "vec!{:?}", &self.0) - } -} - -pub struct PlayerThemePrinter(PlayerTheme); - -impl Debug for PlayerThemePrinter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PlayerTheme") - .field("cursor", &HslaPrinter(self.0.cursor)) - .field("selection", &HslaPrinter(self.0.selection)) - .finish() - } -} From 4da8ee1e1da730e5d06f0d4db7ab5f90348bf656 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 21:19:32 -0600 Subject: [PATCH 040/156] 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 272f85646050d997c755ca3885080ae6ff1a4195 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 04:33:51 +0100 Subject: [PATCH 041/156] Use `Refineable` for `ThemeStyles` (#3196) This PR updates the `ThemeStyles` struct to use the `Refineable` trait instead of a custom declarative macro for generating refinements. Release Notes: - N/A --- crates/theme2/src/colors.rs | 23 ++++++++-------- crates/theme2/src/default_theme.rs | 6 ++--- crates/theme2/src/syntax.rs | 2 +- crates/theme2/src/theme2.rs | 3 +-- crates/theme2/src/utils.rs | 43 ------------------------------ 5 files changed, 17 insertions(+), 60 deletions(-) delete mode 100644 crates/theme2/src/utils.rs diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 2a59fa41bd..02c93a2e98 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -1,8 +1,9 @@ use gpui2::Hsla; use refineable::Refineable; -use crate::{generate_struct_with_overrides, SyntaxTheme}; +use crate::SyntaxTheme; +#[derive(Clone)] pub struct SystemColors { pub transparent: Hsla, pub mac_os_traffic_light_red: Hsla, @@ -17,6 +18,7 @@ pub struct PlayerColor { pub selection: Hsla, } +#[derive(Clone)] pub struct PlayerColors(pub Vec); #[derive(Refineable, Clone, Debug)] @@ -46,7 +48,7 @@ pub struct GitStatusColors { pub renamed: Hsla, } -#[derive(Refineable, Clone, Debug)] +#[derive(Refineable, Clone, Debug, Default)] #[refineable(debug)] pub struct ThemeColors { pub border: Hsla, @@ -86,15 +88,14 @@ pub struct ThemeColors { pub editor_active_line: Hsla, } -generate_struct_with_overrides! { - ThemeStyle, - ThemeStyleOverrides, - system: SystemColors, - colors: ThemeColors, - status: StatusColors, - git: GitStatusColors, - player: PlayerColors, - syntax: SyntaxTheme +#[derive(Refineable, Clone)] +pub struct ThemeStyles { + pub system: SystemColors, + pub colors: ThemeColors, + pub status: StatusColors, + pub git: GitStatusColors, + pub player: PlayerColors, + pub syntax: SyntaxTheme, } #[cfg(test)] diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 26a55b5e0d..d7360b6f71 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,5 +1,5 @@ use crate::{ - colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyle}, + colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, default_color_scales, Appearance, SyntaxTheme, ThemeFamily, ThemeVariant, }; @@ -8,7 +8,7 @@ fn zed_pro_daylight() -> ThemeVariant { id: "zed_pro_daylight".to_string(), name: "Zed Pro Daylight".into(), appearance: Appearance::Light, - styles: ThemeStyle { + styles: ThemeStyles { system: SystemColors::default(), colors: ThemeColors::default_light(), status: StatusColors::default(), @@ -24,7 +24,7 @@ pub(crate) fn zed_pro_moonlight() -> ThemeVariant { id: "zed_pro_moonlight".to_string(), name: "Zed Pro Moonlight".into(), appearance: Appearance::Dark, - styles: ThemeStyle { + styles: ThemeStyles { system: SystemColors::default(), colors: ThemeColors::default_dark(), status: StatusColors::default(), diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs index 1cf8564bca..a8127f0c44 100644 --- a/crates/theme2/src/syntax.rs +++ b/crates/theme2/src/syntax.rs @@ -1,6 +1,6 @@ use gpui2::{HighlightStyle, Hsla}; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct SyntaxTheme { pub highlights: Vec<(String, HighlightStyle)>, } diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 372e976bd3..34727eaf89 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -5,7 +5,6 @@ mod registry; mod scale; mod settings; mod syntax; -mod utils; pub use colors::*; pub use default_colors::*; @@ -55,7 +54,7 @@ pub struct ThemeVariant { pub(crate) id: String, pub name: SharedString, pub appearance: Appearance, - pub styles: ThemeStyle, + pub styles: ThemeStyles, } impl ThemeVariant { diff --git a/crates/theme2/src/utils.rs b/crates/theme2/src/utils.rs deleted file mode 100644 index ccdcde4274..0000000000 --- a/crates/theme2/src/utils.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// This macro generates a struct and a corresponding struct with optional fields. -/// -/// It takes as input the name of the struct to be generated, the name of the struct with optional fields, -/// and a list of field names along with their types. -/// -/// # Example -/// ``` -/// generate_struct_with_overrides!( -/// MyStruct, -/// MyStructOverride, -/// field1: i32, -/// field2: String -/// ); -/// ``` -/// This will generate the following structs: -/// ``` -/// pub struct MyStruct { -/// pub field1: i32, -/// pub field2: String, -/// } -/// -/// pub struct MyStructOverride { -/// pub field1: Option, -/// pub field2: Option, -/// } -/// ``` -#[macro_export] -macro_rules! generate_struct_with_overrides { - ($struct_name:ident, $struct_override_name:ident, $($field:ident: $type:ty),*) => { - pub struct $struct_name { - $( - pub $field: $type, - )* - } - - #[allow(dead_code)] - pub struct $struct_override_name { - $( - pub $field: Option<$type>, - )* - } - }; -} From d47ef6470ba88cd62a35c9672b0c6994dd52d10f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 31 Oct 2023 22:32:18 -0600 Subject: [PATCH 042/156] 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 e0cb95b3344439527ff2610b18ba5b9dfb8353dd Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 00:51:47 -0400 Subject: [PATCH 043/156] Add From<&str> for Hsla --- crates/gpui2/src/color.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index db07259476..44b084e65f 100644 --- a/crates/gpui2/src/color.rs +++ b/crates/gpui2/src/color.rs @@ -233,6 +233,12 @@ impl Hsla { } } +impl From<&str> for Hsla { + fn from(s: &str) -> Self { + Rgba::try_from(s).unwrap().into() + } +} + // impl From for Rgba { // fn from(value: Hsla) -> Self { // let h = value.h; From 0efd69c60fe18f2c74669cbebe3cc07ea4744cdb Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 00:51:57 -0400 Subject: [PATCH 044/156] Refine default colors --- crates/theme2/src/default_colors.rs | 203 ++++++++++++++++------------ crates/theme2/src/theme2.rs | 12 ++ crates/ui2/src/elements/label.rs | 8 +- 3 files changed, 131 insertions(+), 92 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 5ef93d036f..8b7a7ac5cf 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,4 +1,4 @@ -use gpui2::{hsla, Rgba}; +use gpui2::{hsla, Hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, @@ -6,6 +6,10 @@ use crate::{ syntax::SyntaxTheme, }; +fn neutral() -> DefaultColorScaleSet { + slate() +} + impl Default for SystemColors { fn default() -> Self { Self { @@ -20,17 +24,17 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: gpui2::black(), - created: gpui2::black(), - deleted: gpui2::black(), - error: gpui2::black(), - hidden: gpui2::black(), - ignored: gpui2::black(), - info: gpui2::black(), - modified: gpui2::black(), - renamed: gpui2::black(), - success: gpui2::black(), - warning: gpui2::black(), + conflict: red().dark(11).into(), + created: green().dark(11).into(), + deleted: red().dark(11).into(), + error: red().dark(11).into(), + hidden: neutral().dark(11).into(), + ignored: neutral().dark(11).into(), + info: blue().dark(11).into(), + modified: yellow().dark(11).into(), + renamed: blue().dark(11).into(), + success: green().dark(11).into(), + warning: yellow().dark(11).into(), } } } @@ -38,12 +42,12 @@ impl Default for StatusColors { impl Default for GitStatusColors { fn default() -> Self { Self { - conflict: gpui2::rgba(0xdec184ff).into(), - created: gpui2::rgba(0xa1c181ff).into(), - deleted: gpui2::rgba(0xd07277ff).into(), - ignored: gpui2::rgba(0x555a63ff).into(), - modified: gpui2::rgba(0x74ade8ff).into(), - renamed: gpui2::rgba(0xdec184ff).into(), + conflict: orange().dark(11), + created: green().dark(11), + deleted: red().dark(11), + ignored: green().dark(11), + modified: yellow().dark(11), + renamed: blue().dark(11), } } } @@ -189,82 +193,86 @@ impl SyntaxTheme { impl ThemeColors { pub fn default_light() -> Self { + let system = SystemColors::default(); + Self { - border: gpui2::white(), - border_variant: gpui2::white(), - border_focused: gpui2::white(), - border_transparent: gpui2::white(), - elevated_surface: gpui2::white(), - surface: gpui2::white(), - background: gpui2::white(), - element: gpui2::white(), - element_hover: gpui2::white(), - element_active: gpui2::white(), - element_selected: gpui2::white(), - element_disabled: gpui2::white(), - element_placeholder: gpui2::white(), - ghost_element: gpui2::white(), - ghost_element_hover: gpui2::white(), - ghost_element_active: gpui2::white(), - ghost_element_selected: gpui2::white(), - ghost_element_disabled: gpui2::white(), - text: gpui2::white(), - text_muted: gpui2::white(), - text_placeholder: gpui2::white(), - text_disabled: gpui2::white(), - text_accent: gpui2::white(), - icon: gpui2::white(), - icon_muted: gpui2::white(), - icon_disabled: gpui2::white(), - icon_placeholder: gpui2::white(), - icon_accent: gpui2::white(), - status_bar: gpui2::white(), - title_bar: gpui2::white(), - toolbar: gpui2::white(), - tab_bar: gpui2::white(), - editor: gpui2::white(), - editor_subheader: gpui2::white(), - editor_active_line: gpui2::white(), + border: neutral().light(6).into(), + border_variant: neutral().light(5).into(), + border_focused: blue().light(5).into(), + border_transparent: system.transparent, + elevated_surface: neutral().light(2).into(), + surface: neutral().light(2).into(), + background: neutral().light(1).into(), + element: neutral().light(3).into(), + element_hover: neutral().light(4).into(), + element_active: neutral().light(5).into(), + element_selected: neutral().light(5).into(), + element_disabled: neutral().light_alpha(3).into(), + element_placeholder: neutral().light(11).into(), + ghost_element: system.transparent, + ghost_element_hover: neutral().light(4).into(), + ghost_element_active: neutral().light(5).into(), + ghost_element_selected: neutral().light(5).into(), + ghost_element_disabled: neutral().light_alpha(3).into(), + text: neutral().light(12).into(), + text_muted: neutral().light(11).into(), + text_placeholder: neutral().light(11).into(), + text_disabled: neutral().light(10).into(), + text_accent: blue().light(12).into(), + icon: neutral().light(12).into(), + icon_muted: neutral().light(11).into(), + icon_disabled: neutral().light(10).into(), + icon_placeholder: neutral().light(11).into(), + icon_accent: blue().light(12).into(), + status_bar: neutral().light(2).into(), + title_bar: neutral().light(2).into(), + toolbar: neutral().light(2).into(), + tab_bar: neutral().light(2).into(), + editor: neutral().light(1).into(), + editor_subheader: neutral().light(2).into(), + editor_active_line: neutral().light_alpha(3).into(), } } pub fn default_dark() -> Self { + let system = SystemColors::default(); + Self { - border: gpui2::rgba(0x464b57ff).into(), - border_variant: gpui2::rgba(0x464b57ff).into(), - border_focused: gpui2::rgba(0x293b5bff).into(), - border_transparent: gpui2::rgba(0x00000000).into(), - elevated_surface: gpui2::rgba(0x3b414dff).into(), - surface: gpui2::rgba(0x2f343eff).into(), - background: gpui2::rgba(0x3b414dff).into(), - element: gpui2::rgba(0x3b414dff).into(), - element_hover: gpui2::rgba(0xffffff1e).into(), - element_active: gpui2::rgba(0xffffff28).into(), - element_selected: gpui2::rgba(0x18243dff).into(), - element_disabled: gpui2::rgba(0x00000000).into(), - element_placeholder: gpui2::black(), - ghost_element: gpui2::rgba(0x00000000).into(), - ghost_element_hover: gpui2::rgba(0xffffff14).into(), - ghost_element_active: gpui2::rgba(0xffffff1e).into(), - ghost_element_selected: gpui2::rgba(0x18243dff).into(), - ghost_element_disabled: gpui2::rgba(0x00000000).into(), - text: gpui2::rgba(0xc8ccd4ff).into(), - text_muted: gpui2::rgba(0x838994ff).into(), - text_placeholder: gpui2::rgba(0xd07277ff).into(), - text_disabled: gpui2::rgba(0x555a63ff).into(), - text_accent: gpui2::rgba(0x74ade8ff).into(), - icon: gpui2::black(), - icon_muted: gpui2::rgba(0x838994ff).into(), - icon_disabled: gpui2::black(), - icon_placeholder: gpui2::black(), - icon_accent: gpui2::black(), - status_bar: gpui2::rgba(0x3b414dff).into(), - title_bar: gpui2::rgba(0x3b414dff).into(), - toolbar: gpui2::rgba(0x282c33ff).into(), - tab_bar: gpui2::rgba(0x2f343eff).into(), - editor: gpui2::rgba(0x282c33ff).into(), - editor_subheader: gpui2::rgba(0x2f343eff).into(), - editor_active_line: gpui2::rgba(0x2f343eff).into(), + border: neutral().dark(6).into(), + border_variant: neutral().dark(5).into(), + border_focused: blue().dark(5).into(), + border_transparent: system.transparent, + elevated_surface: neutral().dark(2).into(), + surface: neutral().dark(2).into(), + background: neutral().dark(1).into(), + element: neutral().dark(3).into(), + element_hover: neutral().dark(4).into(), + element_active: neutral().dark(5).into(), + element_selected: neutral().dark(5).into(), + element_disabled: neutral().dark_alpha(3).into(), + element_placeholder: neutral().dark(11).into(), + ghost_element: system.transparent, + ghost_element_hover: neutral().dark(4).into(), + ghost_element_active: neutral().dark(5).into(), + ghost_element_selected: neutral().dark(5).into(), + ghost_element_disabled: neutral().dark_alpha(3).into(), + text: neutral().dark(12).into(), + text_muted: neutral().dark(11).into(), + text_placeholder: neutral().dark(11).into(), + text_disabled: neutral().dark(10).into(), + text_accent: blue().dark(12).into(), + icon: neutral().dark(12).into(), + icon_muted: neutral().dark(11).into(), + icon_disabled: neutral().dark(10).into(), + icon_placeholder: neutral().dark(11).into(), + icon_accent: blue().dark(12).into(), + status_bar: neutral().dark(2).into(), + title_bar: neutral().dark(2).into(), + toolbar: neutral().dark(2).into(), + tab_bar: neutral().dark(2).into(), + editor: neutral().dark(1).into(), + editor_subheader: neutral().dark(2).into(), + editor_active_line: neutral().dark_alpha(3).into(), } } } @@ -277,6 +285,25 @@ struct DefaultColorScaleSet { dark_alpha: [&'static str; 12], } +// See [ColorScaleSet] for why we use index-1. +impl DefaultColorScaleSet { + pub fn light(&self, index: usize) -> Hsla { + self.light[index - 1].into() + } + + pub fn light_alpha(&self, index: usize) -> Hsla { + self.light_alpha[index - 1].into() + } + + pub fn dark(&self, index: usize) -> Hsla { + self.dark[index - 1].into() + } + + pub fn dark_alpha(&self, index: usize) -> Hsla { + self.dark_alpha[index - 1].into() + } +} + impl From for ColorScaleSet { fn from(default: DefaultColorScaleSet) -> Self { Self::new( diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 34727eaf89..88dcbd1286 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -70,6 +70,18 @@ impl ThemeVariant { &self.styles.syntax } + /// Returns the [`StatusColors`] for the theme. + #[inline(always)] + pub fn status(&self) -> &StatusColors { + &self.styles.status + } + + /// Returns the [`GitStatusColors`] for the theme. + #[inline(always)] + pub fn git(&self) -> &GitStatusColors { + &self.styles.git + } + /// Returns the color for the syntax node with the given name. #[inline(always)] pub fn syntax_color(&self, name: &str) -> Hsla { diff --git a/crates/ui2/src/elements/label.rs b/crates/ui2/src/elements/label.rs index ee8ac9a636..7051565e17 100644 --- a/crates/ui2/src/elements/label.rs +++ b/crates/ui2/src/elements/label.rs @@ -21,11 +21,11 @@ impl LabelColor { match self { Self::Default => cx.theme().colors().text, Self::Muted => cx.theme().colors().text_muted, - Self::Created => gpui2::red(), - Self::Modified => gpui2::red(), - Self::Deleted => gpui2::red(), + Self::Created => cx.theme().status().created, + Self::Modified => cx.theme().status().modified, + Self::Deleted => cx.theme().status().deleted, Self::Disabled => cx.theme().colors().text_disabled, - Self::Hidden => gpui2::red(), + Self::Hidden => cx.theme().status().hidden, Self::Placeholder => cx.theme().colors().text_placeholder, Self::Accent => cx.theme().colors().text_accent, } From bfb1f5ecf08e5d75e1ef194f8f1e5ac441186a2a Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 01:41:33 -0400 Subject: [PATCH 045/156] Continue refining theme, update tabs & tab bar --- crates/storybook2/src/storybook2.rs | 2 +- crates/theme2/src/colors.rs | 3 ++ crates/theme2/src/default_colors.rs | 42 ++++++++++++++++------------ crates/ui2/src/components/tab.rs | 12 ++++---- crates/ui2/src/components/tab_bar.rs | 8 ++++++ crates/ui2/src/elements/icon.rs | 20 ++++++------- crates/ui2/src/elements/label.rs | 3 +- 7 files changed, 52 insertions(+), 38 deletions(-) diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 411fe18071..02284e433d 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -77,7 +77,7 @@ fn main() { WindowOptions { bounds: WindowBounds::Fixed(Bounds { origin: Default::default(), - size: size(px(1700.), px(980.)).into(), + size: size(px(1500.), px(780.)).into(), }), ..Default::default() }, diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 02c93a2e98..ee69eed612 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -64,6 +64,7 @@ pub struct ThemeColors { pub element_selected: Hsla, pub element_disabled: Hsla, pub element_placeholder: Hsla, + pub element_drop_target: Hsla, pub ghost_element: Hsla, pub ghost_element_hover: Hsla, pub ghost_element_active: Hsla, @@ -83,6 +84,8 @@ pub struct ThemeColors { pub title_bar: Hsla, pub toolbar: Hsla, pub tab_bar: Hsla, + pub tab_inactive: Hsla, + pub tab_active: Hsla, pub editor: Hsla, pub editor_subheader: Hsla, pub editor_active_line: Hsla, diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 8b7a7ac5cf..904e275c84 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -25,7 +25,7 @@ impl Default for StatusColors { fn default() -> Self { Self { conflict: red().dark(11).into(), - created: green().dark(11).into(), + created: grass().dark(11).into(), deleted: red().dark(11).into(), error: red().dark(11).into(), hidden: neutral().dark(11).into(), @@ -33,7 +33,7 @@ impl Default for StatusColors { info: blue().dark(11).into(), modified: yellow().dark(11).into(), renamed: blue().dark(11).into(), - success: green().dark(11).into(), + success: grass().dark(11).into(), warning: yellow().dark(11).into(), } } @@ -43,9 +43,9 @@ impl Default for GitStatusColors { fn default() -> Self { Self { conflict: orange().dark(11), - created: green().dark(11), + created: grass().dark(11), deleted: red().dark(11), - ignored: green().dark(11), + ignored: neutral().dark(11), modified: yellow().dark(11), renamed: blue().dark(11), } @@ -209,6 +209,7 @@ impl ThemeColors { element_selected: neutral().light(5).into(), element_disabled: neutral().light_alpha(3).into(), element_placeholder: neutral().light(11).into(), + element_drop_target: blue().light_alpha(2).into(), ghost_element: system.transparent, ghost_element_hover: neutral().light(4).into(), ghost_element_active: neutral().light(5).into(), @@ -218,16 +219,18 @@ impl ThemeColors { text_muted: neutral().light(11).into(), text_placeholder: neutral().light(11).into(), text_disabled: neutral().light(10).into(), - text_accent: blue().light(12).into(), - icon: neutral().light(12).into(), - icon_muted: neutral().light(11).into(), - icon_disabled: neutral().light(10).into(), - icon_placeholder: neutral().light(11).into(), - icon_accent: blue().light(12).into(), + text_accent: blue().light(11).into(), + icon: neutral().light(11).into(), + icon_muted: neutral().light(10).into(), + icon_disabled: neutral().light(9).into(), + icon_placeholder: neutral().light(10).into(), + icon_accent: blue().light(11).into(), status_bar: neutral().light(2).into(), title_bar: neutral().light(2).into(), - toolbar: neutral().light(2).into(), + toolbar: neutral().light(1).into(), tab_bar: neutral().light(2).into(), + tab_active: neutral().light(1).into(), + tab_inactive: neutral().light(2).into(), editor: neutral().light(1).into(), editor_subheader: neutral().light(2).into(), editor_active_line: neutral().light_alpha(3).into(), @@ -251,6 +254,7 @@ impl ThemeColors { element_selected: neutral().dark(5).into(), element_disabled: neutral().dark_alpha(3).into(), element_placeholder: neutral().dark(11).into(), + element_drop_target: blue().dark_alpha(2).into(), ghost_element: system.transparent, ghost_element_hover: neutral().dark(4).into(), ghost_element_active: neutral().dark(5).into(), @@ -260,16 +264,18 @@ impl ThemeColors { text_muted: neutral().dark(11).into(), text_placeholder: neutral().dark(11).into(), text_disabled: neutral().dark(10).into(), - text_accent: blue().dark(12).into(), - icon: neutral().dark(12).into(), - icon_muted: neutral().dark(11).into(), - icon_disabled: neutral().dark(10).into(), - icon_placeholder: neutral().dark(11).into(), - icon_accent: blue().dark(12).into(), + text_accent: blue().dark(11).into(), + icon: neutral().dark(11).into(), + icon_muted: neutral().dark(10).into(), + icon_disabled: neutral().dark(9).into(), + icon_placeholder: neutral().dark(10).into(), + icon_accent: blue().dark(11).into(), status_bar: neutral().dark(2).into(), title_bar: neutral().dark(2).into(), - toolbar: neutral().dark(2).into(), + toolbar: neutral().dark(1).into(), tab_bar: neutral().dark(2).into(), + tab_active: neutral().dark(1).into(), + tab_inactive: neutral().dark(2).into(), editor: neutral().dark(1).into(), editor_subheader: neutral().dark(2).into(), editor_active_line: neutral().dark_alpha(3).into(), diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 5f20af0955..fddd82b064 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -108,13 +108,13 @@ impl Tab { let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted); let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current { - true => ( - cx.theme().colors().ghost_element, + false => ( + cx.theme().colors().tab_inactive, cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), - false => ( - cx.theme().colors().element, + true => ( + cx.theme().colors().tab_active, cx.theme().colors().element_hover, cx.theme().colors().element_active, ), @@ -127,7 +127,7 @@ impl Tab { div() .id(self.id.clone()) .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone())) - .drag_over::(|d| d.bg(black())) + .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) .on_drop(|_view, state: View, cx| { dbg!(state.read(cx)); }) @@ -144,7 +144,7 @@ impl Tab { .px_1() .flex() .items_center() - .gap_1() + .gap_1p5() .children(has_fs_conflict.then(|| { IconElement::new(Icon::ExclamationTriangle) .size(crate::IconSize::Small) diff --git a/crates/ui2/src/components/tab_bar.rs b/crates/ui2/src/components/tab_bar.rs index 550105b98e..bb7fca1153 100644 --- a/crates/ui2/src/components/tab_bar.rs +++ b/crates/ui2/src/components/tab_bar.rs @@ -27,6 +27,7 @@ impl TabBar { let (can_navigate_back, can_navigate_forward) = self.can_navigate; div() + .group("tab_bar") .id(self.id.clone()) .w_full() .flex() @@ -34,6 +35,7 @@ impl TabBar { // Left Side .child( div() + .relative() .px_1() .flex() .flex_none() @@ -41,6 +43,7 @@ impl TabBar { // Nav Buttons .child( div() + .right_0() .flex() .items_center() .gap_px() @@ -67,10 +70,15 @@ impl TabBar { // 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() diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index 6c1b3a4f08..f3d612562f 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -26,18 +26,16 @@ pub enum IconColor { impl IconColor { pub fn color(self, cx: &WindowContext) -> Hsla { - let theme_colors = cx.theme().colors(); - match self { - IconColor::Default => theme_colors.icon, - IconColor::Muted => theme_colors.icon_muted, - IconColor::Disabled => theme_colors.icon_disabled, - IconColor::Placeholder => theme_colors.icon_placeholder, - IconColor::Accent => theme_colors.icon_accent, - IconColor::Error => gpui2::red(), - IconColor::Warning => gpui2::red(), - IconColor::Success => gpui2::red(), - IconColor::Info => gpui2::red(), + IconColor::Default => cx.theme().colors().icon, + IconColor::Muted => cx.theme().colors().icon_muted, + IconColor::Disabled => cx.theme().colors().icon_disabled, + IconColor::Placeholder => cx.theme().colors().icon_placeholder, + IconColor::Accent => cx.theme().colors().icon_accent, + IconColor::Error => cx.theme().status().error, + IconColor::Warning => cx.theme().status().warning, + IconColor::Success => cx.theme().status().success, + IconColor::Info => cx.theme().status().info, } } } diff --git a/crates/ui2/src/elements/label.rs b/crates/ui2/src/elements/label.rs index 7051565e17..360d003a8b 100644 --- a/crates/ui2/src/elements/label.rs +++ b/crates/ui2/src/elements/label.rs @@ -79,8 +79,7 @@ impl Label { this.relative().child( div() .absolute() - .top_px() - .my_auto() + .top_1_2() .w_full() .h_px() .bg(LabelColor::Hidden.hsla(cx)), From 3bcc2fa17be05441f149691aba5f663eacc4b4c6 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 02:10:23 -0400 Subject: [PATCH 046/156] Update notifications panel --- crates/ui2/src/components/list.rs | 71 ++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 1668592a38..ebc442afdc 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,4 +1,4 @@ -use gpui2::{div, relative, Div}; +use gpui2::{div, px, relative, Div}; use crate::settings::user_settings; use crate::{ @@ -473,42 +473,63 @@ impl ListDetailsEntry { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let settings = user_settings(cx); - let (item_bg, item_bg_hover, item_bg_active) = match self.seen { - true => ( - cx.theme().colors().ghost_element, - cx.theme().colors().ghost_element_hover, - cx.theme().colors().ghost_element_active, - ), - false => ( - cx.theme().colors().element, - cx.theme().colors().element_hover, - cx.theme().colors().element_active, - ), - }; + let (item_bg, item_bg_hover, item_bg_active) = ( + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, + ); let label_color = match self.seen { true => LabelColor::Muted, false => LabelColor::Default, }; - v_stack() + div() .relative() .group("") .bg(item_bg) - .px_1() - .py_1_5() + .px_2() + .py_1p5() .w_full() - .line_height(relative(1.2)) - .child(Label::new(self.label.clone()).color(label_color)) - .children( - self.meta - .map(|meta| Label::new(meta).color(LabelColor::Muted)), - ) + .z_index(1) + .when(!self.seen, |this| { + this.child( + div() + .absolute() + .left(px(3.0)) + .top_3() + .rounded_full() + .border_2() + .border_color(cx.theme().colors().surface) + .w(px(9.0)) + .h(px(9.0)) + .z_index(2) + .bg(cx.theme().status().info), + ) + }) .child( - h_stack() + v_stack() + .w_full() + .line_height(relative(1.2)) .gap_1() - .justify_end() - .children(self.actions.unwrap_or_default()), + .child( + div() + .w_5() + .h_5() + .rounded_full() + .bg(cx.theme().colors().icon_accent), + ) + .child(Label::new(self.label.clone()).color(label_color)) + .children( + self.meta + .map(|meta| Label::new(meta).color(LabelColor::Muted)), + ) + .child( + h_stack() + .gap_1() + .justify_end() + .children(self.actions.unwrap_or_default()), + ), ) } } From 4d320f065ed3ba51181147826d7130d03f6b5924 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 12:47:19 +0100 Subject: [PATCH 047/156] 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 048/156] 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 049/156] 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 050/156] 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 b8547e926de412d78962801dbf1f344bbf944a25 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 10:31:03 -0400 Subject: [PATCH 051/156] Remove blanket `From<&str>` impl for `Hsla` Since this is a fallible operation we don't want to have a blanket infallible conversion from any arbitrary `&str` to an `Hsla`. --- crates/gpui2/src/color.rs | 6 ------ crates/theme2/src/default_colors.rs | 18 +++++++++--------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index 44b084e65f..db07259476 100644 --- a/crates/gpui2/src/color.rs +++ b/crates/gpui2/src/color.rs @@ -233,12 +233,6 @@ impl Hsla { } } -impl From<&str> for Hsla { - fn from(s: &str) -> Self { - Rgba::try_from(s).unwrap().into() - } -} - // impl From for Rgba { // fn from(value: Hsla) -> Self { // let h = value.h; diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 904e275c84..8bb1111727 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -4,6 +4,7 @@ use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, + ColorScaleStep, }; fn neutral() -> DefaultColorScaleSet { @@ -291,22 +292,21 @@ struct DefaultColorScaleSet { dark_alpha: [&'static str; 12], } -// See [ColorScaleSet] for why we use index-1. impl DefaultColorScaleSet { - pub fn light(&self, index: usize) -> Hsla { - self.light[index - 1].into() + pub fn light(&self, index: ColorScaleStep) -> Hsla { + Rgba::try_from(self.light[index - 1]).unwrap().into() } - pub fn light_alpha(&self, index: usize) -> Hsla { - self.light_alpha[index - 1].into() + pub fn light_alpha(&self, index: ColorScaleStep) -> Hsla { + Rgba::try_from(self.light_alpha[index - 1]).unwrap().into() } - pub fn dark(&self, index: usize) -> Hsla { - self.dark[index - 1].into() + pub fn dark(&self, index: ColorScaleStep) -> Hsla { + Rgba::try_from(self.dark[index - 1]).unwrap().into() } - pub fn dark_alpha(&self, index: usize) -> Hsla { - self.dark_alpha[index - 1].into() + pub fn dark_alpha(&self, index: ColorScaleStep) -> Hsla { + Rgba::try_from(self.dark_alpha[index - 1]).unwrap().into() } } From bbe53895efb25233a4ef42ea701054d1efdc0f59 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 15:45:42 +0100 Subject: [PATCH 052/156] Return `ColorScaleSet`s from individual color scale functions (#3197) This PR adjusts the individual color scale functions to return `ColorScaleSet`s instead of `DefaultColorScaleSet`s. We only use the `DefaultColorScaleSet`s to simplify the construction of the scales, so it isn't necessary to surface them outside of the function. Release Notes: - N/A --- crates/theme2/src/default_colors.rs | 165 +++++++++++++++++----------- 1 file changed, 99 insertions(+), 66 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 5ef93d036f..8fb38e9661 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -299,43 +299,43 @@ impl From for ColorScaleSet { pub fn default_color_scales() -> ColorScales { ColorScales { - gray: gray().into(), - mauve: mauve().into(), - slate: slate().into(), - sage: sage().into(), - olive: olive().into(), - sand: sand().into(), - gold: gold().into(), - bronze: bronze().into(), - brown: brown().into(), - yellow: yellow().into(), - amber: amber().into(), - orange: orange().into(), - tomato: tomato().into(), - red: red().into(), - ruby: ruby().into(), - crimson: crimson().into(), - pink: pink().into(), - plum: plum().into(), - purple: purple().into(), - violet: violet().into(), - iris: iris().into(), - indigo: indigo().into(), - blue: blue().into(), - cyan: cyan().into(), - teal: teal().into(), - jade: jade().into(), - green: green().into(), - grass: grass().into(), - lime: lime().into(), - mint: mint().into(), - sky: sky().into(), - black: black().into(), - white: white().into(), + gray: gray(), + mauve: mauve(), + slate: slate(), + sage: sage(), + olive: olive(), + sand: sand(), + gold: gold(), + bronze: bronze(), + brown: brown(), + yellow: yellow(), + amber: amber(), + orange: orange(), + tomato: tomato(), + red: red(), + ruby: ruby(), + crimson: crimson(), + pink: pink(), + plum: plum(), + purple: purple(), + violet: violet(), + iris: iris(), + indigo: indigo(), + blue: blue(), + cyan: cyan(), + teal: teal(), + jade: jade(), + green: green(), + grass: grass(), + lime: lime(), + mint: mint(), + sky: sky(), + black: black(), + white: white(), } } -fn gray() -> DefaultColorScaleSet { +fn gray() -> ColorScaleSet { DefaultColorScaleSet { scale: "Gray", light: [ @@ -395,9 +395,10 @@ fn gray() -> DefaultColorScaleSet { "#ffffffed", ], } + .into() } -fn mauve() -> DefaultColorScaleSet { +fn mauve() -> ColorScaleSet { DefaultColorScaleSet { scale: "Mauve", light: [ @@ -457,9 +458,10 @@ fn mauve() -> DefaultColorScaleSet { "#fdfdffef", ], } + .into() } -fn slate() -> DefaultColorScaleSet { +fn slate() -> ColorScaleSet { DefaultColorScaleSet { scale: "Slate", light: [ @@ -519,9 +521,10 @@ fn slate() -> DefaultColorScaleSet { "#fcfdffef", ], } + .into() } -fn sage() -> DefaultColorScaleSet { +fn sage() -> ColorScaleSet { DefaultColorScaleSet { scale: "Sage", light: [ @@ -581,9 +584,10 @@ fn sage() -> DefaultColorScaleSet { "#fdfffeed", ], } + .into() } -fn olive() -> DefaultColorScaleSet { +fn olive() -> ColorScaleSet { DefaultColorScaleSet { scale: "Olive", light: [ @@ -643,9 +647,10 @@ fn olive() -> DefaultColorScaleSet { "#fdfffded", ], } + .into() } -fn sand() -> DefaultColorScaleSet { +fn sand() -> ColorScaleSet { DefaultColorScaleSet { scale: "Sand", light: [ @@ -705,9 +710,10 @@ fn sand() -> DefaultColorScaleSet { "#fffffded", ], } + .into() } -fn gold() -> DefaultColorScaleSet { +fn gold() -> ColorScaleSet { DefaultColorScaleSet { scale: "Gold", light: [ @@ -767,9 +773,10 @@ fn gold() -> DefaultColorScaleSet { "#fef7ede7", ], } + .into() } -fn bronze() -> DefaultColorScaleSet { +fn bronze() -> ColorScaleSet { DefaultColorScaleSet { scale: "Bronze", light: [ @@ -829,9 +836,10 @@ fn bronze() -> DefaultColorScaleSet { "#fff1e9ec", ], } + .into() } -fn brown() -> DefaultColorScaleSet { +fn brown() -> ColorScaleSet { DefaultColorScaleSet { scale: "Brown", light: [ @@ -891,9 +899,10 @@ fn brown() -> DefaultColorScaleSet { "#feecd4f2", ], } + .into() } -fn yellow() -> DefaultColorScaleSet { +fn yellow() -> ColorScaleSet { DefaultColorScaleSet { scale: "Yellow", light: [ @@ -953,9 +962,10 @@ fn yellow() -> DefaultColorScaleSet { "#fef6baf6", ], } + .into() } -fn amber() -> DefaultColorScaleSet { +fn amber() -> ColorScaleSet { DefaultColorScaleSet { scale: "Amber", light: [ @@ -1015,9 +1025,10 @@ fn amber() -> DefaultColorScaleSet { "#ffe7b3ff", ], } + .into() } -fn orange() -> DefaultColorScaleSet { +fn orange() -> ColorScaleSet { DefaultColorScaleSet { scale: "Orange", light: [ @@ -1077,9 +1088,10 @@ fn orange() -> DefaultColorScaleSet { "#ffe0c2ff", ], } + .into() } -fn tomato() -> DefaultColorScaleSet { +fn tomato() -> ColorScaleSet { DefaultColorScaleSet { scale: "Tomato", light: [ @@ -1139,9 +1151,10 @@ fn tomato() -> DefaultColorScaleSet { "#ffd6cefb", ], } + .into() } -fn red() -> DefaultColorScaleSet { +fn red() -> ColorScaleSet { DefaultColorScaleSet { scale: "Red", light: [ @@ -1201,9 +1214,10 @@ fn red() -> DefaultColorScaleSet { "#ffd1d9ff", ], } + .into() } -fn ruby() -> DefaultColorScaleSet { +fn ruby() -> ColorScaleSet { DefaultColorScaleSet { scale: "Ruby", light: [ @@ -1263,9 +1277,10 @@ fn ruby() -> DefaultColorScaleSet { "#ffd3e2fe", ], } + .into() } -fn crimson() -> DefaultColorScaleSet { +fn crimson() -> ColorScaleSet { DefaultColorScaleSet { scale: "Crimson", light: [ @@ -1325,9 +1340,10 @@ fn crimson() -> DefaultColorScaleSet { "#ffd5eafd", ], } + .into() } -fn pink() -> DefaultColorScaleSet { +fn pink() -> ColorScaleSet { DefaultColorScaleSet { scale: "Pink", light: [ @@ -1387,9 +1403,10 @@ fn pink() -> DefaultColorScaleSet { "#ffd3ecfd", ], } + .into() } -fn plum() -> DefaultColorScaleSet { +fn plum() -> ColorScaleSet { DefaultColorScaleSet { scale: "Plum", light: [ @@ -1449,9 +1466,10 @@ fn plum() -> DefaultColorScaleSet { "#feddfef4", ], } + .into() } -fn purple() -> DefaultColorScaleSet { +fn purple() -> ColorScaleSet { DefaultColorScaleSet { scale: "Purple", light: [ @@ -1511,9 +1529,10 @@ fn purple() -> DefaultColorScaleSet { "#f1ddfffa", ], } + .into() } -fn violet() -> DefaultColorScaleSet { +fn violet() -> ColorScaleSet { DefaultColorScaleSet { scale: "Violet", light: [ @@ -1573,9 +1592,10 @@ fn violet() -> DefaultColorScaleSet { "#e3defffe", ], } + .into() } -fn iris() -> DefaultColorScaleSet { +fn iris() -> ColorScaleSet { DefaultColorScaleSet { scale: "Iris", light: [ @@ -1635,9 +1655,10 @@ fn iris() -> DefaultColorScaleSet { "#e1e0fffe", ], } + .into() } -fn indigo() -> DefaultColorScaleSet { +fn indigo() -> ColorScaleSet { DefaultColorScaleSet { scale: "Indigo", light: [ @@ -1697,9 +1718,10 @@ fn indigo() -> DefaultColorScaleSet { "#d6e1ffff", ], } + .into() } -fn blue() -> DefaultColorScaleSet { +fn blue() -> ColorScaleSet { DefaultColorScaleSet { scale: "Blue", light: [ @@ -1759,9 +1781,10 @@ fn blue() -> DefaultColorScaleSet { "#c2e6ffff", ], } + .into() } -fn cyan() -> DefaultColorScaleSet { +fn cyan() -> ColorScaleSet { DefaultColorScaleSet { scale: "Cyan", light: [ @@ -1821,9 +1844,10 @@ fn cyan() -> DefaultColorScaleSet { "#bbf3fef7", ], } + .into() } -fn teal() -> DefaultColorScaleSet { +fn teal() -> ColorScaleSet { DefaultColorScaleSet { scale: "Teal", light: [ @@ -1883,9 +1907,10 @@ fn teal() -> DefaultColorScaleSet { "#b8ffebef", ], } + .into() } -fn jade() -> DefaultColorScaleSet { +fn jade() -> ColorScaleSet { DefaultColorScaleSet { scale: "Jade", light: [ @@ -1945,9 +1970,10 @@ fn jade() -> DefaultColorScaleSet { "#b8ffe1ef", ], } + .into() } -fn green() -> DefaultColorScaleSet { +fn green() -> ColorScaleSet { DefaultColorScaleSet { scale: "Green", light: [ @@ -2007,9 +2033,10 @@ fn green() -> DefaultColorScaleSet { "#bbffd7f0", ], } + .into() } -fn grass() -> DefaultColorScaleSet { +fn grass() -> ColorScaleSet { DefaultColorScaleSet { scale: "Grass", light: [ @@ -2069,9 +2096,10 @@ fn grass() -> DefaultColorScaleSet { "#ceffceef", ], } + .into() } -fn lime() -> DefaultColorScaleSet { +fn lime() -> ColorScaleSet { DefaultColorScaleSet { scale: "Lime", light: [ @@ -2131,9 +2159,10 @@ fn lime() -> DefaultColorScaleSet { "#e9febff7", ], } + .into() } -fn mint() -> DefaultColorScaleSet { +fn mint() -> ColorScaleSet { DefaultColorScaleSet { scale: "Mint", light: [ @@ -2193,9 +2222,10 @@ fn mint() -> DefaultColorScaleSet { "#cbfee9f5", ], } + .into() } -fn sky() -> DefaultColorScaleSet { +fn sky() -> ColorScaleSet { DefaultColorScaleSet { scale: "Sky", light: [ @@ -2255,9 +2285,10 @@ fn sky() -> DefaultColorScaleSet { "#c2f3ffff", ], } + .into() } -fn black() -> DefaultColorScaleSet { +fn black() -> ColorScaleSet { DefaultColorScaleSet { scale: "Black", light: [ @@ -2317,9 +2348,10 @@ fn black() -> DefaultColorScaleSet { "#000000f2", ], } + .into() } -fn white() -> DefaultColorScaleSet { +fn white() -> ColorScaleSet { DefaultColorScaleSet { scale: "White", light: [ @@ -2379,4 +2411,5 @@ fn white() -> DefaultColorScaleSet { "#fffffff2", ], } + .into() } From d2a8f972f3de4d27343238e548e0d988e3da2039 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 10:48:23 -0400 Subject: [PATCH 053/156] Fix return type --- crates/theme2/src/default_colors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 7e2d808a53..15c09e212b 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -7,7 +7,7 @@ use crate::{ ColorScaleStep, }; -fn neutral() -> DefaultColorScaleSet { +fn neutral() -> ColorScaleSet { slate() } From 5d660759bf6d9d1a93d442541ce1086512542952 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 10:50:00 -0400 Subject: [PATCH 054/156] Continue refining default syntax theme --- crates/theme2/src/default_colors.rs | 174 +++++++++++++--------------- 1 file changed, 78 insertions(+), 96 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 15c09e212b..cb2b406d9b 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -84,54 +84,45 @@ impl SyntaxTheme { pub fn default_light() -> Self { Self { highlights: vec![ - ( - "string.special.symbol".into(), - gpui2::rgba(0xad6e26ff).into(), - ), - ("hint".into(), gpui2::rgba(0x9294beff).into()), - ("link_uri".into(), gpui2::rgba(0x3882b7ff).into()), - ("type".into(), gpui2::rgba(0x3882b7ff).into()), - ("string.regex".into(), gpui2::rgba(0xad6e26ff).into()), - ("constant".into(), gpui2::rgba(0x669f59ff).into()), - ("function".into(), gpui2::rgba(0x5b79e3ff).into()), - ("string.special".into(), gpui2::rgba(0xad6e26ff).into()), - ("punctuation.bracket".into(), gpui2::rgba(0x4d4f52ff).into()), - ("variable".into(), gpui2::rgba(0x383a41ff).into()), - ("punctuation".into(), gpui2::rgba(0x383a41ff).into()), - ("property".into(), gpui2::rgba(0xd3604fff).into()), - ("string".into(), gpui2::rgba(0x649f57ff).into()), - ("predictive".into(), gpui2::rgba(0x9b9ec6ff).into()), - ("attribute".into(), gpui2::rgba(0x5c78e2ff).into()), - ("number".into(), gpui2::rgba(0xad6e25ff).into()), - ("constructor".into(), gpui2::rgba(0x5c78e2ff).into()), - ("embedded".into(), gpui2::rgba(0x383a41ff).into()), - ("title".into(), gpui2::rgba(0xd3604fff).into()), - ("tag".into(), gpui2::rgba(0x5c78e2ff).into()), - ("boolean".into(), gpui2::rgba(0xad6e25ff).into()), - ( - "punctuation.list_marker".into(), - gpui2::rgba(0xd3604fff).into(), - ), - ("variant".into(), gpui2::rgba(0x5b79e3ff).into()), - ("emphasis".into(), gpui2::rgba(0x5c78e2ff).into()), - ("link_text".into(), gpui2::rgba(0x5b79e3ff).into()), - ("comment".into(), gpui2::rgba(0xa2a3a7ff).into()), - ("punctuation.special".into(), gpui2::rgba(0xb92b46ff).into()), - ("emphasis.strong".into(), gpui2::rgba(0xad6e25ff).into()), - ("primary".into(), gpui2::rgba(0x383a41ff).into()), - ( - "punctuation.delimiter".into(), - gpui2::rgba(0x4d4f52ff).into(), - ), - ("label".into(), gpui2::rgba(0x5c78e2ff).into()), - ("keyword".into(), gpui2::rgba(0xa449abff).into()), - ("string.escape".into(), gpui2::rgba(0x7c7e86ff).into()), - ("text.literal".into(), gpui2::rgba(0x649f57ff).into()), - ("variable.special".into(), gpui2::rgba(0xad6e25ff).into()), - ("comment.doc".into(), gpui2::rgba(0x7c7e86ff).into()), - ("enum".into(), gpui2::rgba(0xd3604fff).into()), - ("operator".into(), gpui2::rgba(0x3882b7ff).into()), - ("preproc".into(), gpui2::rgba(0x383a41ff).into()), + ("attribute".into(), cyan().light(11).into()), + ("boolean".into(), tomato().light(11).into()), + ("comment".into(), neutral().light(11).into()), + ("comment.doc".into(), iris().light(12).into()), + ("constant".into(), red().light(7).into()), + ("constructor".into(), red().light(7).into()), + ("embedded".into(), red().light(7).into()), + ("emphasis".into(), red().light(7).into()), + ("emphasis.strong".into(), red().light(7).into()), + ("enum".into(), red().light(7).into()), + ("function".into(), red().light(7).into()), + ("hint".into(), red().light(7).into()), + ("keyword".into(), orange().light(11).into()), + ("label".into(), red().light(7).into()), + ("link_text".into(), red().light(7).into()), + ("link_uri".into(), red().light(7).into()), + ("number".into(), red().light(7).into()), + ("operator".into(), red().light(7).into()), + ("predictive".into(), red().light(7).into()), + ("preproc".into(), red().light(7).into()), + ("primary".into(), red().light(7).into()), + ("property".into(), red().light(7).into()), + ("punctuation".into(), neutral().light(11).into()), + ("punctuation.bracket".into(), neutral().light(11).into()), + ("punctuation.delimiter".into(), neutral().light(11).into()), + ("punctuation.list_marker".into(), blue().light(11).into()), + ("punctuation.special".into(), red().light(7).into()), + ("string".into(), jade().light(11).into()), + ("string.escape".into(), red().light(7).into()), + ("string.regex".into(), tomato().light(11).into()), + ("string.special".into(), red().light(7).into()), + ("string.special.symbol".into(), red().light(7).into()), + ("tag".into(), red().light(7).into()), + ("text.literal".into(), red().light(7).into()), + ("title".into(), red().light(7).into()), + ("type".into(), red().light(7).into()), + ("variable".into(), red().light(7).into()), + ("variable.special".into(), red().light(7).into()), + ("variant".into(), red().light(7).into()), ], } } @@ -139,54 +130,45 @@ impl SyntaxTheme { pub fn default_dark() -> Self { Self { highlights: vec![ - ("keyword".into(), gpui2::rgba(0xb477cfff).into()), - ("comment.doc".into(), gpui2::rgba(0x878e98ff).into()), - ("variant".into(), gpui2::rgba(0x73ade9ff).into()), - ("property".into(), gpui2::rgba(0xd07277ff).into()), - ("function".into(), gpui2::rgba(0x73ade9ff).into()), - ("type".into(), gpui2::rgba(0x6eb4bfff).into()), - ("tag".into(), gpui2::rgba(0x74ade8ff).into()), - ("string.escape".into(), gpui2::rgba(0x878e98ff).into()), - ("punctuation.bracket".into(), gpui2::rgba(0xb2b9c6ff).into()), - ("hint".into(), gpui2::rgba(0x5a6f89ff).into()), - ("punctuation".into(), gpui2::rgba(0xacb2beff).into()), - ("comment".into(), gpui2::rgba(0x5d636fff).into()), - ("emphasis".into(), gpui2::rgba(0x74ade8ff).into()), - ("punctuation.special".into(), gpui2::rgba(0xb1574bff).into()), - ("link_uri".into(), gpui2::rgba(0x6eb4bfff).into()), - ("string.regex".into(), gpui2::rgba(0xbf956aff).into()), - ("constructor".into(), gpui2::rgba(0x73ade9ff).into()), - ("operator".into(), gpui2::rgba(0x6eb4bfff).into()), - ("constant".into(), gpui2::rgba(0xdfc184ff).into()), - ("string.special".into(), gpui2::rgba(0xbf956aff).into()), - ("emphasis.strong".into(), gpui2::rgba(0xbf956aff).into()), - ( - "string.special.symbol".into(), - gpui2::rgba(0xbf956aff).into(), - ), - ("primary".into(), gpui2::rgba(0xacb2beff).into()), - ("preproc".into(), gpui2::rgba(0xc8ccd4ff).into()), - ("string".into(), gpui2::rgba(0xa1c181ff).into()), - ( - "punctuation.delimiter".into(), - gpui2::rgba(0xb2b9c6ff).into(), - ), - ("embedded".into(), gpui2::rgba(0xc8ccd4ff).into()), - ("enum".into(), gpui2::rgba(0xd07277ff).into()), - ("variable.special".into(), gpui2::rgba(0xbf956aff).into()), - ("text.literal".into(), gpui2::rgba(0xa1c181ff).into()), - ("attribute".into(), gpui2::rgba(0x74ade8ff).into()), - ("link_text".into(), gpui2::rgba(0x73ade9ff).into()), - ("title".into(), gpui2::rgba(0xd07277ff).into()), - ("predictive".into(), gpui2::rgba(0x5a6a87ff).into()), - ("number".into(), gpui2::rgba(0xbf956aff).into()), - ("label".into(), gpui2::rgba(0x74ade8ff).into()), - ("variable".into(), gpui2::rgba(0xc8ccd4ff).into()), - ("boolean".into(), gpui2::rgba(0xbf956aff).into()), - ( - "punctuation.list_marker".into(), - gpui2::rgba(0xd07277ff).into(), - ), + ("attribute".into(), cyan().dark(11).into()), + ("boolean".into(), tomato().dark(11).into()), + ("comment".into(), neutral().dark(11).into()), + ("comment.doc".into(), iris().dark(12).into()), + ("constant".into(), red().dark(7).into()), + ("constructor".into(), red().dark(7).into()), + ("embedded".into(), red().dark(7).into()), + ("emphasis".into(), red().dark(7).into()), + ("emphasis.strong".into(), red().dark(7).into()), + ("enum".into(), red().dark(7).into()), + ("function".into(), red().dark(7).into()), + ("hint".into(), red().dark(7).into()), + ("keyword".into(), orange().dark(11).into()), + ("label".into(), red().dark(7).into()), + ("link_text".into(), red().dark(7).into()), + ("link_uri".into(), red().dark(7).into()), + ("number".into(), red().dark(7).into()), + ("operator".into(), red().dark(7).into()), + ("predictive".into(), red().dark(7).into()), + ("preproc".into(), red().dark(7).into()), + ("primary".into(), red().dark(7).into()), + ("property".into(), red().dark(7).into()), + ("punctuation".into(), neutral().dark(11).into()), + ("punctuation.bracket".into(), neutral().dark(11).into()), + ("punctuation.delimiter".into(), neutral().dark(11).into()), + ("punctuation.list_marker".into(), blue().dark(11).into()), + ("punctuation.special".into(), red().dark(7).into()), + ("string".into(), jade().dark(11).into()), + ("string.escape".into(), red().dark(7).into()), + ("string.regex".into(), tomato().dark(11).into()), + ("string.special".into(), red().dark(7).into()), + ("string.special.symbol".into(), red().dark(7).into()), + ("tag".into(), red().dark(7).into()), + ("text.literal".into(), red().dark(7).into()), + ("title".into(), red().dark(7).into()), + ("type".into(), red().dark(7).into()), + ("variable".into(), red().dark(7).into()), + ("variable.special".into(), red().dark(7).into()), + ("variant".into(), red().dark(7).into()), ], } } From 3189cd779eb0149485c11d5b428ac4705d79d585 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 10:52:25 -0400 Subject: [PATCH 055/156] Remove unused impl --- crates/theme2/src/default_colors.rs | 21 +-------------------- crates/ui2/src/components/tab.rs | 2 +- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 15c09e212b..cad75f9935 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,10 +1,9 @@ -use gpui2::{hsla, Hsla, Rgba}; +use gpui2::{hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, - ColorScaleStep, }; fn neutral() -> ColorScaleSet { @@ -292,24 +291,6 @@ struct DefaultColorScaleSet { dark_alpha: [&'static str; 12], } -impl DefaultColorScaleSet { - pub fn light(&self, index: ColorScaleStep) -> Hsla { - Rgba::try_from(self.light[index - 1]).unwrap().into() - } - - pub fn light_alpha(&self, index: ColorScaleStep) -> Hsla { - Rgba::try_from(self.light_alpha[index - 1]).unwrap().into() - } - - pub fn dark(&self, index: ColorScaleStep) -> Hsla { - Rgba::try_from(self.dark[index - 1]).unwrap().into() - } - - pub fn dark_alpha(&self, index: ColorScaleStep) -> Hsla { - Rgba::try_from(self.dark_alpha[index - 1]).unwrap().into() - } -} - impl From for ColorScaleSet { fn from(default: DefaultColorScaleSet) -> Self { Self::new( diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index fddd82b064..d7b52c7ab3 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::{Icon, IconColor, IconElement, Label, LabelColor}; -use gpui2::{black, red, Div, ElementId, Render, View, VisualContext}; +use gpui2::{red, Div, ElementId, Render, View, VisualContext}; #[derive(Component, Clone)] pub struct Tab { From 147db607c7243d4e769e7187964a13bb9d608a45 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 16:24:19 +0100 Subject: [PATCH 056/156] 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 51fa80ef066d28816f47d095e76e9c5ef0aede19 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 09:19:32 -0700 Subject: [PATCH 057/156] ported example app, live_kit_client2 is done --- crates/live_kit_client2/examples/test_app.rs | 299 +++++++++--------- .../live_kit_client2/src/live_kit_client2.rs | 12 +- crates/live_kit_client2/src/test.rs | 4 +- 3 files changed, 159 insertions(+), 156 deletions(-) diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs index 2147f6ab8c..ad10a4c95d 100644 --- a/crates/live_kit_client2/examples/test_app.rs +++ b/crates/live_kit_client2/examples/test_app.rs @@ -1,175 +1,178 @@ -// use std::time::Duration; -// todo!() +use std::{sync::Arc, time::Duration}; -// use futures::StreamExt; -// use gpui2::{actions, keymap_matcher::Binding, Menu, MenuItem}; -// use live_kit_client2::{ -// LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, -// }; -// use live_kit_server::token::{self, VideoGrant}; -// use log::LevelFilter; -// use simplelog::SimpleLogger; +use futures::StreamExt; +use gpui2::KeyBinding; +use live_kit_client2::{ + LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, +}; +use live_kit_server::token::{self, VideoGrant}; +use log::LevelFilter; +use serde_derive::Deserialize; +use simplelog::SimpleLogger; -// actions!(capture, [Quit]); +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] +struct Quit; fn main() { - // SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); + SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - // gpui2::App::new(()).unwrap().run(|cx| { - // #[cfg(any(test, feature = "test-support"))] - // println!("USING TEST LIVEKIT"); + gpui2::App::production(Arc::new(())).run(|cx| { + #[cfg(any(test, feature = "test-support"))] + println!("USING TEST LIVEKIT"); - // #[cfg(not(any(test, feature = "test-support")))] - // println!("USING REAL LIVEKIT"); + #[cfg(not(any(test, feature = "test-support")))] + println!("USING REAL LIVEKIT"); - // cx.platform().activate(true); - // cx.add_global_action(quit); + cx.activate(true); - // cx.add_bindings([Binding::new("cmd-q", Quit, None)]); - // cx.set_menus(vec![Menu { - // name: "Zed", - // items: vec![MenuItem::Action { - // name: "Quit", - // action: Box::new(Quit), - // os_action: None, - // }], - // }]); + cx.on_action(quit); + cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - // let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); - // let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); - // let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); + // todo!() + // cx.set_menus(vec![Menu { + // name: "Zed", + // items: vec![MenuItem::Action { + // name: "Quit", + // action: Box::new(Quit), + // os_action: None, + // }], + // }]); - // cx.spawn(|cx| async move { - // let user_a_token = token::create( - // &live_kit_key, - // &live_kit_secret, - // Some("test-participant-1"), - // VideoGrant::to_join("test-room"), - // ) - // .unwrap(); - // let room_a = Room::new(); - // room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); + let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); + let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); + let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); - // let user2_token = token::create( - // &live_kit_key, - // &live_kit_secret, - // Some("test-participant-2"), - // VideoGrant::to_join("test-room"), - // ) - // .unwrap(); - // let room_b = Room::new(); - // room_b.connect(&live_kit_url, &user2_token).await.unwrap(); + cx.spawn_on_main(|cx| async move { + let user_a_token = token::create( + &live_kit_key, + &live_kit_secret, + Some("test-participant-1"), + VideoGrant::to_join("test-room"), + ) + .unwrap(); + let room_a = Room::new(); + room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); - // let mut audio_track_updates = room_b.remote_audio_track_updates(); - // let audio_track = LocalAudioTrack::create(); - // let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); + let user2_token = token::create( + &live_kit_key, + &live_kit_secret, + Some("test-participant-2"), + VideoGrant::to_join("test-room"), + ) + .unwrap(); + let room_b = Room::new(); + room_b.connect(&live_kit_url, &user2_token).await.unwrap(); - // if let RemoteAudioTrackUpdate::Subscribed(track, _) = - // audio_track_updates.next().await.unwrap() - // { - // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - // assert_eq!(remote_tracks.len(), 1); - // assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); - // assert_eq!(track.publisher_id(), "test-participant-1"); - // } else { - // panic!("unexpected message"); - // } + let mut audio_track_updates = room_b.remote_audio_track_updates(); + let audio_track = LocalAudioTrack::create(); + let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); - // audio_track_publication.set_mute(true).await.unwrap(); + if let RemoteAudioTrackUpdate::Subscribed(track, _) = + audio_track_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks.len(), 1); + assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); + assert_eq!(track.publisher_id(), "test-participant-1"); + } else { + panic!("unexpected message"); + } - // println!("waiting for mute changed!"); - // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - // audio_track_updates.next().await.unwrap() - // { - // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - // assert_eq!(remote_tracks[0].sid(), track_id); - // assert_eq!(muted, true); - // } else { - // panic!("unexpected message"); - // } + audio_track_publication.set_mute(true).await.unwrap(); - // audio_track_publication.set_mute(false).await.unwrap(); + println!("waiting for mute changed!"); + if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + audio_track_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks[0].sid(), track_id); + assert_eq!(muted, true); + } else { + panic!("unexpected message"); + } - // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - // audio_track_updates.next().await.unwrap() - // { - // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - // assert_eq!(remote_tracks[0].sid(), track_id); - // assert_eq!(muted, false); - // } else { - // panic!("unexpected message"); - // } + audio_track_publication.set_mute(false).await.unwrap(); - // println!("Pausing for 5 seconds to test audio, make some noise!"); - // let timer = cx.background().timer(Duration::from_secs(5)); - // timer.await; - // let remote_audio_track = room_b - // .remote_audio_tracks("test-participant-1") - // .pop() - // .unwrap(); - // room_a.unpublish_track(audio_track_publication); + if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + audio_track_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks[0].sid(), track_id); + assert_eq!(muted, false); + } else { + panic!("unexpected message"); + } - // // Clear out any active speakers changed messages - // let mut next = audio_track_updates.next().await.unwrap(); - // while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { - // println!("Speakers changed: {:?}", speakers); - // next = audio_track_updates.next().await.unwrap(); - // } + println!("Pausing for 5 seconds to test audio, make some noise!"); + let timer = cx.executor().timer(Duration::from_secs(5)); + timer.await; + let remote_audio_track = room_b + .remote_audio_tracks("test-participant-1") + .pop() + .unwrap(); + room_a.unpublish_track(audio_track_publication); - // if let RemoteAudioTrackUpdate::Unsubscribed { - // publisher_id, - // track_id, - // } = next - // { - // assert_eq!(publisher_id, "test-participant-1"); - // assert_eq!(remote_audio_track.sid(), track_id); - // assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); - // } else { - // panic!("unexpected message"); - // } + // Clear out any active speakers changed messages + let mut next = audio_track_updates.next().await.unwrap(); + while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { + println!("Speakers changed: {:?}", speakers); + next = audio_track_updates.next().await.unwrap(); + } - // let mut video_track_updates = room_b.remote_video_track_updates(); - // let displays = room_a.display_sources().await.unwrap(); - // let display = displays.into_iter().next().unwrap(); + if let RemoteAudioTrackUpdate::Unsubscribed { + publisher_id, + track_id, + } = next + { + assert_eq!(publisher_id, "test-participant-1"); + assert_eq!(remote_audio_track.sid(), track_id); + assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); + } else { + panic!("unexpected message"); + } - // let local_video_track = LocalVideoTrack::screen_share_for_display(&display); - // let local_video_track_publication = - // room_a.publish_video_track(local_video_track).await.unwrap(); + let mut video_track_updates = room_b.remote_video_track_updates(); + let displays = room_a.display_sources().await.unwrap(); + let display = displays.into_iter().next().unwrap(); - // if let RemoteVideoTrackUpdate::Subscribed(track) = - // video_track_updates.next().await.unwrap() - // { - // let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); - // assert_eq!(remote_video_tracks.len(), 1); - // assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); - // assert_eq!(track.publisher_id(), "test-participant-1"); - // } else { - // panic!("unexpected message"); - // } + let local_video_track = LocalVideoTrack::screen_share_for_display(&display); + let local_video_track_publication = + room_a.publish_video_track(local_video_track).await.unwrap(); - // let remote_video_track = room_b - // .remote_video_tracks("test-participant-1") - // .pop() - // .unwrap(); - // room_a.unpublish_track(local_video_track_publication); - // if let RemoteVideoTrackUpdate::Unsubscribed { - // publisher_id, - // track_id, - // } = video_track_updates.next().await.unwrap() - // { - // assert_eq!(publisher_id, "test-participant-1"); - // assert_eq!(remote_video_track.sid(), track_id); - // assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); - // } else { - // panic!("unexpected message"); - // } + if let RemoteVideoTrackUpdate::Subscribed(track) = + video_track_updates.next().await.unwrap() + { + let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); + assert_eq!(remote_video_tracks.len(), 1); + assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); + assert_eq!(track.publisher_id(), "test-participant-1"); + } else { + panic!("unexpected message"); + } - // cx.platform().quit(); - // }) - // .detach(); - // }); + let remote_video_track = room_b + .remote_video_tracks("test-participant-1") + .pop() + .unwrap(); + room_a.unpublish_track(local_video_track_publication); + if let RemoteVideoTrackUpdate::Unsubscribed { + publisher_id, + track_id, + } = video_track_updates.next().await.unwrap() + { + assert_eq!(publisher_id, "test-participant-1"); + assert_eq!(remote_video_track.sid(), track_id); + assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); + } else { + panic!("unexpected message"); + } + + cx.update(|cx| cx.quit()).ok(); + }) + .detach(); + }); } -// fn quit(_: &Quit, cx: &mut gpui2::AppContext) { -// cx.platform().quit(); -// } +fn quit(_: &Quit, cx: &mut gpui2::AppContext) { + cx.quit(); +} diff --git a/crates/live_kit_client2/src/live_kit_client2.rs b/crates/live_kit_client2/src/live_kit_client2.rs index 35682382e9..47cc3873ff 100644 --- a/crates/live_kit_client2/src/live_kit_client2.rs +++ b/crates/live_kit_client2/src/live_kit_client2.rs @@ -1,11 +1,11 @@ -// #[cfg(not(any(test, feature = "test-support")))] +#[cfg(not(any(test, feature = "test-support")))] pub mod prod; -// #[cfg(not(any(test, feature = "test-support")))] +#[cfg(not(any(test, feature = "test-support")))] pub use prod::*; -// #[cfg(any(test, feature = "test-support"))] -// pub mod test; +#[cfg(any(test, feature = "test-support"))] +pub mod test; -// #[cfg(any(test, feature = "test-support"))] -// pub use test::*; +#[cfg(any(test, feature = "test-support"))] +pub use test::*; diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index 7185c11fa8..535ab20afb 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/crates/live_kit_client2/src/test.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Result, Context}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; @@ -364,7 +364,7 @@ impl Room { let token = token.to_string(); async move { let server = TestServer::get(&url)?; - server.join_room(token.clone(), this.clone()).await?; + server.join_room(token.clone(), this.clone()).await.context("room join")?; *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; Ok(()) } From c3a8bab4d2d7ca067891355da31a6b1e60a020c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 17:21:58 +0100 Subject: [PATCH 058/156] 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 1568ecbe1ee6c64a54597f3d02a31dc591f05624 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 09:29:54 -0700 Subject: [PATCH 059/156] Add back room code to call2 --- crates/call2/src/participant.rs | 29 +- crates/call2/src/room.rs | 692 ++++++++++++++-------------- crates/live_kit_client2/src/prod.rs | 4 + crates/live_kit_client2/src/test.rs | 8 +- 4 files changed, 350 insertions(+), 383 deletions(-) diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index a1837e3ad0..9fe212e776 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -1,10 +1,12 @@ use anyhow::{anyhow, Result}; use client2::ParticipantIndex; use client2::{proto, User}; +use collections::HashMap; use gpui2::WeakModel; pub use live_kit_client2::Frame; +use live_kit_client2::{RemoteAudioTrack, RemoteVideoTrack}; use project2::Project; -use std::{fmt, sync::Arc}; +use std::sync::Arc; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ParticipantLocation { @@ -45,27 +47,6 @@ pub struct RemoteParticipant { pub participant_index: ParticipantIndex, pub muted: bool, pub speaking: bool, - // pub video_tracks: HashMap>, - // pub audio_tracks: HashMap>, -} - -#[derive(Clone)] -pub struct RemoteVideoTrack { - pub(crate) live_kit_track: Arc, -} - -unsafe impl Send for RemoteVideoTrack {} -// todo!("remove this sync because it's not legit") -unsafe impl Sync for RemoteVideoTrack {} - -impl fmt::Debug for RemoteVideoTrack { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RemoteVideoTrack").finish() - } -} - -impl RemoteVideoTrack { - pub fn frames(&self) -> async_broadcast::Receiver { - self.live_kit_track.frames() - } + pub video_tracks: HashMap>, + pub audio_tracks: HashMap>, } diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index f0e0b8de17..cf98db015b 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1,9 +1,6 @@ -#![allow(dead_code, unused)] -// todo!() - use crate::{ call_settings::CallSettings, - participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack}, + participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, IncomingCall, }; use anyhow::{anyhow, Result}; @@ -19,12 +16,15 @@ use gpui2::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language2::LanguageRegistry; -use live_kit_client2::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate}; +use live_kit_client2::{ + LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, + RemoteVideoTrackUpdate, +}; use postage::{sink::Sink, stream::Stream, watch}; use project2::Project; use settings2::Settings; -use std::{future::Future, sync::Arc, time::Duration}; -use util::{ResultExt, TryFutureExt}; +use std::{future::Future, mem, sync::Arc, time::Duration}; +use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -95,15 +95,14 @@ impl Room { #[cfg(any(test, feature = "test-support"))] pub fn is_connected(&self) -> bool { - false - // if let Some(live_kit) = self.live_kit.as_ref() { - // matches!( - // *live_kit.room.status().borrow(), - // live_kit_client::ConnectionState::Connected { .. } - // ) - // } else { - // false - // } + if let Some(live_kit) = self.live_kit.as_ref() { + matches!( + *live_kit.room.status().borrow(), + live_kit_client2::ConnectionState::Connected { .. } + ) + } else { + false + } } fn new( @@ -423,7 +422,7 @@ impl Room { self.pending_participants.clear(); self.participant_user_ids.clear(); self.client_subscriptions.clear(); - // self.live_kit.take(); + self.live_kit.take(); self.pending_room_update.take(); self.maintain_connection.take(); } @@ -799,43 +798,43 @@ impl Room { location, muted: true, speaking: false, - // video_tracks: Default::default(), - // audio_tracks: Default::default(), + video_tracks: Default::default(), + audio_tracks: Default::default(), }, ); Audio::play_sound(Sound::Joined, cx); - // if let Some(live_kit) = this.live_kit.as_ref() { - // let video_tracks = - // live_kit.room.remote_video_tracks(&user.id.to_string()); - // let audio_tracks = - // live_kit.room.remote_audio_tracks(&user.id.to_string()); - // let publications = live_kit - // .room - // .remote_audio_track_publications(&user.id.to_string()); + if let Some(live_kit) = this.live_kit.as_ref() { + let video_tracks = + live_kit.room.remote_video_tracks(&user.id.to_string()); + let audio_tracks = + live_kit.room.remote_audio_tracks(&user.id.to_string()); + let publications = live_kit + .room + .remote_audio_track_publications(&user.id.to_string()); - // for track in video_tracks { - // this.remote_video_track_updated( - // RemoteVideoTrackUpdate::Subscribed(track), - // cx, - // ) - // .log_err(); - // } + for track in video_tracks { + this.remote_video_track_updated( + RemoteVideoTrackUpdate::Subscribed(track), + cx, + ) + .log_err(); + } - // for (track, publication) in - // audio_tracks.iter().zip(publications.iter()) - // { - // this.remote_audio_track_updated( - // RemoteAudioTrackUpdate::Subscribed( - // track.clone(), - // publication.clone(), - // ), - // cx, - // ) - // .log_err(); - // } - // } + for (track, publication) in + audio_tracks.iter().zip(publications.iter()) + { + this.remote_audio_track_updated( + RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + ), + cx, + ) + .log_err(); + } + } } } @@ -923,7 +922,6 @@ impl Room { change: RemoteVideoTrackUpdate, cx: &mut ModelContext, ) -> Result<()> { - todo!(); match change { RemoteVideoTrackUpdate::Subscribed(track) => { let user_id = track.publisher_id().parse()?; @@ -932,12 +930,7 @@ impl Room { .remote_participants .get_mut(&user_id) .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; - // participant.video_tracks.insert( - // track_id.clone(), - // Arc::new(RemoteVideoTrack { - // live_kit_track: track, - // }), - // ); + participant.video_tracks.insert(track_id.clone(), track); cx.emit(Event::RemoteVideoTracksChanged { participant_id: participant.peer_id, }); @@ -951,7 +944,7 @@ impl Room { .remote_participants .get_mut(&user_id) .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; - // participant.video_tracks.remove(&track_id); + participant.video_tracks.remove(&track_id); cx.emit(Event::RemoteVideoTracksChanged { participant_id: participant.peer_id, }); @@ -981,65 +974,61 @@ impl Room { participant.speaking = false; } } - // todo!() - // if let Some(id) = self.client.user_id() { - // if let Some(room) = &mut self.live_kit { - // if let Ok(_) = speaker_ids.binary_search(&id) { - // room.speaking = true; - // } else { - // room.speaking = false; - // } - // } - // } + if let Some(id) = self.client.user_id() { + if let Some(room) = &mut self.live_kit { + if let Ok(_) = speaker_ids.binary_search(&id) { + room.speaking = true; + } else { + room.speaking = false; + } + } + } cx.notify(); } RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { - // todo!() - // let mut found = false; - // for participant in &mut self.remote_participants.values_mut() { - // for track in participant.audio_tracks.values() { - // if track.sid() == track_id { - // found = true; - // break; - // } - // } - // if found { - // participant.muted = muted; - // break; - // } - // } + let mut found = false; + for participant in &mut self.remote_participants.values_mut() { + for track in participant.audio_tracks.values() { + if track.sid() == track_id { + found = true; + break; + } + } + if found { + participant.muted = muted; + break; + } + } cx.notify(); } RemoteAudioTrackUpdate::Subscribed(track, publication) => { - // todo!() - // let user_id = track.publisher_id().parse()?; - // let track_id = track.sid().to_string(); - // let participant = self - // .remote_participants - // .get_mut(&user_id) - // .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; - // // participant.audio_tracks.insert(track_id.clone(), track); - // participant.muted = publication.is_muted(); + let user_id = track.publisher_id().parse()?; + let track_id = track.sid().to_string(); + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; + participant.audio_tracks.insert(track_id.clone(), track); + participant.muted = publication.is_muted(); - // cx.emit(Event::RemoteAudioTracksChanged { - // participant_id: participant.peer_id, - // }); + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); } RemoteAudioTrackUpdate::Unsubscribed { publisher_id, track_id, } => { - // todo!() - // let user_id = publisher_id.parse()?; - // let participant = self - // .remote_participants - // .get_mut(&user_id) - // .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; - // participant.audio_tracks.remove(&track_id); - // cx.emit(Event::RemoteAudioTracksChanged { - // participant_id: participant.peer_id, - // }); + let user_id = publisher_id.parse()?; + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; + participant.audio_tracks.remove(&track_id); + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); } } @@ -1220,278 +1209,269 @@ impl Room { } pub fn is_screen_sharing(&self) -> bool { - todo!() - // self.live_kit.as_ref().map_or(false, |live_kit| { - // !matches!(live_kit.screen_track, LocalTrack::None) - // }) + self.live_kit.as_ref().map_or(false, |live_kit| { + !matches!(live_kit.screen_track, LocalTrack::None) + }) } pub fn is_sharing_mic(&self) -> bool { - todo!() - // self.live_kit.as_ref().map_or(false, |live_kit| { - // !matches!(live_kit.microphone_track, LocalTrack::None) - // }) + self.live_kit.as_ref().map_or(false, |live_kit| { + !matches!(live_kit.microphone_track, LocalTrack::None) + }) } pub fn is_muted(&self, cx: &AppContext) -> bool { - todo!() - // self.live_kit - // .as_ref() - // .and_then(|live_kit| match &live_kit.microphone_track { - // LocalTrack::None => Some(Self::mute_on_join(cx)), - // LocalTrack::Pending { muted, .. } => Some(*muted), - // LocalTrack::Published { muted, .. } => Some(*muted), - // }) - // .unwrap_or(false) + self.live_kit + .as_ref() + .and_then(|live_kit| match &live_kit.microphone_track { + LocalTrack::None => Some(Self::mute_on_join(cx)), + LocalTrack::Pending { muted, .. } => Some(*muted), + LocalTrack::Published { muted, .. } => Some(*muted), + }) + .unwrap_or(false) } pub fn is_speaking(&self) -> bool { - todo!() - // self.live_kit - // .as_ref() - // .map_or(false, |live_kit| live_kit.speaking) + self.live_kit + .as_ref() + .map_or(false, |live_kit| live_kit.speaking) } pub fn is_deafened(&self) -> Option { - // self.live_kit.as_ref().map(|live_kit| live_kit.deafened) - todo!() + self.live_kit.as_ref().map(|live_kit| live_kit.deafened) } #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { - todo!() - // if self.status.is_offline() { - // return Task::ready(Err(anyhow!("room is offline"))); - // } else if self.is_sharing_mic() { - // return Task::ready(Err(anyhow!("microphone was already shared"))); - // } + if self.status.is_offline() { + return Task::ready(Err(anyhow!("room is offline"))); + } else if self.is_sharing_mic() { + return Task::ready(Err(anyhow!("microphone was already shared"))); + } - // let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { - // let publish_id = post_inc(&mut live_kit.next_publish_id); - // live_kit.microphone_track = LocalTrack::Pending { - // publish_id, - // muted: false, - // }; - // cx.notify(); - // publish_id - // } else { - // return Task::ready(Err(anyhow!("live-kit was not initialized"))); - // }; + let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { + let publish_id = post_inc(&mut live_kit.next_publish_id); + live_kit.microphone_track = LocalTrack::Pending { + publish_id, + muted: false, + }; + cx.notify(); + publish_id + } else { + return Task::ready(Err(anyhow!("live-kit was not initialized"))); + }; - // cx.spawn(move |this, mut cx| async move { - // let publish_track = async { - // let track = LocalAudioTrack::create(); - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, _| { - // this.live_kit - // .as_ref() - // .map(|live_kit| live_kit.room.publish_audio_track(track)) - // })? - // .ok_or_else(|| anyhow!("live-kit was not initialized"))? - // .await - // }; + cx.spawn(move |this, mut cx| async move { + let publish_track = async { + let track = LocalAudioTrack::create(); + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, _| { + this.live_kit + .as_ref() + .map(|live_kit| live_kit.room.publish_audio_track(track)) + })? + .ok_or_else(|| anyhow!("live-kit was not initialized"))? + .await + }; - // let publication = publish_track.await; - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, cx| { - // let live_kit = this - // .live_kit - // .as_mut() - // .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let publication = publish_track.await; + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - // let (canceled, muted) = if let LocalTrack::Pending { - // publish_id: cur_publish_id, - // muted, - // } = &live_kit.microphone_track - // { - // (*cur_publish_id != publish_id, *muted) - // } else { - // (true, false) - // }; + let (canceled, muted) = if let LocalTrack::Pending { + publish_id: cur_publish_id, + muted, + } = &live_kit.microphone_track + { + (*cur_publish_id != publish_id, *muted) + } else { + (true, false) + }; - // match publication { - // Ok(publication) => { - // if canceled { - // live_kit.room.unpublish_track(publication); - // } else { - // if muted { - // cx.executor().spawn(publication.set_mute(muted)).detach(); - // } - // live_kit.microphone_track = LocalTrack::Published { - // track_publication: publication, - // muted, - // }; - // cx.notify(); - // } - // Ok(()) - // } - // Err(error) => { - // if canceled { - // Ok(()) - // } else { - // live_kit.microphone_track = LocalTrack::None; - // cx.notify(); - // Err(error) - // } - // } - // } - // })? - // }) + match publication { + Ok(publication) => { + if canceled { + live_kit.room.unpublish_track(publication); + } else { + if muted { + cx.executor().spawn(publication.set_mute(muted)).detach(); + } + live_kit.microphone_track = LocalTrack::Published { + track_publication: publication, + muted, + }; + cx.notify(); + } + Ok(()) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.microphone_track = LocalTrack::None; + cx.notify(); + Err(error) + } + } + } + })? + }) } pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> { - todo!() - // if self.status.is_offline() { - // return Task::ready(Err(anyhow!("room is offline"))); - // } else if self.is_screen_sharing() { - // return Task::ready(Err(anyhow!("screen was already shared"))); - // } + if self.status.is_offline() { + return Task::ready(Err(anyhow!("room is offline"))); + } else if self.is_screen_sharing() { + return Task::ready(Err(anyhow!("screen was already shared"))); + } - // let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { - // let publish_id = post_inc(&mut live_kit.next_publish_id); - // live_kit.screen_track = LocalTrack::Pending { - // publish_id, - // muted: false, - // }; - // cx.notify(); - // (live_kit.room.display_sources(), publish_id) - // } else { - // return Task::ready(Err(anyhow!("live-kit was not initialized"))); - // }; + let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { + let publish_id = post_inc(&mut live_kit.next_publish_id); + live_kit.screen_track = LocalTrack::Pending { + publish_id, + muted: false, + }; + cx.notify(); + (live_kit.room.display_sources(), publish_id) + } else { + return Task::ready(Err(anyhow!("live-kit was not initialized"))); + }; - // cx.spawn(move |this, mut cx| async move { - // let publish_track = async { - // let displays = displays.await?; - // let display = displays - // .first() - // .ok_or_else(|| anyhow!("no display found"))?; - // let track = LocalVideoTrack::screen_share_for_display(&display); - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, _| { - // this.live_kit - // .as_ref() - // .map(|live_kit| live_kit.room.publish_video_track(track)) - // })? - // .ok_or_else(|| anyhow!("live-kit was not initialized"))? - // .await - // }; + cx.spawn_on_main(move |this, mut cx| async move { + let publish_track = async { + let displays = displays.await?; + let display = displays + .first() + .ok_or_else(|| anyhow!("no display found"))?; + let track = LocalVideoTrack::screen_share_for_display(&display); + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, _| { + this.live_kit + .as_ref() + .map(|live_kit| live_kit.room.publish_video_track(track)) + })? + .ok_or_else(|| anyhow!("live-kit was not initialized"))? + .await + }; - // let publication = publish_track.await; - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, cx| { - // let live_kit = this - // .live_kit - // .as_mut() - // .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let publication = publish_track.await; + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - // let (canceled, muted) = if let LocalTrack::Pending { - // publish_id: cur_publish_id, - // muted, - // } = &live_kit.screen_track - // { - // (*cur_publish_id != publish_id, *muted) - // } else { - // (true, false) - // }; + let (canceled, muted) = if let LocalTrack::Pending { + publish_id: cur_publish_id, + muted, + } = &live_kit.screen_track + { + (*cur_publish_id != publish_id, *muted) + } else { + (true, false) + }; - // match publication { - // Ok(publication) => { - // if canceled { - // live_kit.room.unpublish_track(publication); - // } else { - // if muted { - // cx.executor().spawn(publication.set_mute(muted)).detach(); - // } - // live_kit.screen_track = LocalTrack::Published { - // track_publication: publication, - // muted, - // }; - // cx.notify(); - // } + match publication { + Ok(publication) => { + if canceled { + live_kit.room.unpublish_track(publication); + } else { + if muted { + cx.executor().spawn(publication.set_mute(muted)).detach(); + } + live_kit.screen_track = LocalTrack::Published { + track_publication: publication, + muted, + }; + cx.notify(); + } - // Audio::play_sound(Sound::StartScreenshare, cx); + Audio::play_sound(Sound::StartScreenshare, cx); - // Ok(()) - // } - // Err(error) => { - // if canceled { - // Ok(()) - // } else { - // live_kit.screen_track = LocalTrack::None; - // cx.notify(); - // Err(error) - // } - // } - // } - // })? - // }) + Ok(()) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.screen_track = LocalTrack::None; + cx.notify(); + Err(error) + } + } + } + })? + }) } pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { - todo!() - // let should_mute = !self.is_muted(cx); - // if let Some(live_kit) = self.live_kit.as_mut() { - // if matches!(live_kit.microphone_track, LocalTrack::None) { - // return Ok(self.share_microphone(cx)); - // } + let should_mute = !self.is_muted(cx); + if let Some(live_kit) = self.live_kit.as_mut() { + if matches!(live_kit.microphone_track, LocalTrack::None) { + return Ok(self.share_microphone(cx)); + } - // let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; - // live_kit.muted_by_user = should_mute; + let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; + live_kit.muted_by_user = should_mute; - // if old_muted == true && live_kit.deafened == true { - // if let Some(task) = self.toggle_deafen(cx).ok() { - // task.detach(); - // } - // } + if old_muted == true && live_kit.deafened == true { + if let Some(task) = self.toggle_deafen(cx).ok() { + task.detach(); + } + } - // Ok(ret_task) - // } else { - // Err(anyhow!("LiveKit not started")) - // } + Ok(ret_task) + } else { + Err(anyhow!("LiveKit not started")) + } } pub fn toggle_deafen(&mut self, cx: &mut ModelContext) -> Result>> { - todo!() - // if let Some(live_kit) = self.live_kit.as_mut() { - // (*live_kit).deafened = !live_kit.deafened; + if let Some(live_kit) = self.live_kit.as_mut() { + (*live_kit).deafened = !live_kit.deafened; - // let mut tasks = Vec::with_capacity(self.remote_participants.len()); - // // Context notification is sent within set_mute itself. - // let mut mute_task = None; - // // When deafening, mute user's mic as well. - // // When undeafening, unmute user's mic unless it was manually muted prior to deafening. - // if live_kit.deafened || !live_kit.muted_by_user { - // mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); - // }; - // for participant in self.remote_participants.values() { - // for track in live_kit - // .room - // .remote_audio_track_publications(&participant.user.id.to_string()) - // { - // let deafened = live_kit.deafened; - // tasks.push( - // cx.executor() - // .spawn_on_main(move || track.set_enabled(!deafened)), - // ); - // } - // } + let mut tasks = Vec::with_capacity(self.remote_participants.len()); + // Context notification is sent within set_mute itself. + let mut mute_task = None; + // When deafening, mute user's mic as well. + // When undeafening, unmute user's mic unless it was manually muted prior to deafening. + if live_kit.deafened || !live_kit.muted_by_user { + mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); + }; + for participant in self.remote_participants.values() { + for track in live_kit + .room + .remote_audio_track_publications(&participant.user.id.to_string()) + { + let deafened = live_kit.deafened; + tasks.push( + cx.executor() + .spawn_on_main(move || track.set_enabled(!deafened)), + ); + } + } - // Ok(cx.executor().spawn_on_main(|| async { - // if let Some(mute_task) = mute_task { - // mute_task.await?; - // } - // for task in tasks { - // task.await?; - // } - // Ok(()) - // })) - // } else { - // Err(anyhow!("LiveKit not started")) - // } + Ok(cx.executor().spawn_on_main(|| async { + if let Some(mute_task) = mute_task { + mute_task.await?; + } + for task in tasks { + task.await?; + } + Ok(()) + })) + } else { + Err(anyhow!("LiveKit not started")) + } } pub fn unshare_screen(&mut self, cx: &mut ModelContext) -> Result<()> { @@ -1499,37 +1479,35 @@ impl Room { return Err(anyhow!("room is offline")); } - todo!() - // let live_kit = self - // .live_kit - // .as_mut() - // .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - // match mem::take(&mut live_kit.screen_track) { - // LocalTrack::None => Err(anyhow!("screen was not shared")), - // LocalTrack::Pending { .. } => { - // cx.notify(); - // Ok(()) - // } - // LocalTrack::Published { - // track_publication, .. - // } => { - // live_kit.room.unpublish_track(track_publication); - // cx.notify(); + let live_kit = self + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + match mem::take(&mut live_kit.screen_track) { + LocalTrack::None => Err(anyhow!("screen was not shared")), + LocalTrack::Pending { .. } => { + cx.notify(); + Ok(()) + } + LocalTrack::Published { + track_publication, .. + } => { + live_kit.room.unpublish_track(track_publication); + cx.notify(); - // Audio::play_sound(Sound::StopScreenshare, cx); - // Ok(()) - // } - // } + Audio::play_sound(Sound::StopScreenshare, cx); + Ok(()) + } + } } #[cfg(any(test, feature = "test-support"))] pub fn set_display_sources(&self, sources: Vec) { - todo!() - // self.live_kit - // .as_ref() - // .unwrap() - // .room - // .set_display_sources(sources); + self.live_kit + .as_ref() + .unwrap() + .room + .set_display_sources(sources); } } diff --git a/crates/live_kit_client2/src/prod.rs b/crates/live_kit_client2/src/prod.rs index 65ed8b754f..b2b83e95fc 100644 --- a/crates/live_kit_client2/src/prod.rs +++ b/crates/live_kit_client2/src/prod.rs @@ -499,6 +499,10 @@ impl Room { rx, ) } + + pub fn set_display_sources(&self, _: Vec) { + unreachable!("This is a test-only function") + } } impl Drop for Room { diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index 535ab20afb..f1c3d39b8e 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/crates/live_kit_client2/src/test.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result, Context}; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; @@ -364,7 +364,10 @@ impl Room { let token = token.to_string(); async move { let server = TestServer::get(&url)?; - server.join_room(token.clone(), this.clone()).await.context("room join")?; + server + .join_room(token.clone(), this.clone()) + .await + .context("room join")?; *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; Ok(()) } @@ -547,6 +550,7 @@ impl LocalAudioTrack { } } +#[derive(Debug)] pub struct RemoteVideoTrack { sid: Sid, publisher_id: Sid, From 7d300d3f5b416532c9583f38d3160ec90e4a17e4 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 1 Nov 2023 12:34:26 -0400 Subject: [PATCH 060/156] v0.112.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca8767846f..3c6cc30645 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10821,7 +10821,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.111.0" +version = "0.112.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 250a1814aa..c9012a3a14 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.111.0" +version = "0.112.0" publish = false [lib] From 8dafd5f1f31771f1b816c2027c76d0deafc62767 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 12:43:25 -0400 Subject: [PATCH 061/156] Allow ListHeader to take a `meta` --- assets/icons/at-sign.svg | 1 + assets/icons/bell-off.svg | 1 + assets/icons/bell-ring.svg | 1 + assets/icons/bell.svg | 9 +- assets/icons/mail-open.svg | 1 + crates/ui2/src/components/list.rs | 84 +++++++++++-------- .../ui2/src/components/notifications_panel.rs | 20 +++-- crates/ui2/src/elements/icon.rs | 13 ++- crates/ui2/src/static_data.rs | 49 ++++++----- 9 files changed, 105 insertions(+), 74 deletions(-) create mode 100644 assets/icons/at-sign.svg create mode 100644 assets/icons/bell-off.svg create mode 100644 assets/icons/bell-ring.svg create mode 100644 assets/icons/mail-open.svg diff --git a/assets/icons/at-sign.svg b/assets/icons/at-sign.svg new file mode 100644 index 0000000000..5adac38f62 --- /dev/null +++ b/assets/icons/at-sign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bell-off.svg b/assets/icons/bell-off.svg new file mode 100644 index 0000000000..db1021f2d3 --- /dev/null +++ b/assets/icons/bell-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bell-ring.svg b/assets/icons/bell-ring.svg new file mode 100644 index 0000000000..da51fdc5be --- /dev/null +++ b/assets/icons/bell-ring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bell.svg b/assets/icons/bell.svg index ea1c6dd42e..4c7d5472db 100644 --- a/assets/icons/bell.svg +++ b/assets/icons/bell.svg @@ -1,8 +1 @@ - - - + \ No newline at end of file diff --git a/assets/icons/mail-open.svg b/assets/icons/mail-open.svg new file mode 100644 index 0000000000..b63915bd73 --- /dev/null +++ b/assets/icons/mail-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index ebc442afdc..ad7ec2214f 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -15,12 +15,20 @@ pub enum ListItemVariant { Inset, } +pub enum ListHeaderMeta { + // TODO: These should be IconButtons + Tools(Vec), + // TODO: This should be a button + Button(Label), + Text(Label), +} + #[derive(Component)] pub struct ListHeader { label: SharedString, left_icon: Option, + meta: Option, variant: ListItemVariant, - state: InteractionState, toggleable: Toggleable, } @@ -29,8 +37,8 @@ impl ListHeader { Self { label: label.into(), left_icon: None, + meta: None, variant: ListItemVariant::default(), - state: InteractionState::default(), toggleable: Toggleable::Toggleable(ToggleState::Toggled), } } @@ -50,8 +58,8 @@ impl ListHeader { self } - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; + pub fn meta(mut self, meta: Option) -> Self { + self.meta = meta; self } @@ -74,34 +82,37 @@ impl ListHeader { } } - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let is_toggleable = self.toggleable != Toggleable::NotToggleable; let is_toggled = self.toggleable.is_toggled(); let disclosure_control = self.disclosure_control(); + let meta = match self.meta { + Some(ListHeaderMeta::Tools(icons)) => div().child( + h_stack() + .gap_2() + .items_center() + .children(icons.into_iter().map(|i| { + IconElement::new(i) + .color(IconColor::Muted) + .size(IconSize::Small) + })), + ), + Some(ListHeaderMeta::Button(label)) => div().child(label), + Some(ListHeaderMeta::Text(label)) => div().child(label), + None => div(), + }; + h_stack() .flex_1() .w_full() .bg(cx.theme().colors().surface) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(cx.theme().colors().border_focused) - }) + // TODO: Add focus state + // .when(self.state == InteractionState::Focused, |this| { + // this.border() + // .border_color(cx.theme().colors().border_focused) + // }) .relative() .child( div() @@ -109,22 +120,28 @@ impl ListHeader { .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .flex() .flex_1() + .items_center() + .justify_between() .w_full() .gap_1() - .items_center() .child( - div() - .flex() + h_stack() .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child(Label::new(self.label.clone()).color(LabelColor::Muted)), + .child( + div() + .flex() + .gap_1() + .items_center() + .children(self.left_icon.map(|i| { + IconElement::new(i) + .color(IconColor::Muted) + .size(IconSize::Small) + })) + .child(Label::new(self.label.clone()).color(LabelColor::Muted)), + ) + .child(disclosure_control), ) - .child(disclosure_control), + .child(meta), ) } } @@ -593,6 +610,7 @@ impl List { }; v_stack() + .w_full() .py_1() .children(self.header.map(|header| header.toggleable(self.toggleable))) .child(list_content) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 10b0e07af6..c102a2cf57 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, static_new_notification_items, static_read_notification_items}; +use crate::{prelude::*, static_new_notification_items, Icon, ListHeaderMeta}; use crate::{List, ListHeader}; #[derive(Component)] @@ -28,14 +28,16 @@ impl NotificationsPanel { .overflow_y_scroll() .child( List::new(static_new_notification_items()) - .header(ListHeader::new("NEW").toggle(ToggleState::Toggled)) - .toggle(ToggleState::Toggled), - ) - .child( - List::new(static_read_notification_items()) - .header(ListHeader::new("EARLIER").toggle(ToggleState::Toggled)) - .empty_message("No new notifications") - .toggle(ToggleState::Toggled), + .toggle(ToggleState::Toggled) + .header( + ListHeader::new("Notifications") + .toggle(ToggleState::Toggled) + .meta(Some(ListHeaderMeta::Tools(vec![ + Icon::AtSign, + Icon::BellOff, + Icon::MailOpen, + ]))), + ), ), ) } diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index f3d612562f..eef80cb5ad 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -40,7 +40,7 @@ impl IconColor { } } -#[derive(Debug, Default, PartialEq, Copy, Clone, EnumIter)] +#[derive(Debug, PartialEq, Copy, Clone, EnumIter)] pub enum Icon { Ai, ArrowLeft, @@ -67,7 +67,6 @@ pub enum Icon { Folder, FolderOpen, FolderX, - #[default] Hash, InlayHint, MagicWand, @@ -89,6 +88,11 @@ pub enum Icon { XCircle, Copilot, Envelope, + Bell, + BellOff, + BellRing, + MailOpen, + AtSign, } impl Icon { @@ -140,6 +144,11 @@ impl Icon { Icon::XCircle => "icons/error.svg", Icon::Copilot => "icons/copilot.svg", Icon::Envelope => "icons/feedback.svg", + Icon::Bell => "icons/bell.svg", + Icon::BellOff => "icons/bell-off.svg", + Icon::BellRing => "icons/bell-ring.svg", + Icon::MailOpen => "icons/mail-open.svg", + Icon::AtSign => "icons/at-sign.svg", } } } diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 7062c81954..ebbc89832c 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -7,9 +7,10 @@ use theme2::ActiveTheme; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, - HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, - Livestream, MicStatus, ModifierKeys, PaletteItem, Player, PlayerCallStatus, - PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, + HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListHeaderMeta, + ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, + PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, + VideoStatus, }; use crate::{HighlightedText, ListDetailsEntry}; @@ -327,25 +328,29 @@ pub fn static_players_with_call_status() -> Vec { pub fn static_new_notification_items() -> Vec> { vec![ - ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.") - .meta("4 people in stream."), - ListDetailsEntry::new("nathansobo accepted your contact request."), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_read_notification_items() -> Vec> { - vec![ - ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ - Button::new("Decline"), - Button::new("Accept").variant(crate::ButtonVariant::Filled), - ]), - ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") - .seen(true) - .meta("This stream has ended."), - ListDetailsEntry::new("as-cii accepted your contact request."), + ListItem::Header(ListSubHeader::new("New")), + ListItem::Details( + ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.") + .meta("4 people in stream."), + ), + ListItem::Details(ListDetailsEntry::new( + "nathansobo accepted your contact request.", + )), + ListItem::Header(ListSubHeader::new("Earlier")), + ListItem::Details( + ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ + Button::new("Decline"), + Button::new("Accept").variant(crate::ButtonVariant::Filled), + ]), + ), + ListItem::Details( + ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") + .seen(true) + .meta("This stream has ended."), + ), + ListItem::Details(ListDetailsEntry::new( + "as-cii accepted your contact request.", + )), ] .into_iter() .map(From::from) From b910bbf0021002b9dee2a65e630194df73eae490 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 18:11:12 +0100 Subject: [PATCH 062/156] Add `ui_font_size` setting (#3199) This PR adds a new `ui_font_size` setting that can be used to control the scale of the entire UI. We use the value in this setting to set the base rem size of the window. Release Notes: - N/A --- Cargo.lock | 1 + crates/gpui2/src/window.rs | 6 ++++ crates/storybook2/src/storybook2.rs | 7 +++- crates/theme2/src/settings.rs | 12 +++++-- crates/ui2/Cargo.toml | 1 + crates/ui2/src/components/icon_button.rs | 6 ++-- crates/ui2/src/components/workspace.rs | 42 ++++++++++++------------ crates/ui2/src/elements/button.rs | 10 +++--- crates/ui2/src/elements/icon.rs | 6 ++-- crates/ui2/src/elements/label.rs | 4 +-- crates/ui2/src/prelude.rs | 11 +------ crates/ui2/src/settings.rs | 2 -- 12 files changed, 58 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea10948b5a..755f4440d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9666,6 +9666,7 @@ dependencies = [ "itertools 0.11.0", "rand 0.8.5", "serde", + "settings2", "smallvec", "strum", "theme2", diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 3997a3197f..7223826ab0 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -541,6 +541,12 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.rem_size } + /// Sets the size of an em for the base font of the application. Adjusting this value allows the + /// UI to scale, just like zooming a web page. + pub fn set_rem_size(&mut self, rem_size: impl Into) { + self.window.rem_size = rem_size.into(); + } + /// The line height associated with the current text style. pub fn line_height(&self) -> Pixels { let rem_size = self.rem_size(); diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 411fe18071..3b5722732b 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -81,7 +81,12 @@ fn main() { }), ..Default::default() }, - move |cx| cx.build_view(|cx| StoryWrapper::new(selector.story(cx))), + move |cx| { + let theme_settings = ThemeSettings::get_global(cx); + cx.set_rem_size(theme_settings.ui_font_size); + + cx.build_view(|cx| StoryWrapper::new(selector.story(cx))) + }, ); cx.activate(true); diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index bad00ee196..c8d2b52273 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -17,6 +17,7 @@ const MIN_LINE_HEIGHT: f32 = 1.0; #[derive(Clone)] pub struct ThemeSettings { + pub ui_font_size: Pixels, pub buffer_font: Font, pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, @@ -28,6 +29,8 @@ pub struct AdjustedBufferFontSize(Option); #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { + #[serde(default)] + pub ui_font_size: Option, #[serde(default)] pub buffer_font_family: Option, #[serde(default)] @@ -115,6 +118,7 @@ impl settings2::Settings for ThemeSettings { let themes = cx.default_global::>(); let mut this = Self { + ui_font_size: defaults.ui_font_size.unwrap_or(16.).into(), buffer_font: Font { family: defaults.buffer_font_family.clone().unwrap().into(), features: defaults.buffer_font_features.clone().unwrap(), @@ -123,9 +127,10 @@ impl settings2::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - active_theme: themes.get("Zed Pro Moonlight").unwrap(), - // todo!(Read the theme name from the settings) - // active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + active_theme: themes + .get(defaults.theme.as_ref().unwrap()) + .or(themes.get("Zed Pro Moonlight")) + .unwrap(), }; for value in user_values.into_iter().copied().cloned() { @@ -142,6 +147,7 @@ impl settings2::Settings for ThemeSettings { } } + merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into)); merge( &mut this.buffer_font_size, value.buffer_font_size.map(Into::into), diff --git a/crates/ui2/Cargo.toml b/crates/ui2/Cargo.toml index 58013e34cd..f11fd652b6 100644 --- a/crates/ui2/Cargo.toml +++ b/crates/ui2/Cargo.toml @@ -10,6 +10,7 @@ chrono = "0.4" gpui2 = { path = "../gpui2" } itertools = { version = "0.11.0", optional = true } serde.workspace = true +settings2 = { path = "../settings2" } smallvec.workspace = true strum = { version = "0.25.0", features = ["derive"] } theme2 = { path = "../theme2" } diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 06e242b1ef..101c845a76 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use gpui2::MouseButton; +use gpui2::{rems, MouseButton}; use crate::{h_stack, prelude::*}; use crate::{ClickHandler, Icon, IconColor, IconElement}; @@ -88,8 +88,8 @@ impl IconButton { .id(self.id.clone()) .justify_center() .rounded_md() - .py(ui_size(cx, 0.25)) - .px(ui_size(cx, 6. / 14.)) + .py(rems(0.21875)) + .px(rems(0.375)) .bg(bg_color) .hover(|style| style.bg(bg_hover_color)) .active(|style| style.bg(bg_active_color)) diff --git a/crates/ui2/src/components/workspace.rs b/crates/ui2/src/components/workspace.rs index 0e31c6b9ad..97570a33e3 100644 --- a/crates/ui2/src/components/workspace.rs +++ b/crates/ui2/src/components/workspace.rs @@ -1,14 +1,16 @@ use std::sync::Arc; use chrono::DateTime; -use gpui2::{px, relative, rems, Div, Render, Size, View, VisualContext}; +use gpui2::{px, relative, Div, Render, Size, View, VisualContext}; +use settings2::Settings; +use theme2::ThemeSettings; -use crate::{prelude::*, NotificationsPanel}; +use crate::prelude::*; use crate::{ - static_livestream, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, - CollabPanel, EditorPane, FakeSettings, Label, LanguageSelector, Pane, PaneGroup, Panel, - PanelAllowedSides, PanelSide, ProjectPanel, SettingValue, SplitDirection, StatusBar, Terminal, - TitleBar, Toast, ToastOrigin, + static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, CollabPanel, + EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel, + PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, + Toast, ToastOrigin, }; #[derive(Clone)] @@ -150,6 +152,18 @@ impl Workspace { pub fn debug_toggle_user_settings(&mut self, cx: &mut ViewContext) { self.debug.enable_user_settings = !self.debug.enable_user_settings; + let mut theme_settings = ThemeSettings::get_global(cx).clone(); + + if self.debug.enable_user_settings { + theme_settings.ui_font_size = 18.0.into(); + } else { + theme_settings.ui_font_size = 16.0.into(); + } + + ThemeSettings::override_global(theme_settings.clone(), cx); + + cx.set_rem_size(theme_settings.ui_font_size); + cx.notify(); } @@ -179,20 +193,6 @@ impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - // HACK: This should happen inside of `debug_toggle_user_settings`, but - // we don't have `cx.global::()` in event handlers at the moment. - // Need to talk with Nathan/Antonio about this. - { - let settings = user_settings_mut(cx); - - if self.debug.enable_user_settings { - settings.list_indent_depth = SettingValue::UserDefined(rems(0.5).into()); - settings.ui_scale = SettingValue::UserDefined(1.25); - } else { - *settings = FakeSettings::default(); - } - } - let root_group = PaneGroup::new_panes( vec![Pane::new( "pane-0", @@ -321,7 +321,7 @@ impl Render for Workspace { v_stack() .z_index(9) .absolute() - .bottom_10() + .top_20() .left_1_4() .w_40() .gap_2() diff --git a/crates/ui2/src/elements/button.rs b/crates/ui2/src/elements/button.rs index e63269197c..073bcdbb45 100644 --- a/crates/ui2/src/elements/button.rs +++ b/crates/ui2/src/elements/button.rs @@ -1,9 +1,9 @@ use std::sync::Arc; -use gpui2::{div, DefiniteLength, Hsla, MouseButton, WindowContext}; +use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext}; -use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor}; -use crate::{prelude::*, LineHeightStyle}; +use crate::prelude::*; +use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle}; #[derive(Default, PartialEq, Clone, Copy)] pub enum IconPosition { @@ -151,7 +151,7 @@ impl Button { .relative() .id(SharedString::from(format!("{}", self.label))) .p_1() - .text_size(ui_size(cx, 1.)) + .text_size(rems(1.)) .rounded_md() .bg(self.variant.bg_color(cx)) .hover(|style| style.bg(self.variant.bg_color_hover(cx))) @@ -198,7 +198,7 @@ impl ButtonGroup { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let mut el = h_stack().text_size(ui_size(cx, 1.)); + let mut el = h_stack().text_size(rems(1.)); for button in self.buttons { el = el.child(button.render(_view, cx)); diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index 6c1b3a4f08..8cc62f4a8d 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -1,4 +1,4 @@ -use gpui2::{svg, Hsla}; +use gpui2::{rems, svg, Hsla}; use strum::EnumIter; use crate::prelude::*; @@ -175,8 +175,8 @@ impl IconElement { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let fill = self.color.color(cx); let svg_size = match self.size { - IconSize::Small => ui_size(cx, 12. / 14.), - IconSize::Medium => ui_size(cx, 15. / 14.), + IconSize::Small => rems(0.75), + IconSize::Medium => rems(0.9375), }; svg() diff --git a/crates/ui2/src/elements/label.rs b/crates/ui2/src/elements/label.rs index ee8ac9a636..dcc28a3319 100644 --- a/crates/ui2/src/elements/label.rs +++ b/crates/ui2/src/elements/label.rs @@ -1,4 +1,4 @@ -use gpui2::{relative, Hsla, WindowContext}; +use gpui2::{relative, rems, Hsla, WindowContext}; use smallvec::SmallVec; use crate::prelude::*; @@ -86,7 +86,7 @@ impl Label { .bg(LabelColor::Hidden.hsla(cx)), ) }) - .text_size(ui_size(cx, 1.)) + .text_size(rems(1.)) .when(self.line_height_style == LineHeightStyle::UILabel, |this| { this.line_height(relative(1.)) }) diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index b424ce6123..c3f530d70c 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -4,21 +4,12 @@ pub use gpui2::{ }; pub use crate::elevation::*; -use crate::settings::user_settings; pub use crate::ButtonVariant; pub use theme2::ActiveTheme; -use gpui2::{rems, Hsla, Rems}; +use gpui2::Hsla; use strum::EnumIter; -pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems { - const UI_SCALE_RATIO: f32 = 0.875; - - let settings = user_settings(cx); - - rems(*settings.ui_scale * UI_SCALE_RATIO * size) -} - #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] pub enum FileSystemStatus { #[default] diff --git a/crates/ui2/src/settings.rs b/crates/ui2/src/settings.rs index 48a2e8e7b4..6a9426f623 100644 --- a/crates/ui2/src/settings.rs +++ b/crates/ui2/src/settings.rs @@ -58,7 +58,6 @@ pub struct FakeSettings { pub list_disclosure_style: SettingValue, pub list_indent_depth: SettingValue, pub titlebar: TitlebarSettings, - pub ui_scale: SettingValue, } impl Default for FakeSettings { @@ -68,7 +67,6 @@ impl Default for FakeSettings { list_disclosure_style: SettingValue::Default(DisclosureControlStyle::ChevronOnHover), list_indent_depth: SettingValue::Default(rems(0.3).into()), default_panel_size: SettingValue::Default(rems(16.).into()), - ui_scale: SettingValue::Default(1.), } } } From 337a79e35f17f68d71d9c8e095cc7095eaa97165 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Nov 2023 18:34:51 +0100 Subject: [PATCH 063/156] 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 c467fa955b6c7f3d8d3e99dc1939c1037385582f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 10:38:47 -0700 Subject: [PATCH 064/156] Comment out hanging project2 tests Co-authored-by: Conrad --- crates/project/src/project_tests.rs | 104 ++--- crates/project2/src/project_tests.rs | 647 +++++++++++++-------------- 2 files changed, 375 insertions(+), 376 deletions(-) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 540915f354..d5ff1e08d7 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2604,64 +2604,64 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); } -#[gpui::test] -async fn test_save_as(cx: &mut gpui::TestAppContext) { - init_test(cx); +// #[gpui::test] +// async fn test_save_as(cx: &mut gpui::TestAppContext) { +// init_test(cx); - let fs = FakeFs::new(cx.background()); - fs.insert_tree("/dir", json!({})).await; +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree("/dir", json!({})).await; - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; +// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let languages = project.read_with(cx, |project, _| project.languages().clone()); - languages.register( - "/some/path", - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".into()], - ..Default::default() - }, - tree_sitter_rust::language(), - vec![], - |_| Default::default(), - ); +// let languages = project.read_with(cx, |project, _| project.languages().clone()); +// languages.register( +// "/some/path", +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".into()], +// ..Default::default() +// }, +// tree_sitter_rust::language(), +// vec![], +// |_| Default::default(), +// ); - let buffer = project.update(cx, |project, cx| { - project.create_buffer("", None, cx).unwrap() - }); - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "abc")], None, cx); - assert!(buffer.is_dirty()); - assert!(!buffer.has_conflict()); - assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); - }); - project - .update(cx, |project, cx| { - project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) - }) - .await - .unwrap(); - assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); +// let buffer = project.update(cx, |project, cx| { +// project.create_buffer("", None, cx).unwrap() +// }); +// buffer.update(cx, |buffer, cx| { +// buffer.edit([(0..0, "abc")], None, cx); +// assert!(buffer.is_dirty()); +// assert!(!buffer.has_conflict()); +// assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); +// }); +// project +// .update(cx, |project, cx| { +// project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) +// }) +// .await +// .unwrap(); +// assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); - cx.foreground().run_until_parked(); - buffer.read_with(cx, |buffer, cx| { - assert_eq!( - buffer.file().unwrap().full_path(cx), - Path::new("dir/file1.rs") - ); - assert!(!buffer.is_dirty()); - assert!(!buffer.has_conflict()); - assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); - }); +// cx.foreground().run_until_parked(); +// buffer.read_with(cx, |buffer, cx| { +// assert_eq!( +// buffer.file().unwrap().full_path(cx), +// Path::new("dir/file1.rs") +// ); +// assert!(!buffer.is_dirty()); +// assert!(!buffer.has_conflict()); +// assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); +// }); - let opened_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/dir/file1.rs", cx) - }) - .await - .unwrap(); - assert_eq!(opened_buffer, buffer); -} +// let opened_buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/dir/file1.rs", cx) +// }) +// .await +// .unwrap(); +// assert_eq!(opened_buffer, buffer); +// } #[gpui::test(retries = 5)] async fn test_rescan_and_remote_updates( diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 5a3f8605ce..e901a4c653 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -1,5 +1,5 @@ -use crate::{search::PathMatcher, worktree::WorktreeModelHandle, Event, *}; -use fs2::{FakeFs, RealFs}; +use crate::{search::PathMatcher, Event, *}; +use fs2::FakeFs; use futures::{future, StreamExt}; use gpui2::AppContext; use language2::{ @@ -15,44 +15,44 @@ use std::{os::unix, task::Poll}; use unindent::Unindent as _; use util::{assert_set_eq, test::temp_tree}; -#[gpui2::test] -async fn test_symlinks(cx: &mut gpui2::TestAppContext) { - init_test(cx); - cx.executor().allow_parking(); +// #[gpui2::test] +// async fn test_symlinks(cx: &mut gpui2::TestAppContext) { +// init_test(cx); +// cx.executor().allow_parking(); - let dir = temp_tree(json!({ - "root": { - "apple": "", - "banana": { - "carrot": { - "date": "", - "endive": "", - } - }, - "fennel": { - "grape": "", - } - } - })); +// let dir = temp_tree(json!({ +// "root": { +// "apple": "", +// "banana": { +// "carrot": { +// "date": "", +// "endive": "", +// } +// }, +// "fennel": { +// "grape": "", +// } +// } +// })); - let root_link_path = dir.path().join("root_link"); - unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); - unix::fs::symlink( - &dir.path().join("root/fennel"), - &dir.path().join("root/finnochio"), - ) - .unwrap(); +// let root_link_path = dir.path().join("root_link"); +// unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); +// unix::fs::symlink( +// &dir.path().join("root/fennel"), +// &dir.path().join("root/finnochio"), +// ) +// .unwrap(); - let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; - project.update(cx, |project, cx| { - let tree = project.worktrees().next().unwrap().read(cx); - assert_eq!(tree.file_count(), 5); - assert_eq!( - tree.inode_for_path("fennel/grape"), - tree.inode_for_path("finnochio/grape") - ); - }); -} +// let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; +// project.update(cx, |project, cx| { +// let tree = project.worktrees().next().unwrap().read(cx); +// assert_eq!(tree.file_count(), 5); +// assert_eq!( +// tree.inode_for_path("fennel/grape"), +// tree.inode_for_path("finnochio/grape") +// ); +// }); +// } #[gpui2::test] async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) { @@ -2058,121 +2058,121 @@ 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) { - init_test(cx); +// #[gpui2::test] +// async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) { +// init_test(cx); - let text = " - use a::b; - use a::c; +// let text = " +// use a::b; +// use a::c; - fn f() { - b(); - c(); - } - " - .unindent(); +// fn f() { +// b(); +// c(); +// } +// " +// .unindent(); - let fs = FakeFs::new(cx.executor().clone()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": text.clone(), - }), - ) - .await; +// let fs = FakeFs::new(cx.executor().clone()); +// fs.insert_tree( +// "/dir", +// json!({ +// "a.rs": text.clone(), +// }), +// ) +// .await; - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); +// let project = Project::test(fs, ["/dir".as_ref()], cx).await; +// let buffer = project +// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) +// .await +// .unwrap(); - // Simulate the language server sending us edits in a non-ordered fashion, - // with ranges sometimes being inverted or pointing to invalid locations. - let edits = project - .update(cx, |project, cx| { - project.edits_from_lsp( - &buffer, - [ - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 9), - lsp2::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), - ), - new_text: "a::{b, c}".into(), - }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(1, 0), - lsp2::Position::new(99, 0), - ), - new_text: "".into(), - }, - lsp2::TextEdit { - range: lsp2::Range::new( - lsp2::Position::new(0, 9), - lsp2::Position::new(0, 9), - ), - new_text: " - fn f() { - b(); - c(); - }" - .unindent(), - }, - ], - LanguageServerId(0), - None, - cx, - ) - }) - .await - .unwrap(); +// // Simulate the language server sending us edits in a non-ordered fashion, +// // with ranges sometimes being inverted or pointing to invalid locations. +// let edits = project +// .update(cx, |project, cx| { +// project.edits_from_lsp( +// &buffer, +// [ +// lsp2::TextEdit { +// range: lsp2::Range::new( +// lsp2::Position::new(0, 9), +// lsp2::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), +// ), +// new_text: "a::{b, c}".into(), +// }, +// lsp2::TextEdit { +// range: lsp2::Range::new( +// lsp2::Position::new(1, 0), +// lsp2::Position::new(99, 0), +// ), +// new_text: "".into(), +// }, +// lsp2::TextEdit { +// range: lsp2::Range::new( +// lsp2::Position::new(0, 9), +// lsp2::Position::new(0, 9), +// ), +// new_text: " +// fn f() { +// b(); +// c(); +// }" +// .unindent(), +// }, +// ], +// LanguageServerId(0), +// None, +// cx, +// ) +// }) +// .await +// .unwrap(); - buffer.update(cx, |buffer, cx| { - let edits = edits - .into_iter() - .map(|(range, text)| { - ( - range.start.to_point(buffer)..range.end.to_point(buffer), - text, - ) - }) - .collect::>(); +// buffer.update(cx, |buffer, cx| { +// let edits = edits +// .into_iter() +// .map(|(range, text)| { +// ( +// range.start.to_point(buffer)..range.end.to_point(buffer), +// text, +// ) +// }) +// .collect::>(); - assert_eq!( - edits, - [ - (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), - (Point::new(1, 0)..Point::new(2, 0), "".into()) - ] - ); +// assert_eq!( +// edits, +// [ +// (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), +// (Point::new(1, 0)..Point::new(2, 0), "".into()) +// ] +// ); - for (range, new_text) in edits { - buffer.edit([(range, new_text)], None, cx); - } - assert_eq!( - buffer.text(), - " - use a::{b, c}; +// for (range, new_text) in edits { +// buffer.edit([(range, new_text)], None, cx); +// } +// assert_eq!( +// buffer.text(), +// " +// use a::{b, c}; - fn f() { - b(); - c(); - } - " - .unindent() - ); - }); -} +// fn f() { +// b(); +// c(); +// } +// " +// .unindent() +// ); +// }); +// } fn chunks_with_diagnostics( buffer: &Buffer, @@ -2636,213 +2636,212 @@ 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) { - init_test(cx); +// #[gpui2::test] +// async fn test_save_as(cx: &mut gpui2::TestAppContext) { +// init_test(cx); - let fs = FakeFs::new(cx.executor().clone()); - fs.insert_tree("/dir", json!({})).await; +// let fs = FakeFs::new(cx.executor().clone()); +// fs.insert_tree("/dir", json!({})).await; - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; +// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let languages = project.update(cx, |project, _| project.languages().clone()); - languages.register( - "/some/path", - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".into()], - ..Default::default() - }, - tree_sitter_rust::language(), - vec![], - |_| Default::default(), - ); +// let languages = project.update(cx, |project, _| project.languages().clone()); +// languages.register( +// "/some/path", +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".into()], +// ..Default::default() +// }, +// tree_sitter_rust::language(), +// vec![], +// |_| Default::default(), +// ); - let buffer = project.update(cx, |project, cx| { - project.create_buffer("", None, cx).unwrap() - }); - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "abc")], None, cx); - assert!(buffer.is_dirty()); - assert!(!buffer.has_conflict()); - assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); - }); - project - .update(cx, |project, cx| { - project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) - }) - .await - .unwrap(); - assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); +// let buffer = project.update(cx, |project, cx| { +// project.create_buffer("", None, cx).unwrap() +// }); +// buffer.update(cx, |buffer, cx| { +// buffer.edit([(0..0, "abc")], None, cx); +// assert!(buffer.is_dirty()); +// assert!(!buffer.has_conflict()); +// assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); +// }); +// project +// .update(cx, |project, cx| { +// project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) +// }) +// .await +// .unwrap(); +// assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); - cx.executor().run_until_parked(); - buffer.update(cx, |buffer, cx| { - assert_eq!( - buffer.file().unwrap().full_path(cx), - Path::new("dir/file1.rs") - ); - assert!(!buffer.is_dirty()); - assert!(!buffer.has_conflict()); - assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); - }); +// cx.executor().run_until_parked(); +// buffer.update(cx, |buffer, cx| { +// assert_eq!( +// buffer.file().unwrap().full_path(cx), +// Path::new("dir/file1.rs") +// ); +// assert!(!buffer.is_dirty()); +// assert!(!buffer.has_conflict()); +// assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); +// }); - let opened_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/dir/file1.rs", cx) - }) - .await - .unwrap(); - assert_eq!(opened_buffer, buffer); -} +// let opened_buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/dir/file1.rs", cx) +// }) +// .await +// .unwrap(); +// assert_eq!(opened_buffer, buffer); +// } #[gpui2::test(retries = 5)] -async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { - init_test(cx); - cx.executor().allow_parking(); +// async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { +// init_test(cx); +// cx.executor().allow_parking(); - let dir = temp_tree(json!({ - "a": { - "file1": "", - "file2": "", - "file3": "", - }, - "b": { - "c": { - "file4": "", - "file5": "", - } - } - })); +// let dir = temp_tree(json!({ +// "a": { +// "file1": "", +// "file2": "", +// "file3": "", +// }, +// "b": { +// "c": { +// "file4": "", +// "file5": "", +// } +// } +// })); - let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; - let rpc = project.update(cx, |p, _| p.client.clone()); +// 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 = 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| { - project.update(cx, |project, cx| { - let tree = project.worktrees().next().unwrap(); - tree.read(cx) - .entry_for_path(path) - .unwrap_or_else(|| panic!("no entry for path {}", path)) - .id - }) - }; +// let buffer_for_path = |path: &'static str, cx: &mut gpui2::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| { +// project.update(cx, |project, cx| { +// let tree = project.worktrees().next().unwrap(); +// tree.read(cx) +// .entry_for_path(path) +// .unwrap_or_else(|| panic!("no entry for path {}", path)) +// .id +// }) +// }; - let buffer2 = buffer_for_path("a/file2", cx).await; - let buffer3 = buffer_for_path("a/file3", cx).await; - let buffer4 = buffer_for_path("b/c/file4", cx).await; - let buffer5 = buffer_for_path("b/c/file5", cx).await; +// let buffer2 = buffer_for_path("a/file2", cx).await; +// let buffer3 = buffer_for_path("a/file3", cx).await; +// let buffer4 = buffer_for_path("b/c/file4", cx).await; +// let buffer5 = buffer_for_path("b/c/file5", cx).await; - let file2_id = id_for_path("a/file2", cx); - let file3_id = id_for_path("a/file3", cx); - let file4_id = id_for_path("b/c/file4", cx); +// let file2_id = id_for_path("a/file2", cx); +// let file3_id = id_for_path("a/file3", cx); +// let file4_id = id_for_path("b/c/file4", cx); - // Create a remote copy of this worktree. - let tree = project.update(cx, |project, _| project.worktrees().next().unwrap()); +// // Create a remote copy of this worktree. +// let tree = project.update(cx, |project, _| project.worktrees().next().unwrap()); - let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto()); +// let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto()); - let updates = Arc::new(Mutex::new(Vec::new())); - tree.update(cx, |tree, cx| { - let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, { - let updates = updates.clone(); - move |update| { - updates.lock().push(update); - async { true } - } - }); - }); +// let updates = Arc::new(Mutex::new(Vec::new())); +// tree.update(cx, |tree, cx| { +// let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, { +// let updates = updates.clone(); +// move |update| { +// updates.lock().push(update); +// async { true } +// } +// }); +// }); - let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); - cx.executor().run_until_parked(); +// let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); +// cx.executor().run_until_parked(); - cx.update(|cx| { - assert!(!buffer2.read(cx).is_dirty()); - assert!(!buffer3.read(cx).is_dirty()); - assert!(!buffer4.read(cx).is_dirty()); - assert!(!buffer5.read(cx).is_dirty()); - }); +// cx.update(|cx| { +// assert!(!buffer2.read(cx).is_dirty()); +// assert!(!buffer3.read(cx).is_dirty()); +// assert!(!buffer4.read(cx).is_dirty()); +// assert!(!buffer5.read(cx).is_dirty()); +// }); - // Rename and delete files and directories. - tree.flush_fs_events(cx).await; - std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); - std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); - std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); - std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); - tree.flush_fs_events(cx).await; +// // Rename and delete files and directories. +// tree.flush_fs_events(cx).await; +// std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); +// std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); +// std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); +// std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); +// tree.flush_fs_events(cx).await; - let expected_paths = vec![ - "a", - "a/file1", - "a/file2.new", - "b", - "d", - "d/file3", - "d/file4", - ]; +// let expected_paths = vec![ +// "a", +// "a/file1", +// "a/file2.new", +// "b", +// "d", +// "d/file3", +// "d/file4", +// ]; - cx.update(|app| { - assert_eq!( - tree.read(app) - .paths() - .map(|p| p.to_str().unwrap()) - .collect::>(), - expected_paths - ); - }); +// cx.update(|app| { +// assert_eq!( +// tree.read(app) +// .paths() +// .map(|p| p.to_str().unwrap()) +// .collect::>(), +// expected_paths +// ); +// }); - assert_eq!(id_for_path("a/file2.new", cx), file2_id); - assert_eq!(id_for_path("d/file3", cx), file3_id); - assert_eq!(id_for_path("d/file4", cx), file4_id); +// assert_eq!(id_for_path("a/file2.new", cx), file2_id); +// assert_eq!(id_for_path("d/file3", cx), file3_id); +// assert_eq!(id_for_path("d/file4", cx), file4_id); - cx.update(|cx| { - assert_eq!( - buffer2.read(cx).file().unwrap().path().as_ref(), - Path::new("a/file2.new") - ); - assert_eq!( - buffer3.read(cx).file().unwrap().path().as_ref(), - Path::new("d/file3") - ); - assert_eq!( - buffer4.read(cx).file().unwrap().path().as_ref(), - Path::new("d/file4") - ); - assert_eq!( - buffer5.read(cx).file().unwrap().path().as_ref(), - Path::new("b/c/file5") - ); +// cx.update(|cx| { +// assert_eq!( +// buffer2.read(cx).file().unwrap().path().as_ref(), +// Path::new("a/file2.new") +// ); +// assert_eq!( +// buffer3.read(cx).file().unwrap().path().as_ref(), +// Path::new("d/file3") +// ); +// assert_eq!( +// buffer4.read(cx).file().unwrap().path().as_ref(), +// Path::new("d/file4") +// ); +// assert_eq!( +// buffer5.read(cx).file().unwrap().path().as_ref(), +// Path::new("b/c/file5") +// ); - assert!(!buffer2.read(cx).file().unwrap().is_deleted()); - assert!(!buffer3.read(cx).file().unwrap().is_deleted()); - assert!(!buffer4.read(cx).file().unwrap().is_deleted()); - assert!(buffer5.read(cx).file().unwrap().is_deleted()); - }); +// assert!(!buffer2.read(cx).file().unwrap().is_deleted()); +// assert!(!buffer3.read(cx).file().unwrap().is_deleted()); +// assert!(!buffer4.read(cx).file().unwrap().is_deleted()); +// assert!(buffer5.read(cx).file().unwrap().is_deleted()); +// }); - // Update the remote worktree. Check that it becomes consistent with the - // local worktree. - cx.executor().run_until_parked(); - - remote.update(cx, |remote, _| { - for update in updates.lock().drain(..) { - remote.as_remote_mut().unwrap().update_from_remote(update); - } - }); - cx.executor().run_until_parked(); - remote.update(cx, |remote, _| { - assert_eq!( - remote - .paths() - .map(|p| p.to_str().unwrap()) - .collect::>(), - expected_paths - ); - }); -} +// // Update the remote worktree. Check that it becomes consistent with the +// // local worktree. +// cx.executor().run_until_parked(); +// remote.update(cx, |remote, _| { +// for update in updates.lock().drain(..) { +// remote.as_remote_mut().unwrap().update_from_remote(update); +// } +// }); +// cx.executor().run_until_parked(); +// remote.update(cx, |remote, _| { +// assert_eq!( +// remote +// .paths() +// .map(|p| p.to_str().unwrap()) +// .collect::>(), +// expected_paths +// ); +// }); +// } #[gpui2::test(iterations = 10)] async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { init_test(cx); From cfe0ddc61afab44cc467213a924da5c5e3d3d34a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 11:43:06 -0600 Subject: [PATCH 065/156] More unused imports --- crates/project2/src/project_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index e901a4c653..b23c319ba0 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -11,9 +11,9 @@ use lsp2::Url; use parking_lot::Mutex; use pretty_assertions::assert_eq; use serde_json::json; -use std::{os::unix, task::Poll}; +use std::task::Poll; use unindent::Unindent as _; -use util::{assert_set_eq, test::temp_tree}; +use util::assert_set_eq; // #[gpui2::test] // async fn test_symlinks(cx: &mut gpui2::TestAppContext) { From 6aaeb23157dff73dcb4094d416dfd1bdc1d1aded Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 10:52:15 -0700 Subject: [PATCH 066/156] Rename live kit bridge to 2 --- .../{LiveKitBridge => LiveKitBridge2}/Package.resolved | 0 .../{LiveKitBridge => LiveKitBridge2}/Package.swift | 8 ++++---- .../{LiveKitBridge => LiveKitBridge2}/README.md | 2 +- .../Sources/LiveKitBridge2/LiveKitBridge2.swift} | 0 crates/live_kit_client2/build.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename crates/live_kit_client2/{LiveKitBridge => LiveKitBridge2}/Package.resolved (100%) rename crates/live_kit_client2/{LiveKitBridge => LiveKitBridge2}/Package.swift (84%) rename crates/live_kit_client2/{LiveKitBridge => LiveKitBridge2}/README.md (65%) rename crates/live_kit_client2/{LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift => LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift} (100%) diff --git a/crates/live_kit_client2/LiveKitBridge/Package.resolved b/crates/live_kit_client2/LiveKitBridge2/Package.resolved similarity index 100% rename from crates/live_kit_client2/LiveKitBridge/Package.resolved rename to crates/live_kit_client2/LiveKitBridge2/Package.resolved diff --git a/crates/live_kit_client2/LiveKitBridge/Package.swift b/crates/live_kit_client2/LiveKitBridge2/Package.swift similarity index 84% rename from crates/live_kit_client2/LiveKitBridge/Package.swift rename to crates/live_kit_client2/LiveKitBridge2/Package.swift index d7b5c271b9..890eaa2f6d 100644 --- a/crates/live_kit_client2/LiveKitBridge/Package.swift +++ b/crates/live_kit_client2/LiveKitBridge2/Package.swift @@ -3,16 +3,16 @@ import PackageDescription let package = Package( - name: "LiveKitBridge", + name: "LiveKitBridge2", platforms: [ .macOS(.v10_15) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( - name: "LiveKitBridge", + name: "LiveKitBridge2", type: .static, - targets: ["LiveKitBridge"]), + targets: ["LiveKitBridge2"]), ], dependencies: [ .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), @@ -21,7 +21,7 @@ let package = Package( // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( - name: "LiveKitBridge", + name: "LiveKitBridge2", dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]), ] ) diff --git a/crates/live_kit_client2/LiveKitBridge/README.md b/crates/live_kit_client2/LiveKitBridge2/README.md similarity index 65% rename from crates/live_kit_client2/LiveKitBridge/README.md rename to crates/live_kit_client2/LiveKitBridge2/README.md index b982c67286..1fceed8165 100644 --- a/crates/live_kit_client2/LiveKitBridge/README.md +++ b/crates/live_kit_client2/LiveKitBridge2/README.md @@ -1,3 +1,3 @@ -# LiveKitBridge +# LiveKitBridge2 A description of this package. diff --git a/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift similarity index 100% rename from crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift rename to crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift diff --git a/crates/live_kit_client2/build.rs b/crates/live_kit_client2/build.rs index 1445704b46..b346b3168b 100644 --- a/crates/live_kit_client2/build.rs +++ b/crates/live_kit_client2/build.rs @@ -5,7 +5,7 @@ use std::{ process::Command, }; -const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge"; +const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge2"; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] From 1c1b53ecf643bbf901404b9c073e8b99f715b65c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 11:45:31 -0700 Subject: [PATCH 067/156] 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 51ecf8e6b46af8c3a0ebaa32da01d3b73bf6edb7 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 1 Nov 2023 14:50:29 -0400 Subject: [PATCH 068/156] collab 0.28.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 755f4440d9..2b70dcd3cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1603,7 +1603,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.27.0" +version = "0.28.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 987c295407..dea6e09245 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.27.0" +version = "0.28.0" publish = false [[bin]] From 6f38eb325251b5753c7dad9994326ee24bacd50f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 11:52:14 -0700 Subject: [PATCH 069/156] 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 a5d37d351069a24c5c00a9266f23fa9a1713bf56 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 13:26:12 -0600 Subject: [PATCH 070/156] Remove two more hanging tests --- crates/project2/src/project_tests.rs | 294 +++++++++++++-------------- 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index b23c319ba0..f3a3dfb34d 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -2292,168 +2292,168 @@ async fn test_definition(cx: &mut gpui2::TestAppContext) { } } -#[gpui2::test] -async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { - init_test(cx); +// #[gpui2::test] +// async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { +// init_test(cx); - let mut language = Language::new( - LanguageConfig { - name: "TypeScript".into(), - path_suffixes: vec!["ts".to_string()], - ..Default::default() - }, - Some(tree_sitter_typescript::language_typescript()), - ); - let mut fake_language_servers = language - .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp2::ServerCapabilities { - completion_provider: Some(lsp2::CompletionOptions { - trigger_characters: Some(vec![":".to_string()]), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - })) - .await; +// let mut language = Language::new( +// LanguageConfig { +// name: "TypeScript".into(), +// path_suffixes: vec!["ts".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_typescript::language_typescript()), +// ); +// let mut fake_language_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp2::ServerCapabilities { +// completion_provider: Some(lsp2::CompletionOptions { +// trigger_characters: Some(vec![":".to_string()]), +// ..Default::default() +// }), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; - let fs = FakeFs::new(cx.executor().clone()); - fs.insert_tree( - "/dir", - json!({ - "a.ts": "", - }), - ) - .await; +// let fs = FakeFs::new(cx.executor().clone()); +// fs.insert_tree( +// "/dir", +// json!({ +// "a.ts": "", +// }), +// ) +// .await; - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) - .await - .unwrap(); +// let project = Project::test(fs, ["/dir".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages.add(Arc::new(language))); +// let buffer = project +// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) +// .await +// .unwrap(); - let fake_server = fake_language_servers.next().await.unwrap(); +// let fake_server = fake_language_servers.next().await.unwrap(); - let text = "let a = b.fqn"; - buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); - let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len(), cx) - }); +// let text = "let a = b.fqn"; +// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); +// let completions = project.update(cx, |project, cx| { +// project.completions(&buffer, text.len(), cx) +// }); - fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp2::CompletionResponse::Array(vec![ - lsp2::CompletionItem { - label: "fullyQualifiedName?".into(), - insert_text: Some("fullyQualifiedName".into()), - ..Default::default() - }, - ]))) - }) - .next() - .await; - let completions = completions.await.unwrap(); - let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].new_text, "fullyQualifiedName"); - assert_eq!( - completions[0].old_range.to_offset(&snapshot), - text.len() - 3..text.len() - ); +// fake_server +// .handle_request::(|_, _| async move { +// Ok(Some(lsp2::CompletionResponse::Array(vec![ +// lsp2::CompletionItem { +// label: "fullyQualifiedName?".into(), +// insert_text: Some("fullyQualifiedName".into()), +// ..Default::default() +// }, +// ]))) +// }) +// .next() +// .await; +// let completions = completions.await.unwrap(); +// let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); +// assert_eq!(completions.len(), 1); +// assert_eq!(completions[0].new_text, "fullyQualifiedName"); +// assert_eq!( +// completions[0].old_range.to_offset(&snapshot), +// text.len() - 3..text.len() +// ); - let text = "let a = \"atoms/cmp\""; - buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); - let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len() - 1, cx) - }); +// let text = "let a = \"atoms/cmp\""; +// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); +// let completions = project.update(cx, |project, cx| { +// project.completions(&buffer, text.len() - 1, cx) +// }); - fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp2::CompletionResponse::Array(vec![ - lsp2::CompletionItem { - label: "component".into(), - ..Default::default() - }, - ]))) - }) - .next() - .await; - let completions = completions.await.unwrap(); - let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].new_text, "component"); - assert_eq!( - completions[0].old_range.to_offset(&snapshot), - text.len() - 4..text.len() - 1 - ); -} +// fake_server +// .handle_request::(|_, _| async move { +// Ok(Some(lsp2::CompletionResponse::Array(vec![ +// lsp2::CompletionItem { +// label: "component".into(), +// ..Default::default() +// }, +// ]))) +// }) +// .next() +// .await; +// let completions = completions.await.unwrap(); +// let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); +// assert_eq!(completions.len(), 1); +// assert_eq!(completions[0].new_text, "component"); +// assert_eq!( +// completions[0].old_range.to_offset(&snapshot), +// text.len() - 4..text.len() - 1 +// ); +// } -#[gpui2::test] -async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) { - init_test(cx); +// #[gpui2::test] +// async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) { +// init_test(cx); - let mut language = Language::new( - LanguageConfig { - name: "TypeScript".into(), - path_suffixes: vec!["ts".to_string()], - ..Default::default() - }, - Some(tree_sitter_typescript::language_typescript()), - ); - let mut fake_language_servers = language - .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp2::ServerCapabilities { - completion_provider: Some(lsp2::CompletionOptions { - trigger_characters: Some(vec![":".to_string()]), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - })) - .await; +// let mut language = Language::new( +// LanguageConfig { +// name: "TypeScript".into(), +// path_suffixes: vec!["ts".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_typescript::language_typescript()), +// ); +// let mut fake_language_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp2::ServerCapabilities { +// completion_provider: Some(lsp2::CompletionOptions { +// trigger_characters: Some(vec![":".to_string()]), +// ..Default::default() +// }), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; - let fs = FakeFs::new(cx.executor().clone()); - fs.insert_tree( - "/dir", - json!({ - "a.ts": "", - }), - ) - .await; +// let fs = FakeFs::new(cx.executor().clone()); +// fs.insert_tree( +// "/dir", +// json!({ +// "a.ts": "", +// }), +// ) +// .await; - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) - .await - .unwrap(); +// let project = Project::test(fs, ["/dir".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages.add(Arc::new(language))); +// let buffer = project +// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) +// .await +// .unwrap(); - let fake_server = fake_language_servers.next().await.unwrap(); +// let fake_server = fake_language_servers.next().await.unwrap(); - let text = "let a = b.fqn"; - buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); - let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len(), cx) - }); +// let text = "let a = b.fqn"; +// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); +// let completions = project.update(cx, |project, cx| { +// project.completions(&buffer, text.len(), cx) +// }); - fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp2::CompletionResponse::Array(vec![ - lsp2::CompletionItem { - label: "fullyQualifiedName?".into(), - insert_text: Some("fully\rQualified\r\nName".into()), - ..Default::default() - }, - ]))) - }) - .next() - .await; - let completions = completions.await.unwrap(); - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].new_text, "fully\nQualified\nName"); -} +// fake_server +// .handle_request::(|_, _| async move { +// Ok(Some(lsp2::CompletionResponse::Array(vec![ +// lsp2::CompletionItem { +// label: "fullyQualifiedName?".into(), +// insert_text: Some("fully\rQualified\r\nName".into()), +// ..Default::default() +// }, +// ]))) +// }) +// .next() +// .await; +// let completions = completions.await.unwrap(); +// assert_eq!(completions.len(), 1); +// 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) { From ea7fdef417d5580812072f5f8816f30a844e74b7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 13:51:42 -0600 Subject: [PATCH 071/156] And more unused imports --- crates/project2/src/project_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index f3a3dfb34d..fd8c195101 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -4,8 +4,8 @@ use futures::{future, StreamExt}; use gpui2::AppContext; use language2::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, - tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, - LineEnding, OffsetRangeExt, Point, ToPoint, + tree_sitter_rust, Diagnostic, FakeLspAdapter, LanguageConfig, LineEnding, OffsetRangeExt, + Point, ToPoint, }; use lsp2::Url; use parking_lot::Mutex; From 57ffa8201edcc8ed53f665f261d8b1e6db3a593e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 11:31:23 -0700 Subject: [PATCH 072/156] Start removing the Send impl for App Co-authored-by: Antonio Co-authored-by: Nathan --- .../ai2/src/providers/open_ai/completion.rs | 8 +- crates/ai2/src/providers/open_ai/embedding.rs | 6 +- crates/audio2/src/audio2.rs | 8 +- crates/call2/src/room.rs | 3 +- crates/client2/src/client2.rs | 18 +- crates/client2/src/test.rs | 10 +- crates/copilot2/src/copilot2.rs | 2 +- .../src/test/editor_lsp_test_context.rs | 6 +- crates/fs2/src/fs2.rs | 8 +- crates/fuzzy2/src/paths.rs | 4 +- crates/gpui2/src/app.rs | 330 ++++++------------ crates/gpui2/src/app/async_context.rs | 101 ++---- crates/gpui2/src/app/entity_map.rs | 2 +- crates/gpui2/src/app/model_context.rs | 33 +- crates/gpui2/src/app/test_context.rs | 98 +++--- crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/element.rs | 12 +- crates/gpui2/src/executor.rs | 95 ++--- crates/gpui2/src/gpui2.rs | 135 +------ crates/gpui2/src/interactive.rs | 50 ++- crates/gpui2/src/platform.rs | 13 +- crates/gpui2/src/platform/mac/platform.rs | 71 ++-- crates/gpui2/src/platform/mac/window.rs | 57 ++- crates/gpui2/src/platform/test/dispatcher.rs | 55 --- crates/gpui2/src/platform/test/platform.rs | 20 +- crates/gpui2/src/subscription.rs | 6 +- crates/gpui2/src/view.rs | 11 +- crates/gpui2/src/window.rs | 177 +++------- crates/language2/src/language2.rs | 8 +- crates/lsp2/src/lsp2.rs | 18 +- crates/project2/src/project2.rs | 20 +- crates/project2/src/worktree.rs | 11 +- crates/rpc2/src/conn.rs | 4 +- crates/rpc2/src/peer.rs | 2 +- crates/settings2/src/settings_file.rs | 4 +- crates/sqlez/src/thread_safe_connection.rs | 6 +- crates/vim/src/test/vim_test_context.rs | 6 +- crates/zed2/src/main.rs | 18 +- 38 files changed, 506 insertions(+), 932 deletions(-) diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index eca5611027..1582659e71 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -4,7 +4,7 @@ use futures::{ future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt, Stream, StreamExt, }; -use gpui2::{AppContext, Executor}; +use gpui2::{AppContext, BackgroundExecutor}; use isahc::{http::StatusCode, Request, RequestExt}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -105,7 +105,7 @@ pub struct OpenAIResponseStreamEvent { pub async fn stream_completion( credential: ProviderCredential, - executor: Arc, + executor: Arc, request: Box, ) -> Result>> { let api_key = match credential { @@ -198,11 +198,11 @@ pub async fn stream_completion( pub struct OpenAICompletionProvider { model: OpenAILanguageModel, credential: Arc>, - executor: Arc, + executor: Arc, } impl OpenAICompletionProvider { - pub fn new(model_name: &str, executor: Arc) -> Self { + pub fn new(model_name: &str, executor: Arc) -> Self { let model = OpenAILanguageModel::load(model_name); let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials)); Self { diff --git a/crates/ai2/src/providers/open_ai/embedding.rs b/crates/ai2/src/providers/open_ai/embedding.rs index fc49c15134..dde4af1273 100644 --- a/crates/ai2/src/providers/open_ai/embedding.rs +++ b/crates/ai2/src/providers/open_ai/embedding.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::AsyncReadExt; -use gpui2::Executor; +use gpui2::BackgroundExecutor; use gpui2::{serde_json, AppContext}; use isahc::http::StatusCode; use isahc::prelude::Configurable; @@ -35,7 +35,7 @@ pub struct OpenAIEmbeddingProvider { model: OpenAILanguageModel, credential: Arc>, pub client: Arc, - pub executor: Arc, + pub executor: Arc, rate_limit_count_rx: watch::Receiver>, rate_limit_count_tx: Arc>>>, } @@ -66,7 +66,7 @@ struct OpenAIEmbeddingUsage { } impl OpenAIEmbeddingProvider { - pub fn new(client: Arc, executor: Arc) -> Self { + pub fn new(client: Arc, executor: Arc) -> Self { let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None); let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx)); diff --git a/crates/audio2/src/audio2.rs b/crates/audio2/src/audio2.rs index d04587d74e..5a27b79349 100644 --- a/crates/audio2/src/audio2.rs +++ b/crates/audio2/src/audio2.rs @@ -1,6 +1,6 @@ use assets::SoundRegistry; use futures::{channel::mpsc, StreamExt}; -use gpui2::{AppContext, AssetSource, Executor}; +use gpui2::{AppContext, AssetSource, BackgroundExecutor}; use rodio::{OutputStream, OutputStreamHandle}; use util::ResultExt; @@ -34,7 +34,7 @@ impl Sound { } pub struct Audio { - tx: mpsc::UnboundedSender>, + tx: mpsc::UnboundedSender>, } struct AudioState { @@ -60,8 +60,8 @@ impl AudioState { } impl Audio { - pub fn new(executor: &Executor) -> Self { - let (tx, mut rx) = mpsc::unbounded::>(); + pub fn new(executor: &BackgroundExecutor) -> Self { + let (tx, mut rx) = mpsc::unbounded::>(); executor .spawn_on_main(|| async move { let mut audio = AudioState { diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index b7bac52a8b..556f9e778e 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -445,7 +445,8 @@ impl Room { // Wait for client to re-establish a connection to the server. { - let mut reconnection_timeout = cx.executor().timer(RECONNECT_TIMEOUT).fuse(); + let mut reconnection_timeout = + cx.background_executor().timer(RECONNECT_TIMEOUT).fuse(); let client_reconnection = async { let mut remaining_attempts = 3; while remaining_attempts > 0 { diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 19e8685c28..435d5f1840 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -505,7 +505,7 @@ impl Client { }, &cx, ); - cx.executor().timer(delay).await; + cx.background_executor().timer(delay).await; delay = delay .mul_f32(rng.gen_range(1.0..=2.0)) .min(reconnect_interval); @@ -763,7 +763,8 @@ impl Client { self.set_status(Status::Reconnecting, cx); } - let mut timeout = futures::FutureExt::fuse(cx.executor().timer(CONNECTION_TIMEOUT)); + let mut timeout = + futures::FutureExt::fuse(cx.background_executor().timer(CONNECTION_TIMEOUT)); futures::select_biased! { connection = self.establish_connection(&credentials, cx).fuse() => { match connection { @@ -814,7 +815,7 @@ impl Client { conn: Connection, cx: &AsyncAppContext, ) -> Result<()> { - let executor = cx.executor(); + let executor = cx.background_executor(); log::info!("add connection to peer"); let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, { let executor = executor.clone(); @@ -978,7 +979,7 @@ impl Client { .header("x-zed-protocol-version", rpc2::PROTOCOL_VERSION); let http = self.http.clone(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?; let rpc_host = rpc_url .host_str() @@ -1382,7 +1383,7 @@ mod tests { use super::*; use crate::test::FakeServer; - use gpui2::{Context, Executor, TestAppContext}; + use gpui2::{BackgroundExecutor, Context, TestAppContext}; use parking_lot::Mutex; use std::future; use util::http::FakeHttpClient; @@ -1422,7 +1423,7 @@ mod tests { } #[gpui2::test(iterations = 10)] - async fn test_connection_timeout(executor: Executor, cx: &mut TestAppContext) { + 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)); let mut status = client.status(); @@ -1490,7 +1491,10 @@ mod tests { } #[gpui2::test(iterations = 10)] - async fn test_authenticating_more_than_once(cx: &mut TestAppContext, executor: Executor) { + async fn test_authenticating_more_than_once( + cx: &mut TestAppContext, + executor: BackgroundExecutor, + ) { let auth_count = Arc::new(Mutex::new(0)); let dropped_auth_count = Arc::new(Mutex::new(0)); let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); diff --git a/crates/client2/src/test.rs b/crates/client2/src/test.rs index f30547dcfc..61f94580c3 100644 --- a/crates/client2/src/test.rs +++ b/crates/client2/src/test.rs @@ -1,7 +1,7 @@ use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore}; use anyhow::{anyhow, Result}; use futures::{stream::BoxStream, StreamExt}; -use gpui2::{Context, Executor, Model, TestAppContext}; +use gpui2::{BackgroundExecutor, Context, Model, TestAppContext}; use parking_lot::Mutex; use rpc2::{ proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse}, @@ -14,7 +14,7 @@ pub struct FakeServer { peer: Arc, state: Arc>, user_id: u64, - executor: Executor, + executor: BackgroundExecutor, } #[derive(Default)] @@ -79,10 +79,10 @@ impl FakeServer { } let (client_conn, server_conn, _) = - Connection::in_memory(cx.executor().clone()); + Connection::in_memory(cx.background_executor().clone()); let (connection_id, io, incoming) = - peer.add_test_connection(server_conn, cx.executor().clone()); - cx.executor().spawn(io).detach(); + peer.add_test_connection(server_conn, cx.background_executor().clone()); + cx.background_executor().spawn(io).detach(); { let mut state = state.lock(); state.connection_id = Some(connection_id); diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 083c491656..3d50834e94 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -208,7 +208,7 @@ impl RegisteredBuffer { let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?; let content_changes = cx - .executor() + .background_executor() .spawn({ let new_snapshot = new_snapshot.clone(); async move { diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 3e2f38a0b6..59b1cbce05 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -266,9 +266,9 @@ impl<'a> EditorLspTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static + Send, - F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Send + Future>, + T::Params: 'static, + F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Future>, { let url = self.buffer_lsp_url.clone(); self.lsp.handle_request::(move |params, cx| { diff --git a/crates/fs2/src/fs2.rs b/crates/fs2/src/fs2.rs index 6ff8676473..82b5aead07 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::Executor, + executor: gpui2::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::Executor) -> Arc { + pub fn new(executor: gpui2::BackgroundExecutor) -> Arc { Arc::new(Self { executor, state: Mutex::new(FakeFsState { @@ -1222,11 +1222,11 @@ pub fn copy_recursive<'a>( #[cfg(test)] mod tests { use super::*; - use gpui2::Executor; + use gpui2::BackgroundExecutor; use serde_json::json; #[gpui2::test] - async fn test_fake_fs(executor: Executor) { + async fn test_fake_fs(executor: BackgroundExecutor) { let fs = FakeFs::new(executor.clone()); fs.insert_tree( "/root", diff --git a/crates/fuzzy2/src/paths.rs b/crates/fuzzy2/src/paths.rs index f6c5fba6c9..4990b9e5b5 100644 --- a/crates/fuzzy2/src/paths.rs +++ b/crates/fuzzy2/src/paths.rs @@ -1,4 +1,4 @@ -use gpui2::Executor; +use gpui2::BackgroundExecutor; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -134,7 +134,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( smart_case: bool, max_results: usize, cancel_flag: &AtomicBool, - executor: Executor, + executor: BackgroundExecutor, ) -> Vec { let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum(); if path_count == 0 { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index cdc3f8172d..b3747f3cbf 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -14,8 +14,8 @@ 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, Render, + BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, FocusEvent, FocusHandle, + FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, WindowId, }; @@ -26,17 +26,18 @@ use parking_lot::Mutex; use slotmap::SlotMap; use std::{ any::{type_name, Any, TypeId}, - borrow::Borrow, + cell::RefCell, marker::PhantomData, mem, ops::{Deref, DerefMut}, path::PathBuf, - sync::{atomic::Ordering::SeqCst, Arc, Weak}, + rc::{Rc, Weak}, + sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, }; use util::http::{self, HttpClient}; -pub struct App(Arc>); +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`. @@ -54,13 +55,12 @@ impl App { /// app is fully launched. pub fn run(self, on_finish_launching: F) where - F: 'static + FnOnce(&mut MainThread), + F: 'static + FnOnce(&mut AppContext), { let this = self.0.clone(); - let platform = self.0.lock().platform.clone(); - platform.borrow_on_main_thread().run(Box::new(move || { - let cx = &mut *this.lock(); - let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }; + let platform = self.0.borrow().platform.clone(); + platform.run(Box::new(move || { + let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); } @@ -71,16 +71,12 @@ impl App { where F: 'static + FnMut(Vec, &mut AppContext), { - let this = Arc::downgrade(&self.0); - self.0 - .lock() - .platform - .borrow_on_main_thread() - .on_open_urls(Box::new(move |urls| { - if let Some(app) = this.upgrade() { - callback(urls, &mut app.lock()); - } - })); + 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()); + } + })); self } @@ -88,29 +84,25 @@ impl App { where F: 'static + FnMut(&mut AppContext), { - let this = Arc::downgrade(&self.0); - self.0 - .lock() - .platform - .borrow_on_main_thread() - .on_reopen(Box::new(move || { - if let Some(app) = this.upgrade() { - callback(&mut app.lock()); - } - })); + 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 } pub fn metadata(&self) -> AppMetadata { - self.0.lock().app_metadata.clone() + self.0.borrow().app_metadata.clone() } - pub fn executor(&self) -> Executor { - self.0.lock().executor.clone() + pub fn background_executor(&self) -> BackgroundExecutor { + self.0.borrow().background_executor.clone() } pub fn text_system(&self) -> Arc { - self.0.lock().text_system.clone() + self.0.borrow().text_system.clone() } } @@ -122,15 +114,16 @@ type QuitHandler = Box BoxFuture<'static, ()> + Se type ReleaseListener = Box; pub struct AppContext { - this: Weak>, - pub(crate) platform: MainThreadOnly, + this: Weak>, + pub(crate) platform: Rc, app_metadata: AppMetadata, text_system: Arc, flushing_effects: bool, pending_updates: usize, pub(crate) active_drag: Option, pub(crate) next_frame_callbacks: HashMap>, - pub(crate) executor: Executor, + pub(crate) background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, pub(crate) svg_renderer: SvgRenderer, asset_source: Arc, pub(crate) image_cache: ImageCache, @@ -156,11 +149,12 @@ pub struct AppContext { impl AppContext { pub(crate) fn new( - platform: Arc, + platform: Rc, asset_source: Arc, http_client: Arc, - ) -> Arc> { - let executor = platform.executor(); + ) -> Rc> { + let executor = platform.background_executor(); + let foreground_executor = platform.foreground_executor(); assert!( executor.is_main_thread(), "must construct App on main thread" @@ -175,16 +169,17 @@ impl AppContext { app_version: platform.app_version().ok(), }; - Arc::new_cyclic(|this| { - Mutex::new(AppContext { + Rc::new_cyclic(|this| { + RefCell::new(AppContext { this: this.clone(), text_system, - platform: MainThreadOnly::new(platform, executor.clone()), + platform, app_metadata, flushing_effects: false, pending_updates: 0, next_frame_callbacks: Default::default(), - executor, + background_executor: executor, + foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), asset_source, image_cache: ImageCache::new(http_client), @@ -225,7 +220,7 @@ impl AppContext { let futures = futures::future::join_all(futures); if self - .executor + .background_executor .block_with_timeout(Duration::from_millis(100), futures) .is_err() { @@ -244,7 +239,6 @@ impl AppContext { pub fn refresh(&mut self) { self.pending_effects.push_back(Effect::Refresh); } - pub(crate) fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); @@ -258,7 +252,7 @@ impl AppContext { } pub(crate) fn read_window( - &mut self, + &self, id: WindowId, read: impl FnOnce(&WindowContext) -> R, ) -> Result { @@ -295,6 +289,68 @@ impl AppContext { }) } + /// 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 WindowContext) -> 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::mutable(cx, &mut window)); + window.root_view.replace(root_view.into()); + cx.windows.get_mut(id).unwrap().replace(window); + handle + }) + } + + pub(crate) fn platform(&self) -> &Rc { + &self.platform + } + + /// Instructs the platform to activate the application by bringing it to the foreground. + pub fn activate(&self, ignoring_other_apps: bool) { + self.platform().activate(ignoring_other_apps); + } + + /// Writes data to the platform clipboard. + pub fn write_to_clipboard(&self, item: ClipboardItem) { + self.platform().write_to_clipboard(item) + } + + /// Reads data from the platform clipboard. + pub fn read_from_clipboard(&self) -> Option { + self.platform().read_from_clipboard() + } + + /// Writes credentials to the platform keychain. + pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { + self.platform().write_credentials(url, username, password) + } + + /// Reads credentials from the platform keychain. + pub fn read_credentials(&self, url: &str) -> Result)>> { + self.platform().read_credentials(url) + } + + /// Deletes credentials from the platform keychain. + pub fn delete_credentials(&self, url: &str) -> Result<()> { + self.platform().delete_credentials(url) + } + + /// Directs the platform's default browser to open the given URL. + pub fn open_url(&self, url: &str) { + self.platform().open_url(url); + } + + pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { + self.platform().path_for_auxiliary_executable(name) + } + pub(crate) fn push_effect(&mut self, effect: Effect) { match &effect { Effect::Notify { emitter } => { @@ -473,67 +529,24 @@ impl AppContext { pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext { app: unsafe { mem::transmute(self.this.clone()) }, - executor: self.executor.clone(), + background_executor: self.background_executor.clone(), + foreground_executor: self.foreground_executor.clone(), } } /// Obtains a reference to the executor, which can be used to spawn futures. - pub fn executor(&self) -> &Executor { - &self.executor - } - - /// Runs the given closure on the main thread, where interaction with the platform - /// is possible. The given closure will be invoked with a `MainThread`, which - /// has platform-specific methods that aren't present on `AppContext`. - pub fn run_on_main( - &mut self, - f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> Task - where - R: Send + 'static, - { - if self.executor.is_main_thread() { - Task::ready(f(unsafe { - mem::transmute::<&mut AppContext, &mut MainThread>(self) - })) - } else { - let this = self.this.upgrade().unwrap(); - self.executor.run_on_main(move || { - let cx = &mut *this.lock(); - cx.update(|cx| f(unsafe { mem::transmute::<&mut Self, &mut MainThread>(cx) })) - }) - } - } - - /// Spawns the future returned by the given function on the main thread, where interaction with - /// the platform is possible. The given closure will be invoked with a `MainThread`, - /// which has platform-specific methods that aren't present on `AsyncAppContext`. The future will be - /// polled exclusively on the main thread. - // todo!("I think we need somehow to prevent the MainThread from implementing Send") - pub fn spawn_on_main( - &self, - f: impl FnOnce(MainThread) -> F + Send + 'static, - ) -> Task - where - F: Future + 'static, - R: Send + 'static, - { - let cx = self.to_async(); - self.executor.spawn_on_main(move || f(MainThread(cx))) + pub fn executor(&self) -> &BackgroundExecutor { + &self.background_executor } /// 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, + Fut: Future + 'static, + R: 'static, { - let cx = self.to_async(); - self.executor.spawn(async move { - let future = f(cx); - future.await - }) + self.foreground_executor.spawn(f(self.to_async())) } /// Schedules the given function to be run at the end of the current effect cycle, allowing entities @@ -597,7 +610,7 @@ impl AppContext { /// Access the global of the given type mutably. A default value is assigned if a global of this type has not /// yet been assigned. - pub fn default_global(&mut self) -> &mut G { + pub fn default_global(&mut self) -> &mut G { let global_type = TypeId::of::(); self.push_effect(Effect::NotifyGlobalObservers { global_type }); self.globals_by_type @@ -608,7 +621,7 @@ impl AppContext { } /// Set the value of the global of the given type. - pub fn set_global(&mut self, global: G) { + pub fn set_global(&mut self, global: G) { let global_type = TypeId::of::(); self.push_effect(Effect::NotifyGlobalObservers { global_type }); self.globals_by_type.insert(global_type, Box::new(global)); @@ -717,7 +730,7 @@ impl Context for AppContext { /// Build an entity that is owned by the application. The given function will be invoked with /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned /// which can be used to access the entity in a context. - fn build_model( + fn build_model( &mut self, build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Model { @@ -747,107 +760,6 @@ impl Context for AppContext { } } -impl MainThread -where - C: Borrow, -{ - pub(crate) fn platform(&self) -> &dyn Platform { - self.0.borrow().platform.borrow_on_main_thread() - } - - /// Instructs the platform to activate the application by bringing it to the foreground. - pub fn activate(&self, ignoring_other_apps: bool) { - self.platform().activate(ignoring_other_apps); - } - - /// Writes data to the platform clipboard. - pub fn write_to_clipboard(&self, item: ClipboardItem) { - self.platform().write_to_clipboard(item) - } - - /// Reads data from the platform clipboard. - pub fn read_from_clipboard(&self) -> Option { - self.platform().read_from_clipboard() - } - - /// Writes credentials to the platform keychain. - pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { - self.platform().write_credentials(url, username, password) - } - - /// Reads credentials from the platform keychain. - pub fn read_credentials(&self, url: &str) -> Result)>> { - self.platform().read_credentials(url) - } - - /// Deletes credentials from the platform keychain. - pub fn delete_credentials(&self, url: &str) -> Result<()> { - self.platform().delete_credentials(url) - } - - /// Directs the platform's default browser to open the given URL. - pub fn open_url(&self, url: &str) { - self.platform().open_url(url); - } - - pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { - self.platform().path_for_auxiliary_executable(name) - } -} - -impl MainThread { - fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { - self.0.update(|cx| { - update(unsafe { - std::mem::transmute::<&mut AppContext, &mut MainThread>(cx) - }) - }) - } - - pub(crate) fn update_window( - &mut self, - id: WindowId, - update: impl FnOnce(&mut MainThread) -> R, - ) -> Result { - self.0.update_window(id, |cx| { - update(unsafe { - std::mem::transmute::<&mut WindowContext, &mut MainThread>(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 WindowContext) -> 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::mutable(cx, &mut window)); - window.root_view.replace(root_view.into()); - cx.windows.get_mut(id).unwrap().replace(window); - handle - }) - } - - /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides - /// your closure with mutable access to the `MainThread` and the global simultaneously. - pub fn update_global( - &mut self, - update: impl FnOnce(&mut G, &mut MainThread) -> R, - ) -> R { - self.0.update_global(|global, cx| { - let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread>(cx) }; - update(global, cx) - }) - } -} - /// These effects are processed at the end of each application update cycle. pub(crate) enum Effect { Notify { @@ -855,7 +767,7 @@ pub(crate) enum Effect { }, Emit { emitter: EntityId, - event: Box, + event: Box, }, FocusChanged { window_id: WindowId, @@ -905,15 +817,3 @@ pub(crate) struct AnyDrag { pub view: AnyView, pub cursor_offset: Point, } - -#[cfg(test)] -mod tests { - use super::AppContext; - - #[test] - fn test_app_context_send_sync() { - // This will not compile if `AppContext` does not implement `Send` - fn assert_send() {} - assert_send::(); - } -} diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 042a75848e..fb941b91b8 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,16 +1,16 @@ use crate::{ - AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task, - WindowContext, + AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, + ModelContext, Result, Task, WindowContext, }; use anyhow::anyhow; use derive_more::{Deref, DerefMut}; -use parking_lot::Mutex; -use std::{future::Future, sync::Weak}; +use std::{cell::RefCell, future::Future, rc::Weak}; #[derive(Clone)] pub struct AsyncAppContext { - pub(crate) app: Weak>, - pub(crate) executor: Executor, + pub(crate) app: Weak>, + pub(crate) background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, } impl Context for AsyncAppContext { @@ -22,14 +22,14 @@ impl Context for AsyncAppContext { build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Self::Result> where - T: 'static + Send, + T: 'static, { let app = self .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile - Ok(lock.build_model(build_model)) + let mut app = app.borrow_mut(); + Ok(app.build_model(build_model)) } fn update_model( @@ -41,8 +41,8 @@ impl Context for AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile - Ok(lock.update_model(handle, update)) + let mut app = app.borrow_mut(); + Ok(app.update_model(handle, update)) } } @@ -52,13 +52,17 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile + let mut lock = app.borrow_mut(); lock.refresh(); Ok(()) } - pub fn executor(&self) -> &Executor { - &self.executor + pub fn background_executor(&self) -> &BackgroundExecutor { + &self.background_executor + } + + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { @@ -66,7 +70,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); + let mut lock = app.borrow_mut(); Ok(f(&mut *lock)) } @@ -79,7 +83,7 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); + let app_context = app.borrow(); app_context.read_window(handle.id, update) } @@ -92,44 +96,16 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); + let mut app_context = app.borrow_mut(); app_context.update_window(handle.id, update) } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> 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(AsyncAppContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, - R: Send + 'static, + R: '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, - ) -> Result> - where - R: Send + 'static, - { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.lock(); - Ok(app_context.run_on_main(f)) + self.foreground_executor.spawn(f(self.clone())) } pub fn has_global(&self) -> Result { @@ -137,8 +113,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let lock = app.lock(); // Need this to compile - Ok(lock.has_global::()) + let app = app.borrow_mut(); + Ok(app.has_global::()) } pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result { @@ -146,8 +122,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let lock = app.lock(); // Need this to compile - Ok(read(lock.global(), &lock)) + let app = app.borrow_mut(); // Need this to compile + Ok(read(app.global(), &app)) } pub fn try_read_global( @@ -155,8 +131,8 @@ impl AsyncAppContext { read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { let app = self.app.upgrade()?; - let lock = app.lock(); // Need this to compile - Some(read(lock.try_global()?, &lock)) + let app = app.borrow_mut(); + Some(read(app.try_global()?, &app)) } pub fn update_global( @@ -167,8 +143,8 @@ impl AsyncAppContext { .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut lock = app.lock(); // Need this to compile - Ok(lock.update_global(update)) + let mut app = app.borrow_mut(); + Ok(app.update_global(update)) } } @@ -224,7 +200,7 @@ impl Context for AsyncWindowContext { build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Result> where - T: 'static + Send, + T: 'static, { self.app .update_window(self.window, |cx| cx.build_model(build_model)) @@ -239,14 +215,3 @@ impl Context for AsyncWindowContext { .update_window(self.window, |cx| cx.update_model(handle, update)) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_async_app_context_send_sync() { - fn assert_send_sync() {} - assert_send_sync::(); - } -} diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index ecd171d1f8..4a4b178e1e 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -59,7 +59,7 @@ impl EntityMap { /// Insert an entity into a slot obtained by calling `reserve`. pub fn insert(&mut self, slot: Slot, entity: T) -> Model where - T: 'static + Send, + T: 'static, { let model = slot.0; self.entities.insert(model.entity_id, Box::new(entity)); diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 8a4576c052..f0fc1f07f0 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, + AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, EventEmitter, Model, Reference, + Subscription, Task, WeakModel, }; use derive_more::{Deref, DerefMut}; use futures::FutureExt; @@ -191,36 +191,20 @@ 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, - R: Send + 'static, + Fut: Future + 'static, + R: 'static, { let this = self.weak_model(); self.app.spawn(|cx| f(this, cx)) } - - pub fn spawn_on_main( - &self, - f: impl FnOnce(WeakModel, MainThread) -> Fut + Send + 'static, - ) -> Task - where - Fut: Future + 'static, - R: Send + 'static, - { - let this = self.weak_model(); - self.app.spawn_on_main(|cx| f(this, cx)) - } } impl<'a, T> ModelContext<'a, T> where T: EventEmitter, - T::Event: Send, { pub fn emit(&mut self, event: T::Event) { self.app.pending_effects.push_back(Effect::Emit { @@ -234,13 +218,10 @@ impl<'a, T> Context for ModelContext<'a, T> { type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; - fn build_model( + fn build_model( &mut self, build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U, - ) -> Model - where - U: 'static + Send, - { + ) -> Model { self.app.build_model(build_model) } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index cd59f9234f..e3bf8eb7da 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,15 +1,16 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, MainThread, - Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, + AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, + ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, + WindowContext, }; use futures::SinkExt; -use parking_lot::Mutex; -use std::{future::Future, sync::Arc}; +use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc}; #[derive(Clone)] pub struct TestAppContext { - pub app: Arc>, - pub executor: Executor, + pub app: Rc>, + pub background_executor: BackgroundExecutor, + pub foreground_executor: ForegroundExecutor, } impl Context for TestAppContext { @@ -21,10 +22,10 @@ impl Context for TestAppContext { build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Self::Result> where - T: 'static + Send, + T: 'static, { - let mut lock = self.app.lock(); - lock.build_model(build_model) + let mut app = self.app.borrow_mut(); + app.build_model(build_model) } fn update_model( @@ -32,39 +33,45 @@ impl Context for TestAppContext { handle: &Model, update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, ) -> Self::Result { - let mut lock = self.app.lock(); - lock.update_model(handle, update) + let mut app = self.app.borrow_mut(); + app.update_model(handle, update) } } impl TestAppContext { pub fn new(dispatcher: TestDispatcher) -> Self { - let executor = Executor::new(Arc::new(dispatcher)); - let platform = Arc::new(TestPlatform::new(executor.clone())); + let dispatcher = Arc::new(dispatcher); + let background_executor = BackgroundExecutor::new(dispatcher.clone()); + let foreground_executor = ForegroundExecutor::new(dispatcher); + let platform = Rc::new(TestPlatform::new( + background_executor.clone(), + foreground_executor.clone(), + )); let asset_source = Arc::new(()); let http_client = util::http::FakeHttpClient::with_404_response(); Self { app: AppContext::new(platform, asset_source, http_client), - executor, + background_executor, + foreground_executor, } } pub fn quit(&self) { - self.app.lock().quit(); + self.app.borrow_mut().quit(); } pub fn refresh(&mut self) -> Result<()> { - let mut lock = self.app.lock(); - lock.refresh(); + let mut app = self.app.borrow_mut(); + app.refresh(); Ok(()) } - pub fn executor(&self) -> &Executor { - &self.executor + pub fn executor(&self) -> &BackgroundExecutor { + &self.background_executor } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { - let mut cx = self.app.lock(); + let mut cx = self.app.borrow_mut(); cx.update(f) } @@ -73,7 +80,7 @@ impl TestAppContext { handle: AnyWindowHandle, read: impl FnOnce(&WindowContext) -> R, ) -> R { - let mut app_context = self.app.lock(); + let app_context = self.app.borrow(); app_context.read_window(handle.id, read).unwrap() } @@ -82,57 +89,33 @@ impl TestAppContext { handle: AnyWindowHandle, update: impl FnOnce(&mut WindowContext) -> R, ) -> R { - let mut app = self.app.lock(); + let mut app = self.app.borrow_mut(); app.update_window(handle.id, update).unwrap() } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task - where - Fut: Future + Send + 'static, - R: Send + 'static, - { - let cx = self.to_async(); - self.executor.spawn(async move { f(cx).await }) - } - - pub fn spawn_on_main( - &self, - f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static, - ) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, - R: Send + 'static, + R: 'static, { - let cx = self.to_async(); - self.executor.spawn_on_main(|| f(cx)) - } - - pub fn run_on_main( - &self, - f: impl FnOnce(&mut MainThread) -> R + Send + 'static, - ) -> Task - where - R: Send + 'static, - { - let mut app_context = self.app.lock(); - app_context.run_on_main(f) + self.foreground_executor.spawn(f(self.to_async())) } pub fn has_global(&self) -> bool { - let lock = self.app.lock(); - lock.has_global::() + let app = self.app.borrow(); + app.has_global::() } pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { - let lock = self.app.lock(); - read(lock.global(), &lock) + let app = self.app.borrow(); + read(app.global(), &app) } pub fn try_read_global( &self, read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { - let lock = self.app.lock(); + let lock = self.app.borrow(); Some(read(lock.try_global()?, &lock)) } @@ -140,14 +123,15 @@ impl TestAppContext { &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, ) -> R { - let mut lock = self.app.lock(); + let mut lock = self.app.borrow_mut(); lock.update_global(update) } pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext { - app: Arc::downgrade(&self.app), - executor: self.executor.clone(), + app: Rc::downgrade(&self.app), + background_executor: self.background_executor.clone(), + foreground_executor: self.foreground_executor.clone(), } } diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index 39c8562b69..0437b3d6de 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 + Sync { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a715ed30ee..8bd6bcc700 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -4,7 +4,7 @@ pub(crate) use smallvec::SmallVec; use std::{any::Any, mem}; pub trait Element { - type ElementState: 'static + Send; + type ElementState: 'static; fn id(&self) -> Option; @@ -97,7 +97,7 @@ impl> RenderedElement { impl ElementObject for RenderedElement where E: Element, - E::ElementState: 'static + Send, + E::ElementState: 'static, { fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext) { let frame_state = if let Some(id) = self.element.id() { @@ -170,16 +170,14 @@ where } } -pub struct AnyElement(Box + Send>); - -unsafe impl Send for AnyElement {} +pub struct AnyElement(Box>); impl AnyElement { pub fn new(element: E) -> Self where V: 'static, - E: 'static + Element + Send, - E::ElementState: Any + Send, + E: 'static + Element, + E::ElementState: Any, { AnyElement(Box::new(RenderedElement::new(element))) } diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 70c8f4b3ec..d0b65fa10e 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -6,6 +6,7 @@ use std::{ marker::PhantomData, mem, pin::Pin, + rc::Rc, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, @@ -17,10 +18,16 @@ use util::TryFutureExt; use waker_fn::waker_fn; #[derive(Clone)] -pub struct Executor { +pub struct BackgroundExecutor { dispatcher: Arc, } +#[derive(Clone)] +pub struct ForegroundExecutor { + dispatcher: Arc, + not_send: PhantomData>, +} + #[must_use] pub enum Task { Ready(Option), @@ -61,7 +68,7 @@ impl Future for Task { } } -impl Executor { +impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } } @@ -79,63 +86,6 @@ impl Executor { Task::Spawned(task) } - /// Enqueues the given closure to run on the application's event loop. - /// Returns the result asynchronously. - pub fn run_on_main(&self, func: F) -> Task - where - F: FnOnce() -> R + Send + 'static, - R: Send + 'static, - { - if self.dispatcher.is_main_thread() { - Task::ready(func()) - } else { - self.spawn_on_main(move || async move { func() }) - } - } - - /// Enqueues the given closure to be run on the application's event loop. The - /// closure returns a future which will be run to completion on the main thread. - pub fn spawn_on_main(&self, func: impl FnOnce() -> F + Send + 'static) -> Task - where - F: Future + 'static, - R: Send + 'static, - { - let (runnable, task) = async_task::spawn( - { - let this = self.clone(); - async move { - let task = this.spawn_on_main_local(func()); - task.await - } - }, - { - let dispatcher = self.dispatcher.clone(); - move |runnable| dispatcher.dispatch_on_main_thread(runnable) - }, - ); - runnable.schedule(); - Task::Spawned(task) - } - - /// Enqueues the given closure to be run on the application's event loop. Must - /// be called on the main thread. - pub fn spawn_on_main_local(&self, future: impl Future + 'static) -> Task - where - R: 'static, - { - assert!( - self.dispatcher.is_main_thread(), - "must be called on main thread" - ); - - let dispatcher = self.dispatcher.clone(); - let (runnable, task) = async_task::spawn_local(future, move |runnable| { - dispatcher.dispatch_on_main_thread(runnable) - }); - runnable.schedule(); - Task::Spawned(task) - } - pub fn block(&self, future: impl Future) -> R { pin_mut!(future); let (parker, unparker) = parking::pair(); @@ -261,8 +211,31 @@ impl Executor { } } +impl ForegroundExecutor { + pub fn new(dispatcher: Arc) -> Self { + Self { + dispatcher, + not_send: PhantomData, + } + } + + /// Enqueues the given closure to be run on any thread. The closure returns + /// a future which will be run to completion on any available thread. + pub fn spawn(&self, future: impl Future + 'static) -> Task + where + R: 'static, + { + let dispatcher = self.dispatcher.clone(); + let (runnable, task) = async_task::spawn_local(future, move |runnable| { + dispatcher.dispatch_on_main_thread(runnable) + }); + runnable.schedule(); + Task::Spawned(task) + } +} + pub struct Scope<'a> { - executor: Executor, + executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, tx: Option>, rx: mpsc::Receiver<()>, @@ -270,7 +243,7 @@ pub struct Scope<'a> { } impl<'a> Scope<'a> { - fn new(executor: Executor) -> Self { + fn new(executor: BackgroundExecutor) -> Self { let (tx, rx) = mpsc::channel(1); Self { executor, diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 8625866a44..755df91b93 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -68,24 +68,20 @@ use derive_more::{Deref, DerefMut}; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - mem, ops::{Deref, DerefMut}, - sync::Arc, }; use taffy::TaffyLayoutEngine; -type AnyBox = Box; +type AnyBox = Box; pub trait Context { type ModelContext<'a, T>; type Result; - fn build_model( + fn build_model( &mut self, build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, - ) -> Self::Result> - where - T: 'static + Send; + ) -> Self::Result>; fn update_model( &mut self, @@ -102,7 +98,7 @@ pub trait VisualContext: Context { build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, ) -> Self::Result> where - V: 'static + Send; + V: 'static; fn update_view( &mut self, @@ -127,100 +123,6 @@ pub enum GlobalKey { Type(TypeId), } -#[repr(transparent)] -pub struct MainThread(T); - -impl Deref for MainThread { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MainThread { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Context for MainThread { - type ModelContext<'a, T> = MainThread>; - type Result = C::Result; - - fn build_model( - &mut self, - build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, - ) -> Self::Result> - where - T: 'static + Send, - { - self.0.build_model(|cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ModelContext<'_, T>, - &mut MainThread>, - >(cx) - }; - build_model(cx) - }) - } - - fn update_model( - &mut self, - handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, - ) -> Self::Result { - self.0.update_model(handle, |entity, cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ModelContext<'_, T>, - &mut MainThread>, - >(cx) - }; - update(entity, cx) - }) - } -} - -impl VisualContext for MainThread { - type ViewContext<'a, 'w, V> = MainThread>; - - fn build_view( - &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, - ) -> Self::Result> - where - V: 'static + Send, - { - self.0.build_view(|cx| { - let cx = unsafe { - mem::transmute::< - &mut C::ViewContext<'_, '_, V>, - &mut MainThread>, - >(cx) - }; - build_view_state(cx) - }) - } - - fn update_view( - &mut self, - view: &View, - 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>, - >(cx) - }; - update(view_state, cx) - }) - } -} - pub trait BorrowAppContext { fn with_text_style(&mut self, style: TextStyleRefinement, f: F) -> R where @@ -333,32 +235,3 @@ impl<'a, T> DerefMut for Reference<'a, T> { } } } - -pub(crate) struct MainThreadOnly { - executor: Executor, - value: Arc, -} - -impl Clone for MainThreadOnly { - fn clone(&self) -> Self { - Self { - executor: self.executor.clone(), - value: self.value.clone(), - } - } -} - -/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type -/// to become `Send`. -impl MainThreadOnly { - pub(crate) fn new(value: Arc, executor: Executor) -> Self { - Self { executor, value } - } - - pub(crate) fn borrow_on_main_thread(&self) -> &T { - assert!(self.executor.is_main_thread()); - &self.value - } -} - -unsafe impl Send for MainThreadOnly {} diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 5a37c3ee7a..020cb82cd2 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -50,7 +50,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_down( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -71,7 +71,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_up( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -92,7 +92,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_down_out( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -113,7 +113,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_up_out( mut self, button: MouseButton, - handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -133,7 +133,7 @@ pub trait StatelessInteractive: Element { fn on_mouse_move( mut self, - handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -150,7 +150,7 @@ pub trait StatelessInteractive: Element { fn on_scroll_wheel( mut self, - handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -178,7 +178,7 @@ pub trait StatelessInteractive: Element { fn on_action( mut self, - listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -196,7 +196,7 @@ pub trait StatelessInteractive: Element { fn on_key_down( mut self, - listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -214,7 +214,7 @@ pub trait StatelessInteractive: Element { fn on_key_up( mut self, - listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -258,9 +258,9 @@ pub trait StatelessInteractive: Element { self } - fn on_drop( + fn on_drop( mut self, - listener: impl Fn(&mut V, View, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, View, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -303,7 +303,7 @@ pub trait StatefulInteractive: StatelessInteractive { fn on_click( mut self, - listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext) + Send + 'static, + listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, @@ -316,11 +316,11 @@ pub trait StatefulInteractive: StatelessInteractive { fn on_drag( mut self, - listener: impl Fn(&mut V, &mut ViewContext) -> View + Send + 'static, + listener: impl Fn(&mut V, &mut ViewContext) -> View + 'static, ) -> Self where Self: Sized, - W: 'static + Send + Render, + W: 'static + Render, { debug_assert!( self.stateful_interaction().drag_listener.is_none(), @@ -335,7 +335,7 @@ pub trait StatefulInteractive: StatelessInteractive { } } -pub trait ElementInteraction: 'static + Send { +pub trait ElementInteraction: 'static { fn as_stateless(&self) -> &StatelessInteraction; fn as_stateless_mut(&mut self) -> &mut StatelessInteraction; fn as_stateful(&self) -> Option<&StatefulInteraction>; @@ -672,7 +672,7 @@ impl From for StatefulInteraction { } } -type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static + Send; +type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static; pub struct StatelessInteraction { pub dispatch_context: DispatchContext, @@ -1077,32 +1077,25 @@ pub struct FocusEvent { } pub type MouseDownListener = Box< - dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + 'static, + dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) + 'static, >; pub type MouseUpListener = Box< - dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + 'static, + dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) + 'static, >; pub type MouseMoveListener = Box< - dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + 'static, + dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) + 'static, >; pub type ScrollWheelListener = Box< dyn Fn(&mut V, &ScrollWheelEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send + 'static, >; -pub type ClickListener = Box) + Send + 'static>; +pub type ClickListener = Box) + 'static>; pub(crate) type DragListener = - Box, &mut ViewContext) -> AnyDrag + Send + 'static>; + Box, &mut ViewContext) -> AnyDrag + 'static>; pub type KeyListener = Box< dyn Fn( @@ -1112,6 +1105,5 @@ pub type KeyListener = Box< DispatchPhase, &mut ViewContext, ) -> Option> - + Send + 'static, >; diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index cacb1922f6..7c2dbcce18 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -5,9 +5,9 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun, - GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams, - RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, + AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, + ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, + RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -35,12 +35,13 @@ pub use test::*; pub use time::UtcOffset; #[cfg(target_os = "macos")] -pub(crate) fn current_platform() -> Arc { - Arc::new(MacPlatform::new()) +pub(crate) fn current_platform() -> Rc { + Rc::new(MacPlatform::new()) } pub(crate) trait Platform: 'static { - fn executor(&self) -> Executor; + fn background_executor(&self) -> BackgroundExecutor; + fn foreground_executor(&self) -> ForegroundExecutor; fn text_system(&self) -> Arc; fn run(&self, on_finish_launching: Box); diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 881dd69bc8..8dd94f052e 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -1,9 +1,9 @@ use super::BoolExt; use crate::{ - AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Executor, InputEvent, MacDispatcher, - MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform, - PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, - WindowOptions, + AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, + InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, + PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, + SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -143,7 +143,8 @@ unsafe fn build_classes() { pub struct MacPlatform(Mutex); pub struct MacPlatformState { - executor: Executor, + background_executor: BackgroundExecutor, + foreground_executor: ForegroundExecutor, text_system: Arc, display_linker: MacDisplayLinker, pasteboard: id, @@ -164,8 +165,10 @@ pub struct MacPlatformState { impl MacPlatform { pub fn new() -> Self { + let dispatcher = Arc::new(MacDispatcher); Self(Mutex::new(MacPlatformState { - executor: Executor::new(Arc::new(MacDispatcher)), + background_executor: BackgroundExecutor::new(dispatcher.clone()), + foreground_executor: ForegroundExecutor::new(dispatcher.clone()), text_system: Arc::new(MacTextSystem::new()), display_linker: MacDisplayLinker::new(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, @@ -345,8 +348,12 @@ impl MacPlatform { } impl Platform for MacPlatform { - fn executor(&self) -> Executor { - self.0.lock().executor.clone() + fn background_executor(&self) -> BackgroundExecutor { + self.0.lock().background_executor.clone() + } + + fn foreground_executor(&self) -> crate::ForegroundExecutor { + self.0.lock().foreground_executor.clone() } fn text_system(&self) -> Arc { @@ -457,6 +464,10 @@ impl Platform for MacPlatform { } } + // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box { + // Box::new(StatusItem::add(self.fonts())) + // } + fn displays(&self) -> Vec> { MacDisplay::all() .into_iter() @@ -464,10 +475,6 @@ impl Platform for MacPlatform { .collect() } - // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box { - // Box::new(StatusItem::add(self.fonts())) - // } - fn display(&self, id: DisplayId) -> Option> { MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) } @@ -481,7 +488,7 @@ impl Platform for MacPlatform { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { - Box::new(MacWindow::open(handle, options, self.executor())) + Box::new(MacWindow::open(handle, options, self.foreground_executor())) } fn set_display_link_output_callback( @@ -589,8 +596,8 @@ impl Platform for MacPlatform { let path = path.to_path_buf(); self.0 .lock() - .executor - .spawn_on_main_local(async move { + .background_executor + .spawn(async move { let full_path = ns_string(path.to_str().unwrap_or("")); let root_full_path = ns_string(""); let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; @@ -674,23 +681,6 @@ impl Platform for MacPlatform { } } - fn path_for_auxiliary_executable(&self, name: &str) -> Result { - unsafe { - let bundle: id = NSBundle::mainBundle(); - if bundle.is_null() { - Err(anyhow!("app is not running inside a bundle")) - } else { - let name = ns_string(name); - let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name]; - if url.is_null() { - Err(anyhow!("resource not found")) - } else { - ns_url_to_path(url) - } - } - } - } - // fn on_menu_command(&self, callback: Box) { // self.0.lock().menu_command = Some(callback); // } @@ -717,6 +707,23 @@ impl Platform for MacPlatform { // } // } + fn path_for_auxiliary_executable(&self, name: &str) -> Result { + unsafe { + let bundle: id = NSBundle::mainBundle(); + if bundle.is_null() { + Err(anyhow!("app is not running inside a bundle")) + } else { + let name = ns_string(name); + let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name]; + if url.is_null() { + Err(anyhow!("resource not found")) + } else { + ns_url_to_path(url) + } + } + } + } + fn set_cursor_style(&self, style: CursorStyle) { unsafe { let new_cursor: id = match style { diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index dbdb60469b..bf62e2e0dc 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -1,10 +1,10 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use crate::{ - display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, ExternalPaths, - FileDropEvent, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, - PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size, - Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, + display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, + FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, + Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, + Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, }; use block::ConcreteBlock; use cocoa::{ @@ -315,7 +315,7 @@ struct InsertText { struct MacWindowState { handle: AnyWindowHandle, - executor: Executor, + executor: ForegroundExecutor, native_window: id, renderer: MetalRenderer, scene_to_render: Option, @@ -451,7 +451,11 @@ unsafe impl Send for MacWindowState {} pub struct MacWindow(Arc>); impl MacWindow { - pub fn open(handle: AnyWindowHandle, options: WindowOptions, executor: Executor) -> Self { + pub fn open( + handle: AnyWindowHandle, + options: WindowOptions, + executor: ForegroundExecutor, + ) -> Self { unsafe { let pool = NSAutoreleasePool::new(nil); @@ -674,13 +678,10 @@ impl MacWindow { impl Drop for MacWindow { fn drop(&mut self) { - let this = self.0.clone(); - let executor = self.0.lock().executor.clone(); - executor - .run_on_main(move || unsafe { - this.lock().native_window.close(); - }) - .detach(); + let native_window = self.0.lock().native_window; + unsafe { + native_window.close(); + } } } @@ -807,7 +808,7 @@ impl PlatformWindow for MacWindow { let native_window = self.0.lock().native_window; let executor = self.0.lock().executor.clone(); executor - .spawn_on_main_local(async move { + .spawn(async move { let _: () = msg_send![ alert, beginSheetModalForWindow: native_window @@ -824,7 +825,7 @@ impl PlatformWindow for MacWindow { let window = self.0.lock().native_window; let executor = self.0.lock().executor.clone(); executor - .spawn_on_main_local(async move { + .spawn(async move { unsafe { let _: () = msg_send![window, makeKeyAndOrderFront: nil]; } @@ -872,25 +873,17 @@ impl PlatformWindow for MacWindow { fn zoom(&self) { let this = self.0.lock(); let window = this.native_window; - this.executor - .spawn_on_main_local(async move { - unsafe { - window.zoom_(nil); - } - }) - .detach(); + unsafe { + window.zoom_(nil); + } } fn toggle_full_screen(&self) { let this = self.0.lock(); let window = this.native_window; - this.executor - .spawn_on_main_local(async move { - unsafe { - window.toggleFullScreen_(nil); - } - }) - .detach(); + unsafe { + window.toggleFullScreen_(nil); + } } fn on_input(&self, callback: Box bool>) { @@ -1189,7 +1182,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { lock.synthetic_drag_counter += 1; let executor = lock.executor.clone(); executor - .spawn_on_main_local(synthetic_drag( + .spawn(synthetic_drag( weak_window_state, lock.synthetic_drag_counter, event.clone(), @@ -1317,7 +1310,7 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) let executor = lock.executor.clone(); drop(lock); executor - .spawn_on_main_local(async move { + .spawn(async move { let mut lock = window_state.as_ref().lock(); if let Some(mut callback) = lock.activate_callback.take() { drop(lock); diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 52a25d352c..98a7897752 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -215,58 +215,3 @@ impl PlatformDispatcher for TestDispatcher { Some(self) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::Executor; - use std::sync::Arc; - - #[test] - fn test_dispatch() { - let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0)); - let executor = Executor::new(Arc::new(dispatcher)); - - let result = executor.block(async { executor.run_on_main(|| 1).await }); - assert_eq!(result, 1); - - let result = executor.block({ - let executor = executor.clone(); - async move { - executor - .spawn_on_main({ - let executor = executor.clone(); - assert!(executor.is_main_thread()); - || async move { - assert!(executor.is_main_thread()); - let result = executor - .spawn({ - let executor = executor.clone(); - async move { - assert!(!executor.is_main_thread()); - - let result = executor - .spawn_on_main({ - let executor = executor.clone(); - || async move { - assert!(executor.is_main_thread()); - 2 - } - }) - .await; - - assert!(!executor.is_main_thread()); - result - } - }) - .await; - assert!(executor.is_main_thread()); - result - } - }) - .await - } - }); - assert_eq!(result, 2); - } -} diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 4d86c434d0..524611b620 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -1,21 +1,29 @@ -use crate::{DisplayId, Executor, Platform, PlatformTextSystem}; +use crate::{BackgroundExecutor, DisplayId, ForegroundExecutor, Platform, PlatformTextSystem}; use anyhow::{anyhow, Result}; use std::sync::Arc; pub struct TestPlatform { - executor: Executor, + background_executor: BackgroundExecutor, + foreground_executor: ForegroundExecutor, } impl TestPlatform { - pub fn new(executor: Executor) -> Self { - TestPlatform { executor } + pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Self { + TestPlatform { + background_executor: executor, + foreground_executor, + } } } // todo!("implement out what our tests needed in GPUI 1") impl Platform for TestPlatform { - fn executor(&self) -> Executor { - self.executor.clone() + fn background_executor(&self) -> BackgroundExecutor { + self.background_executor.clone() + } + + fn foreground_executor(&self) -> ForegroundExecutor { + self.foreground_executor.clone() } fn text_system(&self) -> Arc { diff --git a/crates/gpui2/src/subscription.rs b/crates/gpui2/src/subscription.rs index 3bf28792bb..c42a007622 100644 --- a/crates/gpui2/src/subscription.rs +++ b/crates/gpui2/src/subscription.rs @@ -21,8 +21,8 @@ struct SubscriberSetState { impl SubscriberSet where - EmitterKey: 'static + Send + Ord + Clone + Debug, - Callback: 'static + Send, + EmitterKey: 'static + Ord + Clone + Debug, + Callback: 'static, { pub fn new() -> Self { Self(Arc::new(Mutex::new(SubscriberSetState { @@ -96,7 +96,7 @@ where #[must_use] pub struct Subscription { - unsubscribe: Option>, + unsubscribe: Option>, } impl Subscription { diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index f1d54e7ae0..06b20a3088 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -4,10 +4,13 @@ use crate::{ Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; -use std::{any::TypeId, marker::PhantomData}; +use std::{ + any::{Any, TypeId}, + marker::PhantomData, +}; pub trait Render: 'static + Sized { - type Element: Element + 'static + Send; + type Element: Element + 'static; fn render(&mut self, cx: &mut ViewContext) -> Self::Element; } @@ -163,7 +166,7 @@ impl Component for EraseViewState Element for EraseViewState { - type ElementState = AnyBox; + type ElementState = Box; fn id(&self) -> Option { Element::id(&self.view) @@ -343,7 +346,7 @@ impl From> for AnyView { } impl Element for AnyView { - type ElementState = AnyBox; + type ElementState = Box; fn id(&self) -> Option { Some(self.model.entity_id.into()) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 3997a3197f..1ef8012828 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -3,12 +3,11 @@ use crate::{ 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, Reference, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView, - WindowOptions, SUBPIXEL_VARIANTS, + 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, WeakModel, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -52,7 +51,7 @@ pub enum DispatchPhase { Capture, } -type AnyListener = Box; +type AnyListener = Box; type AnyKeyListener = Box< dyn Fn( &dyn Any, @@ -60,10 +59,9 @@ type AnyKeyListener = Box< DispatchPhase, &mut WindowContext, ) -> Option> - + Send + 'static, >; -type AnyFocusListener = Box; +type AnyFocusListener = Box; slotmap::new_key_type! { pub struct FocusId; } @@ -159,7 +157,7 @@ impl Drop for FocusHandle { // Holds the state for a specific window. pub struct Window { handle: AnyWindowHandle, - platform_window: MainThreadOnly>, + platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, rem_size: Pixels, @@ -194,7 +192,7 @@ impl Window { pub(crate) fn new( handle: AnyWindowHandle, options: WindowOptions, - cx: &mut MainThread, + cx: &mut AppContext, ) -> Self { let platform_window = cx.platform().open_window(handle, options); let display_id = platform_window.display().id(); @@ -209,12 +207,7 @@ impl Window { 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.display_id = cx.window.platform_window.display().id(); cx.window.dirty = true; }) .log_err(); @@ -230,8 +223,6 @@ impl Window { }) }); - let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone()); - Window { handle, platform_window, @@ -378,26 +369,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.notify(); } - /// 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. - pub fn run_on_main( - &mut self, - f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, - ) -> Task> - where - R: Send + 'static, - { - if self.executor.is_main_thread() { - Task::ready(Ok(f(unsafe { - 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)) - } - } - /// Create an `AsyncWindowContext`, which has a static lifetime and can be held across /// await points in async code. pub fn to_async(&self) -> AsyncWindowContext { @@ -408,44 +379,39 @@ impl<'a, 'w> WindowContext<'a, 'w> { pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { let f = Box::new(f); let display_id = self.window.display_id; - self.run_on_main(move |cx| { - if let Some(callbacks) = cx.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 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 callbacks = cx - .next_frame_callbacks - .get_mut(&display_id) - .unwrap() - .drain(..) - .collect::>(); - for callback in callbacks { - callback(cx); - } - cx.run_on_main(move |cx| { - if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { - cx.platform().stop_display_link(display_id); - } - }) - .detach(); - }); - }), - ); + 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 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); + } - cx.platform().start_display_link(display_id); - }) - .detach(); + 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); } /// Spawn the future returned by the given closure on the application thread pool. @@ -453,17 +419,16 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// 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, - Fut: Future + Send + 'static, + R: 'static, + Fut: Future + 'static, { 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) }) } @@ -569,7 +534,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { /// a specific need to register a global listener. pub fn on_mouse_event( &mut self, - handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + 'static, + handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { let order = self.window.z_index_stack.clone(); self.window @@ -906,14 +871,8 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.root_view = Some(root_view); let scene = self.window.scene_builder.build(); - self.run_on_main(|cx| { - cx.window - .platform_window - .borrow_on_main_thread() - .draw(scene); - cx.window.dirty = false; - }) - .detach(); + self.window.platform_window.draw(scene); + self.window.dirty = false; } fn start_frame(&mut self) { @@ -1246,7 +1205,7 @@ impl Context for WindowContext<'_, '_> { build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, ) -> Model where - T: 'static + Send, + T: 'static, { let slot = self.app.entities.reserve(); let model = build_model(&mut ModelContext::mutable(&mut *self.app, slot.downgrade())); @@ -1276,7 +1235,7 @@ impl VisualContext for WindowContext<'_, '_> { build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, ) -> Self::Result> where - V: 'static + Send, + V: 'static, { let slot = self.app.entities.reserve(); let view = View { @@ -1422,7 +1381,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(Option, &mut Self) -> (R, S), ) -> R where - S: 'static + Send, + S: 'static, { self.with_element_id(id, |global_id, cx| { if let Some(any) = cx @@ -1460,7 +1419,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(Option, &mut Self) -> (R, S), ) -> R where - S: 'static + Send, + S: 'static, { if let Some(element_id) = element_id { self.with_element_state(element_id, f) @@ -1772,30 +1731,13 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { result } - pub fn run_on_main( - &mut self, - view: &mut V, - f: impl FnOnce(&mut V, &mut MainThread>) -> R + Send + 'static, - ) -> Task> - where - R: Send + 'static, - { - if self.executor.is_main_thread() { - let cx = unsafe { mem::transmute::<&mut Self, &mut MainThread>(self) }; - Task::ready(Ok(f(view, cx))) - } else { - let view = self.view().upgrade().unwrap(); - self.window_cx.run_on_main(move |cx| view.update(cx, f)) - } - } - 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, + R: 'static, + Fut: Future + 'static, { let view = self.view(); self.window_cx.spawn(move |_, cx| { @@ -1833,7 +1775,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { pub fn on_mouse_event( &mut self, - handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + Send + 'static, + handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { let handle = self.view().upgrade().unwrap(); self.window_cx.on_mouse_event(move |event, phase, cx| { @@ -1862,13 +1804,10 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { type ModelContext<'b, U> = ModelContext<'b, U>; type Result = U; - fn build_model( + fn build_model( &mut self, build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T, - ) -> Model - where - T: 'static + Send, - { + ) -> Model { self.window_cx.build_model(build_model) } @@ -1884,7 +1823,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>; - fn build_view( + fn build_view( &mut self, build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W, ) -> Self::Result> { diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 717a80619b..8233caf62f 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -17,7 +17,7 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt as _, }; -use gpui2::{AppContext, AsyncAppContext, Executor, Task}; +use gpui2::{AppContext, AsyncAppContext, BackgroundExecutor, Task}; pub use highlight_map::HighlightMap; use lazy_static::lazy_static; use lsp2::{CodeActionKind, LanguageServerBinary}; @@ -631,7 +631,7 @@ pub struct LanguageRegistry { lsp_binary_paths: Mutex< HashMap>>>>, >, - executor: Option, + executor: Option, lsp_binary_status_tx: LspBinaryStatusSender, } @@ -680,7 +680,7 @@ impl LanguageRegistry { Self::new(Task::ready(())) } - pub fn set_executor(&mut self, executor: Executor) { + pub fn set_executor(&mut self, executor: BackgroundExecutor) { self.executor = Some(executor); } @@ -916,7 +916,7 @@ impl LanguageRegistry { } let servers_tx = servers_tx.clone(); - cx.executor() + cx.background_executor() .spawn(async move { if fake_server .try_receive_notification::() diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 27b7b36e73..70f908b45e 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, Executor, Task}; +use gpui2::{AsyncAppContext, BackgroundExecutor, Task}; use parking_lot::Mutex; use postage::{barrier, prelude::Stream}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -62,7 +62,7 @@ pub struct LanguageServer { notification_handlers: Arc>>, response_handlers: Arc>>>, io_handlers: Arc>>, - executor: Executor, + executor: BackgroundExecutor, #[allow(clippy::type_complexity)] io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, @@ -248,7 +248,7 @@ impl LanguageServer { let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task); stdout.or(stderr) }); - let output_task = cx.executor().spawn({ + let output_task = cx.background_executor().spawn({ Self::handle_output( stdin, outbound_rx, @@ -269,7 +269,7 @@ impl LanguageServer { code_action_kinds, next_id: Default::default(), outbound_tx, - executor: cx.executor().clone(), + executor: cx.background_executor().clone(), io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), @@ -670,10 +670,10 @@ impl LanguageServer { match serde_json::from_str(params) { Ok(params) => { let response = f(params, cx.clone()); - cx.executor() - .spawn_on_main({ + cx.foreground_executor() + .spawn({ let outbound_tx = outbound_tx.clone(); - move || async move { + async move { let response = match response.await { Ok(result) => Response { jsonrpc: JSON_RPC_VERSION, @@ -769,7 +769,7 @@ impl LanguageServer { next_id: &AtomicUsize, response_handlers: &Mutex>>, outbound_tx: &channel::Sender, - executor: &Executor, + executor: &BackgroundExecutor, params: T::Params, ) -> impl 'static + Future> where @@ -1048,7 +1048,7 @@ impl FakeLanguageServer { let result = handler(params, cx.clone()); let responded_tx = responded_tx.clone(); async move { - cx.executor().simulate_random_delay().await; + cx.background_executor().simulate_random_delay().await; let result = result.await; responded_tx.unbounded_send(()).ok(); result diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 41b6296367..748e619e96 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -26,8 +26,8 @@ use futures::{ }; use globset::{Glob, GlobSet, GlobSetBuilder}; use gpui2::{ - AnyModel, AppContext, AsyncAppContext, Context, Entity, EventEmitter, Executor, Model, - ModelContext, Task, WeakModel, + AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter, + Model, ModelContext, Task, WeakModel, }; use itertools::Itertools; use language2::{ @@ -207,7 +207,7 @@ impl DelayedDebounced { let previous_task = self.task.take(); self.task = Some(cx.spawn(move |project, 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; } @@ -1453,7 +1453,7 @@ impl Project { }; if client.send(initial_state).log_err().is_some() { let client = client.clone(); - cx.executor() + cx.background_executor() .spawn(async move { let mut chunks = split_operations(operations).peekable(); while let Some(chunk) = chunks.next() { @@ -2436,7 +2436,7 @@ impl Project { Duration::from_secs(1); let task = cx.spawn(move |this, mut cx| async move { - cx.executor().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await; + cx.background_executor().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await; if let Some(this) = this.upgrade() { this.update(&mut cx, |this, cx| { this.disk_based_diagnostics_finished( @@ -3477,7 +3477,7 @@ impl Project { }); const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); - let mut timeout = cx.executor().timer(PROCESS_TIMEOUT).fuse(); + let mut timeout = cx.background_executor().timer(PROCESS_TIMEOUT).fuse(); let mut errored = false; if let Some(mut process) = process { @@ -5741,7 +5741,7 @@ impl Project { async fn background_search( unnamed_buffers: Vec>, opened_buffers: HashMap, (Model, BufferSnapshot)>, - executor: Executor, + executor: BackgroundExecutor, fs: Arc, workers: usize, query: SearchQuery, @@ -6376,7 +6376,7 @@ impl Project { let snapshot = worktree_handle.update(&mut cx, |tree, _| tree.as_local().unwrap().snapshot())?; let diff_bases_by_buffer = cx - .executor() + .background_executor() .spawn(async move { future_buffers .into_iter() @@ -7983,7 +7983,7 @@ impl Project { // Any incomplete buffers have open requests waiting. Request that the host sends // creates these buffers for us again to unblock any waiting futures. for id in incomplete_buffer_ids { - cx.executor() + cx.background_executor() .spawn(client.request(proto::OpenBufferById { project_id, id })) .detach(); } @@ -8438,7 +8438,7 @@ impl Project { let fs = self.fs.clone(); cx.spawn(move |this, mut cx| async move { let prettier_dir = match cx - .executor() + .background_executor() .spawn(Prettier::locate( worktree_path.zip(buffer_path).map( |(worktree_root_path, starting_path)| LocateStart { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index 6a13bc3599..dd90df81b3 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -22,7 +22,8 @@ use futures::{ use fuzzy2::CharBag; use git::{DOT_GIT, GITIGNORE}; use gpui2::{ - AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext, Task, + AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext, + Task, }; use language2::{ proto::{ @@ -600,7 +601,7 @@ impl LocalWorktree { .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))? .await?; let text_buffer = cx - .executor() + .background_executor() .spawn(async move { text::Buffer::new(0, id, contents) }) .await; cx.build_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file)))) @@ -888,7 +889,7 @@ impl LocalWorktree { if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) { let repo = repo.repo_ptr.clone(); index_task = Some( - cx.executor() + cx.background_executor() .spawn(async move { repo.lock().load_index_text(&repo_path) }), ); } @@ -3012,7 +3013,7 @@ struct BackgroundScanner { state: Mutex, fs: Arc, status_updates_tx: UnboundedSender, - executor: Executor, + executor: BackgroundExecutor, scan_requests_rx: channel::Receiver, path_prefixes_to_scan_rx: channel::Receiver>, next_entry_id: Arc, @@ -3032,7 +3033,7 @@ impl BackgroundScanner { next_entry_id: Arc, fs: Arc, status_updates_tx: UnboundedSender, - executor: Executor, + executor: BackgroundExecutor, scan_requests_rx: channel::Receiver, path_prefixes_to_scan_rx: channel::Receiver>, ) -> Self { diff --git a/crates/rpc2/src/conn.rs b/crates/rpc2/src/conn.rs index 902e9822d5..ec3c5b68cf 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::Executor, + executor: gpui2::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::Executor, + executor: gpui2::BackgroundExecutor, ) -> ( Box>, Box>>, diff --git a/crates/rpc2/src/peer.rs b/crates/rpc2/src/peer.rs index 6dfb170f4c..367eba2b4e 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::Executor, + executor: gpui2::BackgroundExecutor, ) -> ( ConnectionId, impl Future> + Send, diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index 2105035605..c3903c1c22 100644 --- a/crates/settings2/src/settings_file.rs +++ b/crates/settings2/src/settings_file.rs @@ -2,7 +2,7 @@ use crate::{settings_store::SettingsStore, Settings}; use anyhow::Result; use fs2::Fs; use futures::{channel::mpsc, StreamExt}; -use gpui2::{AppContext, Executor}; +use gpui2::{AppContext, BackgroundExecutor}; use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; use util::{paths, ResultExt}; @@ -28,7 +28,7 @@ pub fn test_settings() -> String { } pub fn watch_config_file( - executor: &Executor, + executor: &BackgroundExecutor, fs: Arc, path: PathBuf, ) -> mpsc::UnboundedReceiver { diff --git a/crates/sqlez/src/thread_safe_connection.rs b/crates/sqlez/src/thread_safe_connection.rs index ffadb3af41..54241b6d72 100644 --- a/crates/sqlez/src/thread_safe_connection.rs +++ b/crates/sqlez/src/thread_safe_connection.rs @@ -336,13 +336,13 @@ mod test { FOREIGN KEY(dock_pane) REFERENCES panes(pane_id), FOREIGN KEY(active_pane) REFERENCES panes(pane_id) ) 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 + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; "] diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 7cee320373..94b5820d90 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -133,9 +133,9 @@ impl<'a> VimTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static + Send, - F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Send + Future>, + T::Params: 'static, + F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Future>, { self.cx.handle_request::(handler) } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 82eacd9710..20b14a249a 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -62,20 +62,26 @@ fn main() { log::info!("========== starting zed =========="); let app = App::production(Arc::new(Assets)); - let installation_id = app.executor().block(installation_id()).ok(); + let installation_id = app.background_executor().block(installation_id()).ok(); let session_id = Uuid::new_v4().to_string(); init_panic_hook(&app, installation_id.clone(), session_id.clone()); let fs = Arc::new(RealFs); - let user_settings_file_rx = - watch_config_file(&app.executor(), fs.clone(), paths::SETTINGS.clone()); - let _user_keymap_file_rx = - watch_config_file(&app.executor(), fs.clone(), paths::KEYMAP.clone()); + let user_settings_file_rx = watch_config_file( + &app.background_executor(), + fs.clone(), + paths::SETTINGS.clone(), + ); + let _user_keymap_file_rx = watch_config_file( + &app.background_executor(), + fs.clone(), + paths::KEYMAP.clone(), + ); let login_shell_env_loaded = if stdout_is_a_pty() { Task::ready(()) } else { - app.executor().spawn(async { + app.background_executor().spawn(async { load_login_shell_environment().await.log_err(); }) }; From dd1a2a9e441ea03c79a664c37b56de2cc615db18 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 11:59:49 -0700 Subject: [PATCH 073/156] wip --- crates/ai2/src/auth.rs | 9 +- .../ai2/src/providers/open_ai/completion.rs | 51 +++++------ crates/audio2/src/audio2.rs | 72 +++++---------- crates/client2/src/client2.rs | 90 +++++++++---------- crates/client2/src/telemetry.rs | 4 +- .../src/test/editor_lsp_test_context.rs | 6 +- crates/fuzzy2/src/strings.rs | 4 +- crates/gpui2/src/app.rs | 24 ++--- crates/gpui2/src/element.rs | 12 +-- crates/gpui2_macros/src/derive_component.rs | 4 + crates/gpui2_macros/src/test.rs | 8 +- crates/install_cli2/src/install_cli2.rs | 4 +- crates/language2/src/outline.rs | 4 +- crates/lsp2/src/lsp2.rs | 3 +- crates/terminal2/src/terminal2.rs | 18 ++-- crates/vim/src/test/vim_test_context.rs | 6 +- 16 files changed, 138 insertions(+), 181 deletions(-) diff --git a/crates/ai2/src/auth.rs b/crates/ai2/src/auth.rs index e4670bb449..2f689f2cda 100644 --- a/crates/ai2/src/auth.rs +++ b/crates/ai2/src/auth.rs @@ -8,10 +8,9 @@ pub enum ProviderCredential { NotNeeded, } -#[async_trait] -pub trait CredentialProvider: Send + Sync { +pub trait CredentialProvider { fn has_credentials(&self) -> bool; - async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential; - async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential); - async fn delete_credentials(&self, cx: &mut AppContext); + fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential; + fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential); + fn delete_credentials(&self, cx: &mut AppContext); } diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index 1582659e71..840841a936 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -213,7 +213,6 @@ impl OpenAICompletionProvider { } } -#[async_trait] impl CredentialProvider for OpenAICompletionProvider { fn has_credentials(&self) -> bool { match *self.credential.read() { @@ -221,50 +220,44 @@ impl CredentialProvider for OpenAICompletionProvider { _ => false, } } - async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential { + + fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential { let existing_credential = self.credential.read().clone(); - - let retrieved_credential = cx - .run_on_main(move |cx| match existing_credential { - ProviderCredential::Credentials { .. } => { - return existing_credential.clone(); - } - _ => { - if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() { - return ProviderCredential::Credentials { api_key }; - } - - if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err() - { - if let Some(api_key) = String::from_utf8(api_key).log_err() { - return ProviderCredential::Credentials { api_key }; - } else { - return ProviderCredential::NoCredentials; - } + let retrieved_credential = match existing_credential { + ProviderCredential::Credentials { .. } => existing_credential.clone(), + _ => { + if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() { + ProviderCredential::Credentials { api_key } + } else if let Some(Some((_, api_key))) = + cx.read_credentials(OPENAI_API_URL).log_err() + { + if let Some(api_key) = String::from_utf8(api_key).log_err() { + ProviderCredential::Credentials { api_key } } else { - return ProviderCredential::NoCredentials; + ProviderCredential::NoCredentials } + } else { + ProviderCredential::NoCredentials } - }) - .await; - + } + }; *self.credential.write() = retrieved_credential.clone(); retrieved_credential } - async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) { + fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) { *self.credential.write() = credential.clone(); let credential = credential.clone(); - cx.run_on_main(move |cx| match credential { + match credential { ProviderCredential::Credentials { api_key } => { cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()) .log_err(); } _ => {} - }) - .await; + } } - async fn delete_credentials(&self, cx: &mut AppContext) { + + fn delete_credentials(&self, cx: &mut AppContext) { cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err()) .await; *self.credential.write() = ProviderCredential::NoCredentials; diff --git a/crates/audio2/src/audio2.rs b/crates/audio2/src/audio2.rs index 5a27b79349..286b07aba1 100644 --- a/crates/audio2/src/audio2.rs +++ b/crates/audio2/src/audio2.rs @@ -1,14 +1,13 @@ use assets::SoundRegistry; -use futures::{channel::mpsc, StreamExt}; -use gpui2::{AppContext, AssetSource, BackgroundExecutor}; +use gpui2::{AppContext, AssetSource}; use rodio::{OutputStream, OutputStreamHandle}; use util::ResultExt; mod assets; pub fn init(source: impl AssetSource, cx: &mut AppContext) { - cx.set_global(Audio::new(cx.executor())); cx.set_global(SoundRegistry::new(source)); + cx.set_global(Audio::new()); } pub enum Sound { @@ -34,15 +33,18 @@ impl Sound { } pub struct Audio { - tx: mpsc::UnboundedSender>, -} - -struct AudioState { _output_stream: Option, output_handle: Option, } -impl AudioState { +impl Audio { + pub fn new() -> Self { + Self { + _output_stream: None, + output_handle: None, + } + } + fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> { if self.output_handle.is_none() { let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip(); @@ -53,59 +55,27 @@ impl AudioState { self.output_handle.as_ref() } - fn take(&mut self) { - self._output_stream.take(); - self.output_handle.take(); - } -} - -impl Audio { - pub fn new(executor: &BackgroundExecutor) -> Self { - let (tx, mut rx) = mpsc::unbounded::>(); - executor - .spawn_on_main(|| async move { - let mut audio = AudioState { - _output_stream: None, - output_handle: None, - }; - - while let Some(f) = rx.next().await { - (f)(&mut audio); - } - }) - .detach(); - - Self { tx } - } - pub fn play_sound(sound: Sound, cx: &mut AppContext) { if !cx.has_global::() { return; } - let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else { - return; - }; - - let this = cx.global::(); - this.tx - .unbounded_send(Box::new(move |state| { - if let Some(output_handle) = state.ensure_output_exists() { - output_handle.play_raw(source).log_err(); - } - })) - .ok(); + cx.update_global::(|this, cx| { + let output_handle = this.ensure_output_exists()?; + let source = SoundRegistry::global(cx).get(sound.file()).log_err()?; + output_handle.play_raw(source).log_err()?; + Some(()) + }); } - pub fn end_call(cx: &AppContext) { + pub fn end_call(cx: &mut AppContext) { if !cx.has_global::() { return; } - let this = cx.global::(); - - this.tx - .unbounded_send(Box::new(move |state| state.take())) - .ok(); + cx.update_global::(|this, _| { + this._output_stream.take(); + this.output_handle.take(); + }); } } diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 435d5f1840..50a6bf1632 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -11,7 +11,8 @@ use async_tungstenite::tungstenite::{ http::{Request, StatusCode}, }; use futures::{ - future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt, + future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, + TryStreamExt, }; use gpui2::{ serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task, @@ -233,14 +234,12 @@ struct ClientState { message_handlers: HashMap< TypeId, Arc< - dyn Send - + Sync - + Fn( - AnyModel, - Box, - &Arc, - AsyncAppContext, - ) -> BoxFuture<'static, Result<()>>, + dyn Fn( + AnyModel, + Box, + &Arc, + AsyncAppContext, + ) -> LocalBoxFuture<'static, Result<()>>, >, >, } @@ -310,10 +309,7 @@ pub struct PendingEntitySubscription { consumed: bool, } -impl PendingEntitySubscription -where - T: 'static + Send, -{ +impl PendingEntitySubscription { pub fn set_model(mut self, model: &Model, cx: &mut AsyncAppContext) -> Subscription { self.consumed = true; let mut state = self.client.state.write(); @@ -341,10 +337,7 @@ where } } -impl Drop for PendingEntitySubscription -where - T: 'static, -{ +impl Drop for PendingEntitySubscription { fn drop(&mut self) { if !self.consumed { let mut state = self.client.state.write(); @@ -529,7 +522,7 @@ impl Client { remote_id: u64, ) -> Result> where - T: 'static + Send, + T: 'static, { let id = (TypeId::of::(), remote_id); @@ -557,9 +550,9 @@ impl Client { ) -> Subscription where M: EnvelopedMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + F: 'static + Future>, { let message_type_id = TypeId::of::(); @@ -573,7 +566,7 @@ impl Client { Arc::new(move |subscriber, envelope, client, cx| { let subscriber = subscriber.downcast::().unwrap(); let envelope = envelope.into_any().downcast::>().unwrap(); - handler(subscriber, *envelope, client.clone(), cx).boxed() + handler(subscriber, *envelope, client.clone(), cx).boxed_local() }), ); if prev_handler.is_some() { @@ -599,9 +592,9 @@ impl Client { ) -> Subscription where M: RequestMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + F: 'static + Future>, { self.add_message_handler(model, move |handle, envelope, this, cx| { Self::respond_to_request( @@ -615,9 +608,9 @@ impl Client { pub fn add_model_message_handler(self: &Arc, handler: H) where M: EntityMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + F: 'static + Future>, { self.add_entity_message_handler::(move |subscriber, message, client, cx| { handler(subscriber.downcast::().unwrap(), message, client, cx) @@ -627,9 +620,9 @@ impl Client { fn add_entity_message_handler(self: &Arc, handler: H) where M: EntityMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F, + F: 'static + Future>, { let model_type_id = TypeId::of::(); let message_type_id = TypeId::of::(); @@ -655,7 +648,7 @@ impl Client { message_type_id, Arc::new(move |handle, envelope, client, cx| { let envelope = envelope.into_any().downcast::>().unwrap(); - handler(handle, *envelope, client.clone(), cx).boxed() + handler(handle, *envelope, client.clone(), cx).boxed_local() }), ); if prev_handler.is_some() { @@ -666,9 +659,9 @@ impl Client { pub fn add_model_request_handler(self: &Arc, handler: H) where M: EntityMessage + RequestMessage, - E: 'static + Send, - H: 'static + Send + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, - F: 'static + Future> + Send, + E: 'static, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + F: 'static + Future>, { self.add_model_message_handler(move |entity, envelope, client, cx| { Self::respond_to_request::( @@ -705,7 +698,7 @@ impl Client { read_credentials_from_keychain(cx).await.is_some() } - #[async_recursion] + #[async_recursion(?Send)] pub async fn authenticate_and_connect( self: &Arc, try_keychain: bool, @@ -1050,7 +1043,7 @@ impl Client { write!(&mut url, "&impersonate={}", impersonate_login).unwrap(); } - cx.run_on_main(move |cx| cx.open_url(&url))?.await; + cx.update(|cx| cx.open_url(&url))?; // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted // access token from the query params. @@ -1101,7 +1094,7 @@ impl Client { let access_token = private_key .decrypt_string(&access_token) .context("failed to decrypt access token")?; - cx.run_on_main(|cx| cx.activate(true))?.await; + cx.update(|cx| cx.activate(true))?; Ok(Credentials { user_id: user_id.parse()?, @@ -1293,7 +1286,7 @@ impl Client { sender_id, type_name ); - cx.spawn_on_main(move |_| async move { + cx.spawn(move |_| async move { match future.await { Ok(()) => { log::debug!( @@ -1332,9 +1325,8 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option Result<()> { - cx.run_on_main(move |cx| { + cx.update(move |cx| { cx.write_credentials( &ZED_SERVER_URL, &credentials.user_id.to_string(), credentials.access_token.as_bytes(), ) })? - .await } async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { - cx.run_on_main(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? - .await + cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? } const WORKTREE_URL_PREFIX: &str = "zed://worktrees/"; @@ -1430,7 +1420,7 @@ mod tests { // Time out when client tries to connect. client.override_authenticate(move |cx| { - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { Ok(Credentials { user_id, access_token: "token".into(), @@ -1438,7 +1428,7 @@ mod tests { }) }); client.override_establish_connection(|_, cx| { - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { future::pending::<()>().await; unreachable!() }) @@ -1472,7 +1462,7 @@ mod tests { // Time out when re-establishing the connection. server.allow_connections(); client.override_establish_connection(|_, cx| { - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { future::pending::<()>().await; unreachable!() }) @@ -1504,7 +1494,7 @@ mod tests { move |cx| { let auth_count = auth_count.clone(); let dropped_auth_count = dropped_auth_count.clone(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { *auth_count.lock() += 1; let _drop = util::defer(move || *dropped_auth_count.lock() += 1); future::pending::<()>().await; diff --git a/crates/client2/src/telemetry.rs b/crates/client2/src/telemetry.rs index 47d1c143e1..7fa57f9fb6 100644 --- a/crates/client2/src/telemetry.rs +++ b/crates/client2/src/telemetry.rs @@ -1,5 +1,5 @@ use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; -use gpui2::{serde_json, AppContext, AppMetadata, Executor, Task}; +use gpui2::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; @@ -14,7 +14,7 @@ use util::{channel::ReleaseChannel, TryFutureExt}; pub struct Telemetry { http_client: Arc, - executor: Executor, + executor: BackgroundExecutor, state: Mutex, } diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 59b1cbce05..3e2f38a0b6 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -266,9 +266,9 @@ impl<'a> EditorLspTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static, - F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Future>, + T::Params: 'static + Send, + F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Send + Future>, { let url = self.buffer_lsp_url.clone(); self.lsp.handle_request::(move |params, cx| { diff --git a/crates/fuzzy2/src/strings.rs b/crates/fuzzy2/src/strings.rs index 6f7533ddd0..7c71496a13 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::Executor; +use gpui2::BackgroundExecutor; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -83,7 +83,7 @@ pub async fn match_strings( smart_case: bool, max_results: usize, cancel_flag: &AtomicBool, - executor: Executor, + executor: BackgroundExecutor, ) -> Vec { if candidates.is_empty() || max_results == 0 { return Default::default(); diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index b3747f3cbf..3ab2d8c1f8 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -107,11 +107,11 @@ impl App { } type ActionBuilder = fn(json: Option) -> anyhow::Result>; -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 FrameCallback = Box; +type Handler = Box bool + 'static>; +type Listener = Box bool + 'static>; +type QuitHandler = Box BoxFuture<'static, ()> + 'static>; +type ReleaseListener = Box; pub struct AppContext { this: Weak>, @@ -133,7 +133,7 @@ pub struct AppContext { pub(crate) windows: SlotMap>, pub(crate) keymap: Arc>, pub(crate) global_action_listeners: - HashMap>>, + HashMap>>, action_builders: HashMap, pending_effects: VecDeque, pub(crate) pending_notifications: HashSet, @@ -295,7 +295,7 @@ impl AppContext { 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 WindowContext) -> View + 'static, ) -> WindowHandle { self.update(|cx| { let id = cx.windows.insert(None); @@ -520,7 +520,7 @@ impl AppContext { .retain(&type_id, |observer| observer(self)); } - fn apply_defer_effect(&mut self, callback: Box) { + fn apply_defer_effect(&mut self, callback: Box) { callback(self); } @@ -551,7 +551,7 @@ impl AppContext { /// 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 AppContext) + 'static + Send) { + pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) { self.push_effect(Effect::Defer { callback: Box::new(f), }); @@ -639,7 +639,7 @@ impl AppContext { /// Register a callback to be invoked when a global of the given type is updated. pub fn observe_global( &mut self, - mut f: impl FnMut(&mut Self) + Send + 'static, + mut f: impl FnMut(&mut Self) + 'static, ) -> Subscription { self.global_observers.insert( TypeId::of::(), @@ -686,7 +686,7 @@ impl AppContext { } /// Register a global listener for actions invoked via the keyboard. - pub fn on_action(&mut self, listener: impl Fn(&A, &mut Self) + Send + 'static) { + pub fn on_action(&mut self, listener: impl Fn(&A, &mut Self) + 'static) { self.global_action_listeners .entry(TypeId::of::()) .or_default() @@ -778,7 +778,7 @@ pub(crate) enum Effect { global_type: TypeId, }, Defer { - callback: Box, + callback: Box, }, } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 8bd6bcc700..e41dc4ebd5 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -218,8 +218,8 @@ impl Component for AnyElement { impl Element for Option where V: 'static, - E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + E: 'static + Component, + F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, { type ElementState = AnyElement; @@ -262,8 +262,8 @@ where impl Component for Option where V: 'static, - E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + E: 'static + Component, + F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -273,8 +273,8 @@ where impl Component for F where V: 'static, - E: 'static + Component + Send, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static, + E: 'static + Component, + F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(Some(self)) diff --git a/crates/gpui2_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index d1919c8bc4..b5887e5620 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -36,6 +36,10 @@ pub fn derive_component(input: TokenStream) -> TokenStream { } }; + if name == "CollabPanel" { + println!("{}", expanded) + } + TokenStream::from(expanded) } diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index b5a2111e19..7fb499f9f1 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -89,8 +89,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(_seed),)); continue; } - Some("Executor") => { - inner_fn_args.extend(quote!(gpui2::Executor::new( + Some("BackgroundExecutor") => { + inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new( std::sync::Arc::new(dispatcher.clone()) ),)); continue; @@ -134,7 +134,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { #num_iterations as u64, #max_retries, &mut |dispatcher, _seed| { - let executor = gpui2::Executor::new(std::sync::Arc::new(dispatcher.clone())); + let executor = gpui2::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone())); #cx_vars executor.block(#inner_fn_name(#inner_fn_args)); #cx_teardowns @@ -170,7 +170,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let mut #cx_varname = gpui2::TestAppContext::new( dispatcher.clone() ); - let mut #cx_varname_lock = #cx_varname.app.lock(); + let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); )); inner_fn_args.extend(quote!(&mut #cx_varname_lock,)); cx_teardowns.extend(quote!( diff --git a/crates/install_cli2/src/install_cli2.rs b/crates/install_cli2/src/install_cli2.rs index ecdf2a0f2a..e24a48ef07 100644 --- a/crates/install_cli2/src/install_cli2.rs +++ b/crates/install_cli2/src/install_cli2.rs @@ -7,9 +7,7 @@ use util::ResultExt; // actions!(cli, [Install]); pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { - let cli_path = cx - .run_on_main(|cx| cx.path_for_auxiliary_executable("cli"))? - .await?; + let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??; let link_path = Path::new("/usr/local/bin/zed"); let bin_dir_path = link_path.parent().unwrap(); diff --git a/crates/language2/src/outline.rs b/crates/language2/src/outline.rs index dd3a4acf6b..94dfaa0e11 100644 --- a/crates/language2/src/outline.rs +++ b/crates/language2/src/outline.rs @@ -1,5 +1,5 @@ use fuzzy2::{StringMatch, StringMatchCandidate}; -use gpui2::{Executor, HighlightStyle}; +use gpui2::{BackgroundExecutor, HighlightStyle}; use std::ops::Range; #[derive(Debug)] @@ -57,7 +57,7 @@ impl Outline { } } - pub async fn search(&self, query: &str, executor: Executor) -> Vec { + pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec { let query = query.trim_start(); let is_path_query = query.contains(' '); let smart_case = query.chars().any(|c| c.is_uppercase()); diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 70f908b45e..120e749d19 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -1047,8 +1047,9 @@ impl FakeLanguageServer { .on_request::(move |params, cx| { let result = handler(params, cx.clone()); let responded_tx = responded_tx.clone(); + let executor = cx.background_executor().clone(); async move { - cx.background_executor().simulate_random_delay().await; + executor.simulate_random_delay().await; let result = result.await; responded_tx.unbounded_send(()).ok(); result diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 5cf73576bc..b683fd5b51 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -51,8 +51,8 @@ use thiserror::Error; use gpui2::{ px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke, - MainThread, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, Point, ScrollWheelEvent, Size, Task, TouchPhase, + ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, + Point, ScrollWheelEvent, Size, Task, TouchPhase, }; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; @@ -403,7 +403,7 @@ impl TerminalBuilder { pub fn subscribe(mut self, cx: &mut ModelContext) -> Terminal { //Event loop - cx.spawn_on_main(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { use futures::StreamExt; while let Some(event) = self.events_rx.next().await { @@ -414,7 +414,10 @@ impl TerminalBuilder { 'outer: loop { let mut events = vec![]; - let mut timer = cx.executor().timer(Duration::from_millis(4)).fuse(); + let mut timer = cx + .background_executor() + .timer(Duration::from_millis(4)) + .fuse(); let mut wakeup = false; loop { futures::select_biased! { @@ -551,7 +554,7 @@ pub struct Terminal { } impl Terminal { - fn process_event(&mut self, event: &AlacTermEvent, cx: &mut MainThread>) { + fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext) { match event { AlacTermEvent::Title(title) => { self.breadcrumb_text = title.to_string(); @@ -708,8 +711,7 @@ impl Terminal { InternalEvent::Copy => { if let Some(txt) = term.selection_to_string() { - cx.run_on_main(|cx| cx.write_to_clipboard(ClipboardItem::new(txt))) - .detach(); + cx.write_to_clipboard(ClipboardItem::new(txt)) } } InternalEvent::ScrollToAlacPoint(point) => { @@ -1189,7 +1191,7 @@ impl Terminal { &mut self, e: &MouseUpEvent, origin: Point, - cx: &mut MainThread>, + cx: &mut ModelContext, ) { let setting = TerminalSettings::get_global(cx); diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 94b5820d90..7cee320373 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -133,9 +133,9 @@ impl<'a> VimTestContext<'a> { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - T::Params: 'static, - F: 'static + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, - Fut: 'static + Future>, + T::Params: 'static + Send, + F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Send + Future>, { self.cx.handle_request::(handler) } From 11b6d9e33ac5b3c4bbaff7f8b21ce7d782378d40 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 13:53:08 -0600 Subject: [PATCH 074/156] Split out a foreground and background executor --- crates/ai2/src/auth.rs | 1 - .../ai2/src/providers/open_ai/completion.rs | 4 +- crates/ai2/src/providers/open_ai/embedding.rs | 52 ++++++++----------- crates/ai2/src/test.rs | 14 +++-- crates/call2/src/call2.rs | 4 +- crates/call2/src/room.rs | 9 ++-- crates/client2/src/client2.rs | 32 ++++++++---- crates/client2/src/telemetry.rs | 2 +- crates/copilot2/src/copilot2.rs | 12 ++--- crates/db2/src/db2.rs | 2 +- crates/gpui2/src/app.rs | 15 ++++-- crates/gpui2/src/app/model_context.rs | 28 +++++----- crates/gpui2/src/app/test_context.rs | 8 +-- crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/executor.rs | 2 +- crates/journal2/src/journal2.rs | 2 +- crates/language2/src/buffer.rs | 16 +++--- crates/language2/src/buffer_tests.rs | 10 ++-- crates/lsp2/src/lsp2.rs | 10 ++-- crates/prettier2/src/prettier2.rs | 2 +- crates/project2/src/lsp_command.rs | 24 ++++----- crates/project2/src/project2.rs | 24 ++++----- crates/project2/src/worktree.rs | 25 ++++----- crates/settings2/src/settings_file.rs | 5 +- crates/terminal2/src/terminal2.rs | 4 +- crates/zed2/src/main.rs | 6 +-- 26 files changed, 165 insertions(+), 150 deletions(-) diff --git a/crates/ai2/src/auth.rs b/crates/ai2/src/auth.rs index 2f689f2cda..995f20d39c 100644 --- a/crates/ai2/src/auth.rs +++ b/crates/ai2/src/auth.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use gpui2::AppContext; #[derive(Clone, Debug)] diff --git a/crates/ai2/src/providers/open_ai/completion.rs b/crates/ai2/src/providers/open_ai/completion.rs index 840841a936..bf9dc704a2 100644 --- a/crates/ai2/src/providers/open_ai/completion.rs +++ b/crates/ai2/src/providers/open_ai/completion.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; -use async_trait::async_trait; use futures::{ future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt, Stream, StreamExt, @@ -258,8 +257,7 @@ impl CredentialProvider for OpenAICompletionProvider { } fn delete_credentials(&self, cx: &mut AppContext) { - cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err()) - .await; + cx.delete_credentials(OPENAI_API_URL).log_err(); *self.credential.write() = ProviderCredential::NoCredentials; } } diff --git a/crates/ai2/src/providers/open_ai/embedding.rs b/crates/ai2/src/providers/open_ai/embedding.rs index dde4af1273..27a01328f3 100644 --- a/crates/ai2/src/providers/open_ai/embedding.rs +++ b/crates/ai2/src/providers/open_ai/embedding.rs @@ -146,7 +146,6 @@ impl OpenAIEmbeddingProvider { } } -#[async_trait] impl CredentialProvider for OpenAIEmbeddingProvider { fn has_credentials(&self) -> bool { match *self.credential.read() { @@ -154,52 +153,45 @@ impl CredentialProvider for OpenAIEmbeddingProvider { _ => false, } } - async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential { + fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential { let existing_credential = self.credential.read().clone(); - let retrieved_credential = cx - .run_on_main(move |cx| match existing_credential { - ProviderCredential::Credentials { .. } => { - return existing_credential.clone(); - } - _ => { - if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() { - return ProviderCredential::Credentials { api_key }; - } - - if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err() - { - if let Some(api_key) = String::from_utf8(api_key).log_err() { - return ProviderCredential::Credentials { api_key }; - } else { - return ProviderCredential::NoCredentials; - } + let retrieved_credential = match existing_credential { + ProviderCredential::Credentials { .. } => existing_credential.clone(), + _ => { + if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() { + ProviderCredential::Credentials { api_key } + } else if let Some(Some((_, api_key))) = + cx.read_credentials(OPENAI_API_URL).log_err() + { + if let Some(api_key) = String::from_utf8(api_key).log_err() { + ProviderCredential::Credentials { api_key } } else { - return ProviderCredential::NoCredentials; + ProviderCredential::NoCredentials } + } else { + ProviderCredential::NoCredentials } - }) - .await; + } + }; *self.credential.write() = retrieved_credential.clone(); retrieved_credential } - async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) { + fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) { *self.credential.write() = credential.clone(); - let credential = credential.clone(); - cx.run_on_main(move |cx| match credential { + match credential { ProviderCredential::Credentials { api_key } => { cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()) .log_err(); } _ => {} - }) - .await; + } } - async fn delete_credentials(&self, cx: &mut AppContext) { - cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err()) - .await; + + fn delete_credentials(&self, cx: &mut AppContext) { + cx.delete_credentials(OPENAI_API_URL).log_err(); *self.credential.write() = ProviderCredential::NoCredentials; } } diff --git a/crates/ai2/src/test.rs b/crates/ai2/src/test.rs index ee88529aec..b061a47139 100644 --- a/crates/ai2/src/test.rs +++ b/crates/ai2/src/test.rs @@ -100,16 +100,15 @@ impl FakeEmbeddingProvider { } } -#[async_trait] impl CredentialProvider for FakeEmbeddingProvider { fn has_credentials(&self) -> bool { true } - async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential { + fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential { ProviderCredential::NotNeeded } - async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {} - async fn delete_credentials(&self, _cx: &mut AppContext) {} + fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {} + fn delete_credentials(&self, _cx: &mut AppContext) {} } #[async_trait] @@ -162,16 +161,15 @@ impl FakeCompletionProvider { } } -#[async_trait] impl CredentialProvider for FakeCompletionProvider { fn has_credentials(&self) -> bool { true } - async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential { + fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential { ProviderCredential::NotNeeded } - async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {} - async fn delete_credentials(&self, _cx: &mut AppContext) {} + fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {} + fn delete_credentials(&self, _cx: &mut AppContext) {} } impl CompletionProvider for FakeCompletionProvider { diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index fd09dc3180..9383f9845f 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -196,7 +196,7 @@ impl ActiveCall { }) .shared(); self.pending_room_creation = Some(room.clone()); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { room.await.map_err(|err| anyhow!("{:?}", err))?; anyhow::Ok(()) }) @@ -230,7 +230,7 @@ impl ActiveCall { }; let client = self.client.clone(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { client .request(proto::CancelCall { room_id, diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index 556f9e778e..f44bdd35b5 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -322,7 +322,7 @@ impl Room { fn app_will_quit(&mut self, cx: &mut ModelContext) -> impl Future { let task = if self.status.is_online() { let leave = self.leave_internal(cx); - Some(cx.executor().spawn(async move { + Some(cx.background_executor().spawn(async move { leave.await.log_err(); })) } else { @@ -390,7 +390,7 @@ impl Room { self.clear_state(cx); let leave_room = self.client.request(proto::LeaveRoom {}); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { leave_room.await?; anyhow::Ok(()) }) @@ -1202,7 +1202,7 @@ impl Room { }; cx.notify(); - cx.executor().spawn_on_main(move || async move { + cx.background_executor().spawn(async move { client .request(proto::UpdateParticipantLocation { room_id, @@ -1569,7 +1569,8 @@ impl LiveKitRoom { *muted = should_mute; cx.notify(); Ok(( - cx.executor().spawn(track_publication.set_mute(*muted)), + cx.background_executor() + .spawn(track_publication.set_mute(*muted)), old_muted, )) } diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 50a6bf1632..b933b62a6f 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -234,12 +234,14 @@ struct ClientState { message_handlers: HashMap< TypeId, Arc< - dyn Fn( - AnyModel, - Box, - &Arc, - AsyncAppContext, - ) -> LocalBoxFuture<'static, Result<()>>, + dyn Send + + Sync + + Fn( + AnyModel, + Box, + &Arc, + AsyncAppContext, + ) -> LocalBoxFuture<'static, Result<()>>, >, >, } @@ -551,7 +553,11 @@ impl Client { where M: EnvelopedMessage, E: 'static, - H: 'static + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + + Sync + + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + + Send + + Sync, F: 'static + Future>, { let message_type_id = TypeId::of::(); @@ -593,7 +599,11 @@ impl Client { where M: RequestMessage, E: 'static, - H: 'static + Sync + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + + Sync + + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + + Send + + Sync, F: 'static + Future>, { self.add_message_handler(model, move |handle, envelope, this, cx| { @@ -609,7 +619,7 @@ impl Client { where M: EntityMessage, E: 'static, - H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, F: 'static + Future>, { self.add_entity_message_handler::(move |subscriber, message, client, cx| { @@ -621,7 +631,7 @@ impl Client { where M: EntityMessage, E: 'static, - H: 'static + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Fn(AnyModel, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, F: 'static + Future>, { let model_type_id = TypeId::of::(); @@ -660,7 +670,7 @@ impl Client { where M: EntityMessage + RequestMessage, E: 'static, - H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F, + H: 'static + Fn(Model, TypedEnvelope, Arc, AsyncAppContext) -> F + Send + Sync, F: 'static + Future>, { self.add_model_message_handler(move |entity, envelope, client, cx| { diff --git a/crates/client2/src/telemetry.rs b/crates/client2/src/telemetry.rs index 7fa57f9fb6..0ef5f0d140 100644 --- a/crates/client2/src/telemetry.rs +++ b/crates/client2/src/telemetry.rs @@ -123,7 +123,7 @@ impl Telemetry { // TODO: Replace all hardware stuff with nested SystemSpecs json let this = Arc::new(Self { http_client: client, - executor: cx.executor().clone(), + executor: cx.background_executor().clone(), state: Mutex::new(TelemetryState { app_metadata: cx.app_metadata(), architecture: env::consts::ARCH, diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 3d50834e94..3b059775cd 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -535,7 +535,7 @@ impl Copilot { } }; - cx.executor() + cx.background_executor() .spawn(task.map_err(|err| anyhow!("{:?}", err))) } else { // If we're downloading, wait until download is finished @@ -549,7 +549,7 @@ impl Copilot { self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx); if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server { let server = server.clone(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { server .request::(request::SignOutParams {}) .await?; @@ -579,7 +579,7 @@ impl Copilot { cx.notify(); - cx.executor().spawn(start_task) + cx.background_executor().spawn(start_task) } pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc)> { @@ -760,7 +760,7 @@ impl Copilot { .request::(request::NotifyAcceptedParams { uuid: completion.uuid.clone(), }); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { request.await?; Ok(()) }) @@ -784,7 +784,7 @@ impl Copilot { .map(|completion| completion.uuid.clone()) .collect(), }); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { request.await?; Ok(()) }) @@ -827,7 +827,7 @@ impl Copilot { .map(|file| file.path().to_path_buf()) .unwrap_or_default(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let (version, snapshot) = snapshot.await?; let result = lsp .request::(request::GetCompletionsParams { diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs index e2e1ae9eaa..fe79dfbb0c 100644 --- a/crates/db2/src/db2.rs +++ b/crates/db2/src/db2.rs @@ -185,7 +185,7 @@ pub fn write_and_log(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send where F: Future> + Send, { - cx.executor() + cx.background_executor() .spawn(async move { db_write().await.log_err() }) .detach() } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 3ab2d8c1f8..265ce59a02 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -21,7 +21,7 @@ use crate::{ }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; -use futures::{future::BoxFuture, Future}; +use futures::{future::LocalBoxFuture, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use std::{ @@ -101,6 +101,10 @@ impl App { self.0.borrow().background_executor.clone() } + pub fn foreground_executor(&self) -> ForegroundExecutor { + self.0.borrow().foreground_executor.clone() + } + pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -110,7 +114,7 @@ type ActionBuilder = fn(json: Option) -> anyhow::Result; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; -type QuitHandler = Box BoxFuture<'static, ()> + 'static>; +type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; type ReleaseListener = Box; pub struct AppContext { @@ -535,10 +539,15 @@ impl AppContext { } /// Obtains a reference to the executor, which can be used to spawn futures. - pub fn executor(&self) -> &BackgroundExecutor { + pub fn background_executor(&self) -> &BackgroundExecutor { &self.background_executor } + /// Obtains a reference to the executor, which can be used to spawn futures. + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor + } + /// 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) -> Task diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index f0fc1f07f0..ee8c8871e6 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -43,10 +43,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn observe( &mut self, entity: &E, - mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + Send + 'static, + mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where - T: 'static + Send, + T: 'static, T2: 'static, E: Entity, { @@ -69,10 +69,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + Send + 'static, + mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where - T: 'static + Send, + T: 'static, T2: 'static + EventEmitter, E: Entity, { @@ -95,7 +95,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn on_release( &mut self, - mut on_release: impl FnMut(&mut T, &mut AppContext) + Send + 'static, + mut on_release: impl FnMut(&mut T, &mut AppContext) + 'static, ) -> Subscription where T: 'static, @@ -112,10 +112,10 @@ 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>) + Send + 'static, + mut on_release: impl FnMut(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where - T: Any + Send, + T: Any, T2: 'static, E: Entity, { @@ -134,10 +134,10 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn observe_global( &mut self, - mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + Send + 'static, + mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where - T: 'static + Send, + T: 'static, { let handle = self.weak_model(); self.global_observers.insert( @@ -148,11 +148,11 @@ impl<'a, T: 'static> ModelContext<'a, T> { pub fn on_app_quit( &mut self, - mut on_quit: impl FnMut(&mut T, &mut ModelContext) -> Fut + Send + 'static, + mut on_quit: impl FnMut(&mut T, &mut ModelContext) -> Fut + 'static, ) -> Subscription where - Fut: 'static + Future + Send, - T: 'static + Send, + Fut: 'static + Future, + T: 'static, { let handle = self.weak_model(); self.app.quit_observers.insert( @@ -164,7 +164,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { future.await; } } - .boxed() + .boxed_local() }), ) } @@ -183,7 +183,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { 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); diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index e3bf8eb7da..624ec67eac 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -135,18 +135,20 @@ impl TestAppContext { } } - pub fn subscribe( + pub fn subscribe( &mut self, entity: &Model, ) -> futures::channel::mpsc::UnboundedReceiver where - T::Event: 'static + Send + Clone, + T::Event: 'static + Clone, { let (mut tx, rx) = futures::channel::mpsc::unbounded(); entity .update(self, |_, cx: &mut ModelContext| { cx.subscribe(entity, move |_, _, event, cx| { - cx.executor().block(tx.send(event.clone())).unwrap(); + cx.background_executor() + .block(tx.send(event.clone())) + .unwrap(); }) }) .detach(); diff --git a/crates/gpui2/src/assets.rs b/crates/gpui2/src/assets.rs index 0437b3d6de..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 + Sync { +pub trait AssetSource: 'static + Send + Sync { fn load(&self, path: &str) -> Result>; fn list(&self, path: &str) -> Result>; } diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index d0b65fa10e..5a04cf40f0 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -53,7 +53,7 @@ where E: 'static + Send + Debug, { pub fn detach_and_log_err(self, cx: &mut AppContext) { - cx.executor().spawn(self.log_err()).detach(); + cx.background_executor().spawn(self.log_err()).detach(); } } diff --git a/crates/journal2/src/journal2.rs b/crates/journal2/src/journal2.rs index 6268548530..d875cb3834 100644 --- a/crates/journal2/src/journal2.rs +++ b/crates/journal2/src/journal2.rs @@ -77,7 +77,7 @@ pub fn new_journal_entry(_: Arc, cx: &mut AppContext) { let now = now.time(); let _entry_heading = heading_entry(now, &settings.hour_format); - let _create_entry = cx.executor().spawn(async move { + let _create_entry = cx.background_executor().spawn(async move { std::fs::create_dir_all(month_dir)?; OpenOptions::new() .create(true) diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index 3ab68d9f44..d8e0149460 100644 --- a/crates/language2/src/buffer.rs +++ b/crates/language2/src/buffer.rs @@ -652,7 +652,7 @@ impl Buffer { if !self.is_dirty() { let reload = self.reload(cx).log_err().map(drop); - task = cx.executor().spawn(reload); + task = cx.background_executor().spawn(reload); } } } @@ -684,7 +684,7 @@ impl Buffer { let snapshot = self.snapshot(); let mut diff = self.git_diff.clone(); - let diff = cx.executor().spawn(async move { + let diff = cx.background_executor().spawn(async move { diff.update(&diff_base, &snapshot).await; diff }); @@ -793,7 +793,7 @@ impl Buffer { let mut syntax_snapshot = syntax_map.snapshot(); drop(syntax_map); - let parse_task = cx.executor().spawn({ + let parse_task = cx.background_executor().spawn({ let language = language.clone(); let language_registry = language_registry.clone(); async move { @@ -803,7 +803,7 @@ impl Buffer { }); match cx - .executor() + .background_executor() .block_with_timeout(self.sync_parse_timeout, parse_task) { Ok(new_syntax_snapshot) => { @@ -866,9 +866,9 @@ impl Buffer { fn request_autoindent(&mut self, cx: &mut ModelContext) { if let Some(indent_sizes) = self.compute_autoindents() { - let indent_sizes = cx.executor().spawn(indent_sizes); + let indent_sizes = cx.background_executor().spawn(indent_sizes); match cx - .executor() + .background_executor() .block_with_timeout(Duration::from_micros(500), indent_sizes) { Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx), @@ -1117,7 +1117,7 @@ impl Buffer { pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task { let old_text = self.as_rope().clone(); let base_version = self.version(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let old_text = old_text.to_string(); let line_ending = LineEnding::detect(&new_text); LineEnding::normalize(&mut new_text); @@ -1155,7 +1155,7 @@ impl Buffer { let old_text = self.as_rope().clone(); let line_ending = self.line_ending(); let base_version = self.version(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let ranges = trailing_whitespace_ranges(&old_text); let empty = Arc::::from(""); Diff { diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index d2d886dd84..2012509878 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/crates/language2/src/buffer_tests.rs @@ -559,7 +559,7 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) { cx: &'a gpui2::TestAppContext, ) -> Vec<(&'a str, Vec)> { let matches = cx - .update(|cx| outline.search(query, cx.executor().clone())) + .update(|cx| outline.search(query, cx.background_executor().clone())) .await; matches .into_iter() @@ -1879,7 +1879,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) { let state = buffer1.read(cx).to_proto(); let ops = cx - .executor() + .background_executor() .block(buffer1.read(cx).serialize_ops(None, cx)); let buffer2 = cx.build_model(|cx| { let mut buffer = Buffer::from_proto(1, state, None).unwrap(); @@ -1921,7 +1921,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { let buffer = cx.build_model(|cx| { let state = base_buffer.read(cx).to_proto(); let ops = cx - .executor() + .background_executor() .block(base_buffer.read(cx).serialize_ops(None, cx)); let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap(); buffer @@ -2025,7 +2025,9 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { } 50..=59 if replica_ids.len() < max_peers => { let old_buffer_state = buffer.read(cx).to_proto(); - let old_buffer_ops = cx.executor().block(buffer.read(cx).serialize_ops(None, cx)); + let old_buffer_ops = cx + .background_executor() + .block(buffer.read(cx).serialize_ops(None, cx)); let new_replica_id = (0..=replica_ids.len() as ReplicaId) .filter(|replica_id| *replica_id != buffer.read(cx).replica_id()) .choose(&mut rng) diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index 120e749d19..ed67a5c9c2 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -595,8 +595,8 @@ impl LanguageServer { where T: request::Request, T::Params: 'static + Send, - F: 'static + Send + FnMut(T::Params, AsyncAppContext) -> Fut, - Fut: 'static + Future> + Send, + F: 'static + FnMut(T::Params, AsyncAppContext) -> Fut + Send, + Fut: 'static + Future>, { self.on_custom_request(T::METHOD, f) } @@ -629,7 +629,7 @@ impl LanguageServer { #[must_use] pub fn on_custom_notification(&self, method: &'static str, mut f: F) -> Subscription where - F: 'static + Send + FnMut(Params, AsyncAppContext), + F: 'static + FnMut(Params, AsyncAppContext) + Send, Params: DeserializeOwned, { let prev_handler = self.notification_handlers.lock().insert( @@ -657,8 +657,8 @@ impl LanguageServer { mut f: F, ) -> Subscription where - F: 'static + Send + FnMut(Params, AsyncAppContext) -> Fut, - Fut: 'static + Future> + Send, + F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send, + Fut: 'static + Future>, Params: DeserializeOwned + Send + 'static, Res: Serialize, { diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index a71bf1a8b0..6d9664b234 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/crates/prettier2/src/prettier2.rs @@ -143,7 +143,7 @@ impl Prettier { ) -> anyhow::Result { use lsp2::LanguageServerBinary; - let executor = cx.executor().clone(); + let executor = cx.background_executor().clone(); anyhow::ensure!( prettier_dir.is_dir(), "Prettier dir {prettier_dir:?} is not a directory" diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index 84a6c0517c..9e6a96e15e 100644 --- a/crates/project2/src/lsp_command.rs +++ b/crates/project2/src/lsp_command.rs @@ -32,7 +32,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp2::FormattingOptions { } } -#[async_trait] +#[async_trait(?Send)] pub(crate) trait LspCommand: 'static + Sized + Send { type Response: 'static + Default + Send; type LspRequest: 'static + Send + lsp2::request::Request; @@ -148,7 +148,7 @@ impl From for FormattingOptions { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for PrepareRename { type Response = Option>; type LspRequest = lsp2::request::PrepareRenameRequest; @@ -279,7 +279,7 @@ impl LspCommand for PrepareRename { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for PerformRename { type Response = ProjectTransaction; type LspRequest = lsp2::request::Rename; @@ -398,7 +398,7 @@ impl LspCommand for PerformRename { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetDefinition { type Response = Vec; type LspRequest = lsp2::request::GotoDefinition; @@ -491,7 +491,7 @@ impl LspCommand for GetDefinition { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetTypeDefinition { type Response = Vec; type LspRequest = lsp2::request::GotoTypeDefinition; @@ -783,7 +783,7 @@ fn location_links_to_proto( .collect() } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetReferences { type Response = Vec; type LspRequest = lsp2::request::References; @@ -945,7 +945,7 @@ impl LspCommand for GetReferences { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetDocumentHighlights { type Response = Vec; type LspRequest = lsp2::request::DocumentHighlightRequest; @@ -1096,7 +1096,7 @@ impl LspCommand for GetDocumentHighlights { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetHover { type Response = Option; type LspRequest = lsp2::request::HoverRequest; @@ -1314,7 +1314,7 @@ impl LspCommand for GetHover { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetCompletions { type Response = Vec; type LspRequest = lsp2::request::Completion; @@ -1545,7 +1545,7 @@ impl LspCommand for GetCompletions { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for GetCodeActions { type Response = Vec; type LspRequest = lsp2::request::CodeActionRequest; @@ -1684,7 +1684,7 @@ impl LspCommand for GetCodeActions { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for OnTypeFormatting { type Response = Option; type LspRequest = lsp2::request::OnTypeFormatting; @@ -2192,7 +2192,7 @@ impl InlayHints { } } -#[async_trait] +#[async_trait(?Send)] impl LspCommand for InlayHints { type Response = Vec; type LspRequest = lsp2::InlayHintRequest; diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 748e619e96..1457bd41cc 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -1758,7 +1758,7 @@ impl Project { } }; - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { wait_for_loading_buffer(loading_watch) .await .map_err(|error| anyhow!("{}", error)) @@ -5593,7 +5593,7 @@ impl Project { }) .collect::>(); - let background = cx.executor().clone(); + let background = cx.background_executor().clone(); let path_count: usize = snapshots.iter().map(|s| s.visible_file_count()).sum(); if path_count == 0 { let (_, rx) = smol::channel::bounded(1024); @@ -5616,11 +5616,11 @@ impl Project { } }) .collect(); - cx.executor() + cx.background_executor() .spawn(Self::background_search( unnamed_files, opened_buffers, - cx.executor().clone(), + cx.background_executor().clone(), self.fs.clone(), workers, query.clone(), @@ -5631,9 +5631,9 @@ impl Project { .detach(); let (buffers, buffers_rx) = Self::sort_candidates_and_open_buffers(matching_paths_rx, cx); - let background = cx.executor().clone(); + let background = cx.background_executor().clone(); let (result_tx, result_rx) = smol::channel::bounded(1024); - cx.executor() + cx.background_executor() .spawn(async move { let Ok(buffers) = buffers.await else { return; @@ -5993,7 +5993,7 @@ impl Project { Task::ready(Ok((tree, relative_path))) } else { let worktree = self.create_local_worktree(abs_path, visible, cx); - cx.executor() + cx.background_executor() .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) } } @@ -6064,7 +6064,7 @@ impl Project { .shared() }) .clone(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { match task.await { Ok(worktree) => Ok(worktree), Err(err) => Err(anyhow!("{}", err)), @@ -6519,7 +6519,7 @@ impl Project { }) .collect::>(); - cx.executor() + cx.background_executor() .spawn(async move { for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { async move { @@ -7358,7 +7358,7 @@ impl Project { }) .log_err(); - cx.executor() + cx.background_executor() .spawn( async move { let operations = operations.await; @@ -7960,7 +7960,7 @@ impl Project { if let Some(buffer) = this.buffer_for_id(buffer_id) { let operations = buffer.read(cx).serialize_ops(Some(remote_version), cx); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let operations = operations.await; for chunk in split_operations(operations) { client @@ -8198,7 +8198,7 @@ impl Project { cx: &mut ModelContext, ) -> Task, String)>>> { let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let snapshot = snapshot?; let mut lsp_edits = lsp_edits .into_iter() diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index dd90df81b3..2718b5d8f0 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -365,10 +365,10 @@ impl Worktree { }) .detach(); - let background_scanner_task = cx.executor().spawn({ + let background_scanner_task = cx.background_executor().spawn({ let fs = fs.clone(); let snapshot = snapshot.clone(); - let background = cx.executor().clone(); + let background = cx.background_executor().clone(); async move { let events = fs.watch(&abs_path, Duration::from_millis(100)).await; BackgroundScanner::new( @@ -429,7 +429,7 @@ impl Worktree { let background_snapshot = Arc::new(Mutex::new(snapshot.clone())); let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel(); - cx.executor() + cx.background_executor() .spawn({ let background_snapshot = background_snapshot.clone(); async move { @@ -1008,7 +1008,7 @@ impl LocalWorktree { let lowest_ancestor = self.lowest_ancestor(&path); let abs_path = self.absolutize(&path); let fs = self.fs.clone(); - let write = cx.executor().spawn(async move { + let write = cx.background_executor().spawn(async move { if is_dir { fs.create_dir(&abs_path).await } else { @@ -1058,7 +1058,7 @@ impl LocalWorktree { let abs_path = self.absolutize(&path); let fs = self.fs.clone(); let write = cx - .executor() + .background_executor() .spawn(async move { fs.save(&abs_path, &text, line_ending).await }); cx.spawn(|this, mut cx| async move { @@ -1079,7 +1079,7 @@ impl LocalWorktree { let abs_path = self.absolutize(&entry.path); let fs = self.fs.clone(); - let delete = cx.executor().spawn(async move { + let delete = cx.background_executor().spawn(async move { if entry.is_file() { fs.remove_file(&abs_path, Default::default()).await?; } else { @@ -1119,7 +1119,7 @@ impl LocalWorktree { let abs_old_path = self.absolutize(&old_path); let abs_new_path = self.absolutize(&new_path); let fs = self.fs.clone(); - let rename = cx.executor().spawn(async move { + let rename = cx.background_executor().spawn(async move { fs.rename(&abs_old_path, &abs_new_path, Default::default()) .await }); @@ -1146,7 +1146,7 @@ impl LocalWorktree { let abs_old_path = self.absolutize(&old_path); let abs_new_path = self.absolutize(&new_path); let fs = self.fs.clone(); - let copy = cx.executor().spawn(async move { + let copy = cx.background_executor().spawn(async move { copy_recursive( fs.as_ref(), &abs_old_path, @@ -1174,7 +1174,7 @@ impl LocalWorktree { ) -> Option>> { let path = self.entry_for_id(entry_id)?.path.clone(); let mut refresh = self.refresh_entries_for_paths(vec![path]); - Some(cx.executor().spawn(async move { + Some(cx.background_executor().spawn(async move { refresh.next().await; Ok(()) })) @@ -1248,7 +1248,7 @@ impl LocalWorktree { .ok(); let worktree_id = cx.entity_id().as_u64(); - let _maintain_remote_snapshot = cx.executor().spawn(async move { + let _maintain_remote_snapshot = cx.background_executor().spawn(async move { let mut is_first = true; while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await { let update; @@ -1306,7 +1306,7 @@ impl LocalWorktree { let rx = self.observe_updates(project_id, cx, move |update| { client.request(update).map(|result| result.is_ok()) }); - cx.executor() + cx.background_executor() .spawn(async move { rx.await.map_err(|_| anyhow!("share ended")) }) } @@ -2672,7 +2672,8 @@ impl language2::LocalFile for File { let worktree = self.worktree.read(cx).as_local().unwrap(); let abs_path = worktree.absolutize(&self.path); let fs = worktree.fs.clone(); - cx.executor().spawn(async move { fs.load(&abs_path).await }) + cx.background_executor() + .spawn(async move { fs.load(&abs_path).await }) } fn buffer_reloaded( diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index c3903c1c22..002c9daf12 100644 --- a/crates/settings2/src/settings_file.rs +++ b/crates/settings2/src/settings_file.rs @@ -63,7 +63,10 @@ pub fn handle_settings_file_changes( mut user_settings_file_rx: mpsc::UnboundedReceiver, cx: &mut AppContext, ) { - let user_settings_content = cx.executor().block(user_settings_file_rx.next()).unwrap(); + let user_settings_content = cx + .background_executor() + .block(user_settings_file_rx.next()) + .unwrap(); cx.update_global(|store: &mut SettingsStore, cx| { store .set_user_settings(&user_settings_content, cx) diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index b683fd5b51..adc5dd3511 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -984,7 +984,7 @@ impl Terminal { term.lock_unfair() //It's been too long, force block } else if let None = self.sync_task { //Skip this frame - let delay = cx.executor().timer(Duration::from_millis(16)); + let delay = cx.background_executor().timer(Duration::from_millis(16)); self.sync_task = Some(cx.spawn(|weak_handle, mut cx| async move { delay.await; if let Some(handle) = weak_handle.upgrade() { @@ -1302,7 +1302,7 @@ impl Terminal { cx: &mut ModelContext, ) -> Task>> { let term = self.term.clone(); - cx.executor().spawn(async move { + cx.background_executor().spawn(async move { let term = term.lock(); all_search_matches(&term, &searcher).collect() diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 20b14a249a..1233bee327 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -117,7 +117,7 @@ fn main() { let client = client2::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.executor().clone()); + languages.set_executor(cx.background_executor().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); let node_runtime = RealNodeRuntime::new(http.clone()); @@ -514,7 +514,7 @@ 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); - cx.executor() + cx.background_executor() .spawn(async move { let panic_report_url = format!("{}/api/panic", &*client2::ZED_SERVER_URL); let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; @@ -644,7 +644,7 @@ fn load_embedded_fonts(cx: &AppContext) { let asset_source = cx.asset_source(); let font_paths = asset_source.list("fonts").unwrap(); let embedded_fonts = Mutex::new(Vec::new()); - let executor = cx.executor(); + let executor = cx.background_executor(); executor.block(executor.scoped(|scope| { for font_path in &font_paths { From 3f34a8e7ec6b7e7604b1fa82b4450c3472707dc4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 14:00:26 -0600 Subject: [PATCH 075/156] Checkpoint --- crates/gpui2/src/executor.rs | 2 +- crates/gpui2/src/platform.rs | 2 +- crates/gpui2/src/platform/mac/dispatcher.rs | 23 +------------------- crates/gpui2/src/platform/test/dispatcher.rs | 18 +++++++++------ 4 files changed, 14 insertions(+), 31 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 5a04cf40f0..c25eeac899 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -102,7 +102,7 @@ impl BackgroundExecutor { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, Poll::Pending => { - if !self.dispatcher.poll() { + if !self.dispatcher.poll(true) { if awoken.swap(false, SeqCst) { continue; } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 7c2dbcce18..6e710daf6c 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -162,7 +162,7 @@ pub trait PlatformDispatcher: Send + Sync { fn dispatch(&self, runnable: Runnable); fn dispatch_on_main_thread(&self, runnable: Runnable); fn dispatch_after(&self, duration: Duration, runnable: Runnable); - fn poll(&self) -> bool; + fn poll(&self, background_only: bool) -> bool; #[cfg(any(test, feature = "test-support"))] fn as_test(&self) -> Option<&TestDispatcher> { diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index a4ae2cc028..f19de6f627 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -68,7 +68,7 @@ impl PlatformDispatcher for MacDispatcher { } } - fn poll(&self) -> bool { + fn poll(&self, _background_only: bool) -> bool { false } } @@ -77,24 +77,3 @@ extern "C" fn trampoline(runnable: *mut c_void) { let task = unsafe { Runnable::from_raw(runnable as *mut ()) }; task.run(); } - -// #include - -// int main(void) { - -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -// // Do some lengthy background work here... -// printf("Background Work\n"); - -// dispatch_async(dispatch_get_main_queue(), ^{ -// // Once done, update your UI on the main queue here. -// printf("UI Updated\n"); - -// }); -// }); - -// sleep(3); // prevent the program from terminating immediately - -// return 0; -// } -// ``` diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 98a7897752..e537f86311 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -96,7 +96,7 @@ impl TestDispatcher { } pub fn run_until_parked(&self) { - while self.poll() {} + while self.poll(false) {} } pub fn parking_allowed(&self) -> bool { @@ -160,7 +160,7 @@ impl PlatformDispatcher for TestDispatcher { state.delayed.insert(ix, (next_time, runnable)); } - fn poll(&self) -> bool { + fn poll(&self, background_only: bool) -> bool { let mut state = self.state.lock(); while let Some((deadline, _)) = state.delayed.first() { @@ -171,11 +171,15 @@ impl PlatformDispatcher for TestDispatcher { state.background.push(runnable); } - let foreground_len: usize = state - .foreground - .values() - .map(|runnables| runnables.len()) - .sum(); + let foreground_len: usize = if background_only { + 0 + } else { + state + .foreground + .values() + .map(|runnables| runnables.len()) + .sum() + }; let background_len = state.background.len(); if foreground_len == 0 && background_len == 0 { From 77dbb15aa3a942c80522bec64a55c37cf7e5a530 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Wed, 1 Nov 2023 16:13:53 -0400 Subject: [PATCH 076/156] port text2 to zed2 --- Cargo.lock | 25 +- crates/text2/Cargo.toml | 37 + crates/text2/src/anchor.rs | 144 ++ crates/text2/src/locator.rs | 125 ++ crates/text2/src/network.rs | 69 + crates/text2/src/operation_queue.rs | 153 ++ crates/text2/src/patch.rs | 594 ++++++ crates/text2/src/selection.rs | 123 ++ crates/text2/src/subscription.rs | 48 + crates/text2/src/tests.rs | 764 ++++++++ crates/text2/src/text2.rs | 2682 +++++++++++++++++++++++++++ crates/text2/src/undo_map.rs | 112 ++ crates/zed2/Cargo.toml | 4 +- 13 files changed, 4877 insertions(+), 3 deletions(-) create mode 100644 crates/text2/Cargo.toml create mode 100644 crates/text2/src/anchor.rs create mode 100644 crates/text2/src/locator.rs create mode 100644 crates/text2/src/network.rs create mode 100644 crates/text2/src/operation_queue.rs create mode 100644 crates/text2/src/patch.rs create mode 100644 crates/text2/src/selection.rs create mode 100644 crates/text2/src/subscription.rs create mode 100644 crates/text2/src/tests.rs create mode 100644 crates/text2/src/text2.rs create mode 100644 crates/text2/src/undo_map.rs diff --git a/Cargo.lock b/Cargo.lock index 755f4440d9..5d6ce29b14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8806,6 +8806,29 @@ dependencies = [ "util", ] +[[package]] +name = "text2" +version = "0.1.0" +dependencies = [ + "anyhow", + "clock", + "collections", + "ctor", + "digest 0.9.0", + "env_logger 0.9.3", + "gpui2", + "lazy_static", + "log", + "parking_lot 0.11.2", + "postage", + "rand 0.8.5", + "regex", + "rope", + "smallvec", + "sum_tree", + "util", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -11052,7 +11075,7 @@ dependencies = [ "smol", "sum_tree", "tempdir", - "text", + "text2", "theme2", "thiserror", "tiny_http", diff --git a/crates/text2/Cargo.toml b/crates/text2/Cargo.toml new file mode 100644 index 0000000000..6891fef680 --- /dev/null +++ b/crates/text2/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "text2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/text2.rs" +doctest = false + +[features] +test-support = ["rand"] + +[dependencies] +clock = { path = "../clock" } +collections = { path = "../collections" } +rope = { path = "../rope" } +sum_tree = { path = "../sum_tree" } +util = { path = "../util" } + +anyhow.workspace = true +digest = { version = "0.9", features = ["std"] } +lazy_static.workspace = true +log.workspace = true +parking_lot.workspace = true +postage.workspace = true +rand = { workspace = true, optional = true } +smallvec.workspace = true +regex.workspace = true + +[dev-dependencies] +collections = { path = "../collections", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true diff --git a/crates/text2/src/anchor.rs b/crates/text2/src/anchor.rs new file mode 100644 index 0000000000..084be0e336 --- /dev/null +++ b/crates/text2/src/anchor.rs @@ -0,0 +1,144 @@ +use crate::{ + locator::Locator, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint, + ToPointUtf16, +}; +use anyhow::Result; +use std::{cmp::Ordering, fmt::Debug, ops::Range}; +use sum_tree::Bias; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)] +pub struct Anchor { + pub timestamp: clock::Lamport, + pub offset: usize, + pub bias: Bias, + pub buffer_id: Option, +} + +impl Anchor { + pub const MIN: Self = Self { + timestamp: clock::Lamport::MIN, + offset: usize::MIN, + bias: Bias::Left, + buffer_id: None, + }; + + pub const MAX: Self = Self { + timestamp: clock::Lamport::MAX, + offset: usize::MAX, + bias: Bias::Right, + buffer_id: None, + }; + + pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering { + let fragment_id_comparison = if self.timestamp == other.timestamp { + Ordering::Equal + } else { + buffer + .fragment_id_for_anchor(self) + .cmp(buffer.fragment_id_for_anchor(other)) + }; + + fragment_id_comparison + .then_with(|| self.offset.cmp(&other.offset)) + .then_with(|| self.bias.cmp(&other.bias)) + } + + pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self { + if self.cmp(other, buffer).is_le() { + *self + } else { + *other + } + } + + pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self { + if self.cmp(other, buffer).is_ge() { + *self + } else { + *other + } + } + + pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor { + if bias == Bias::Left { + self.bias_left(buffer) + } else { + self.bias_right(buffer) + } + } + + pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor { + if self.bias == Bias::Left { + *self + } else { + buffer.anchor_before(self) + } + } + + pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor { + if self.bias == Bias::Right { + *self + } else { + buffer.anchor_after(self) + } + } + + pub fn summary(&self, content: &BufferSnapshot) -> D + where + D: TextDimension, + { + content.summary_for_anchor(self) + } + + /// Returns true when the [Anchor] is located inside a visible fragment. + pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { + if *self == Anchor::MIN || *self == Anchor::MAX { + true + } else { + let fragment_id = buffer.fragment_id_for_anchor(self); + let mut fragment_cursor = buffer.fragments.cursor::<(Option<&Locator>, usize)>(); + fragment_cursor.seek(&Some(fragment_id), Bias::Left, &None); + fragment_cursor + .item() + .map_or(false, |fragment| fragment.visible) + } + } +} + +pub trait OffsetRangeExt { + fn to_offset(&self, snapshot: &BufferSnapshot) -> Range; + fn to_point(&self, snapshot: &BufferSnapshot) -> Range; + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range; +} + +impl OffsetRangeExt for Range +where + T: ToOffset, +{ + fn to_offset(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot)..self.end.to_offset(snapshot) + } + + fn to_point(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot).to_point(snapshot) + ..self.end.to_offset(snapshot).to_point(snapshot) + } + + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot).to_point_utf16(snapshot) + ..self.end.to_offset(snapshot).to_point_utf16(snapshot) + } +} + +pub trait AnchorRangeExt { + fn cmp(&self, b: &Range, buffer: &BufferSnapshot) -> Result; +} + +impl AnchorRangeExt for Range { + fn cmp(&self, other: &Range, buffer: &BufferSnapshot) -> Result { + Ok(match self.start.cmp(&other.start, buffer) { + Ordering::Equal => other.end.cmp(&self.end, buffer), + ord => ord, + }) + } +} diff --git a/crates/text2/src/locator.rs b/crates/text2/src/locator.rs new file mode 100644 index 0000000000..27fdb34cde --- /dev/null +++ b/crates/text2/src/locator.rs @@ -0,0 +1,125 @@ +use lazy_static::lazy_static; +use smallvec::{smallvec, SmallVec}; +use std::iter; + +lazy_static! { + static ref MIN: Locator = Locator::min(); + static ref MAX: Locator = Locator::max(); +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Locator(SmallVec<[u64; 4]>); + +impl Locator { + pub fn min() -> Self { + Self(smallvec![u64::MIN]) + } + + pub fn max() -> Self { + Self(smallvec![u64::MAX]) + } + + pub fn min_ref() -> &'static Self { + &*MIN + } + + pub fn max_ref() -> &'static Self { + &*MAX + } + + pub fn assign(&mut self, other: &Self) { + self.0.resize(other.0.len(), 0); + self.0.copy_from_slice(&other.0); + } + + pub fn between(lhs: &Self, rhs: &Self) -> Self { + let lhs = lhs.0.iter().copied().chain(iter::repeat(u64::MIN)); + let rhs = rhs.0.iter().copied().chain(iter::repeat(u64::MAX)); + let mut location = SmallVec::new(); + for (lhs, rhs) in lhs.zip(rhs) { + let mid = lhs + ((rhs.saturating_sub(lhs)) >> 48); + location.push(mid); + if mid > lhs { + break; + } + } + Self(location) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for Locator { + fn default() -> Self { + Self::min() + } +} + +impl sum_tree::Item for Locator { + type Summary = Locator; + + fn summary(&self) -> Self::Summary { + self.clone() + } +} + +impl sum_tree::KeyedItem for Locator { + type Key = Locator; + + fn key(&self) -> Self::Key { + self.clone() + } +} + +impl sum_tree::Summary for Locator { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.assign(summary); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::mem; + + #[gpui2::test(iterations = 100)] + fn test_locators(mut rng: StdRng) { + let mut lhs = Default::default(); + let mut rhs = Default::default(); + while lhs == rhs { + lhs = Locator( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + rhs = Locator( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + } + + if lhs > rhs { + mem::swap(&mut lhs, &mut rhs); + } + + let middle = Locator::between(&lhs, &rhs); + assert!(middle > lhs); + assert!(middle < rhs); + for ix in 0..middle.0.len() - 1 { + assert!( + middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0) + || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0) + ); + } + } +} diff --git a/crates/text2/src/network.rs b/crates/text2/src/network.rs new file mode 100644 index 0000000000..2f49756ca3 --- /dev/null +++ b/crates/text2/src/network.rs @@ -0,0 +1,69 @@ +use clock::ReplicaId; + +pub struct Network { + inboxes: std::collections::BTreeMap>>, + all_messages: Vec, + rng: R, +} + +#[derive(Clone)] +struct Envelope { + message: T, +} + +impl Network { + pub fn new(rng: R) -> Self { + Network { + inboxes: Default::default(), + all_messages: Vec::new(), + rng, + } + } + + pub fn add_peer(&mut self, id: ReplicaId) { + self.inboxes.insert(id, Vec::new()); + } + + pub fn replicate(&mut self, old_replica_id: ReplicaId, new_replica_id: ReplicaId) { + self.inboxes + .insert(new_replica_id, self.inboxes[&old_replica_id].clone()); + } + + pub fn is_idle(&self) -> bool { + self.inboxes.values().all(|i| i.is_empty()) + } + + pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec) { + for (replica, inbox) in self.inboxes.iter_mut() { + if *replica != sender { + for message in &messages { + // Insert one or more duplicates of this message, potentially *before* the previous + // message sent by this peer to simulate out-of-order delivery. + for _ in 0..self.rng.gen_range(1..4) { + let insertion_index = self.rng.gen_range(0..inbox.len() + 1); + inbox.insert( + insertion_index, + Envelope { + message: message.clone(), + }, + ); + } + } + } + } + self.all_messages.extend(messages); + } + + pub fn has_unreceived(&self, receiver: ReplicaId) -> bool { + !self.inboxes[&receiver].is_empty() + } + + pub fn receive(&mut self, receiver: ReplicaId) -> Vec { + let inbox = self.inboxes.get_mut(&receiver).unwrap(); + let count = self.rng.gen_range(0..inbox.len() + 1); + inbox + .drain(0..count) + .map(|envelope| envelope.message) + .collect() + } +} diff --git a/crates/text2/src/operation_queue.rs b/crates/text2/src/operation_queue.rs new file mode 100644 index 0000000000..063f050665 --- /dev/null +++ b/crates/text2/src/operation_queue.rs @@ -0,0 +1,153 @@ +use std::{fmt::Debug, ops::Add}; +use sum_tree::{Dimension, Edit, Item, KeyedItem, SumTree, Summary}; + +pub trait Operation: Clone + Debug { + fn lamport_timestamp(&self) -> clock::Lamport; +} + +#[derive(Clone, Debug)] +struct OperationItem(T); + +#[derive(Clone, Debug)] +pub struct OperationQueue(SumTree>); + +#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct OperationKey(clock::Lamport); + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct OperationSummary { + pub key: OperationKey, + pub len: usize, +} + +impl OperationKey { + pub fn new(timestamp: clock::Lamport) -> Self { + Self(timestamp) + } +} + +impl Default for OperationQueue { + fn default() -> Self { + OperationQueue::new() + } +} + +impl OperationQueue { + pub fn new() -> Self { + OperationQueue(SumTree::new()) + } + + pub fn len(&self) -> usize { + self.0.summary().len + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn insert(&mut self, mut ops: Vec) { + ops.sort_by_key(|op| op.lamport_timestamp()); + ops.dedup_by_key(|op| op.lamport_timestamp()); + self.0.edit( + ops.into_iter() + .map(|op| Edit::Insert(OperationItem(op))) + .collect(), + &(), + ); + } + + pub fn drain(&mut self) -> Self { + let clone = self.clone(); + self.0 = SumTree::new(); + clone + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|i| &i.0) + } +} + +impl Summary for OperationSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + assert!(self.key < other.key); + self.key = other.key; + self.len += other.len; + } +} + +impl<'a> Add<&'a Self> for OperationSummary { + type Output = Self; + + fn add(self, other: &Self) -> Self { + assert!(self.key < other.key); + OperationSummary { + key: other.key, + len: self.len + other.len, + } + } +} + +impl<'a> Dimension<'a, OperationSummary> for OperationKey { + fn add_summary(&mut self, summary: &OperationSummary, _: &()) { + assert!(*self <= summary.key); + *self = summary.key; + } +} + +impl Item for OperationItem { + type Summary = OperationSummary; + + fn summary(&self) -> Self::Summary { + OperationSummary { + key: OperationKey::new(self.0.lamport_timestamp()), + len: 1, + } + } +} + +impl KeyedItem for OperationItem { + type Key = OperationKey; + + fn key(&self) -> Self::Key { + OperationKey::new(self.0.lamport_timestamp()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + let mut clock = clock::Lamport::new(0); + + let mut queue = OperationQueue::new(); + assert_eq!(queue.len(), 0); + + queue.insert(vec![ + TestOperation(clock.tick()), + TestOperation(clock.tick()), + ]); + assert_eq!(queue.len(), 2); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 3); + + drop(queue.drain()); + assert_eq!(queue.len(), 0); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 1); + } + + #[derive(Clone, Debug, Eq, PartialEq)] + struct TestOperation(clock::Lamport); + + impl Operation for TestOperation { + fn lamport_timestamp(&self) -> clock::Lamport { + self.0 + } + } +} diff --git a/crates/text2/src/patch.rs b/crates/text2/src/patch.rs new file mode 100644 index 0000000000..20e4a4d889 --- /dev/null +++ b/crates/text2/src/patch.rs @@ -0,0 +1,594 @@ +use crate::Edit; +use std::{ + cmp, mem, + ops::{Add, AddAssign, Sub}, +}; + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Patch(Vec>); + +impl Patch +where + T: 'static + + Clone + + Copy + + Ord + + Sub + + Add + + AddAssign + + Default + + PartialEq, +{ + pub fn new(edits: Vec>) -> Self { + #[cfg(debug_assertions)] + { + let mut last_edit: Option<&Edit> = None; + for edit in &edits { + if let Some(last_edit) = last_edit { + assert!(edit.old.start > last_edit.old.end); + assert!(edit.new.start > last_edit.new.end); + } + last_edit = Some(edit); + } + } + Self(edits) + } + + pub fn edits(&self) -> &[Edit] { + &self.0 + } + + pub fn into_inner(self) -> Vec> { + self.0 + } + + pub fn compose(&self, new_edits_iter: impl IntoIterator>) -> Self { + let mut old_edits_iter = self.0.iter().cloned().peekable(); + let mut new_edits_iter = new_edits_iter.into_iter().peekable(); + let mut composed = Patch(Vec::new()); + + let mut old_start = T::default(); + let mut new_start = T::default(); + loop { + let old_edit = old_edits_iter.peek_mut(); + let new_edit = new_edits_iter.peek_mut(); + + // Push the old edit if its new end is before the new edit's old start. + if let Some(old_edit) = old_edit.as_ref() { + let new_edit = new_edit.as_ref(); + if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + old_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + continue; + } + } + + // Push the new edit if its old end is before the old edit's new start. + if let Some(new_edit) = new_edit.as_ref() { + let old_edit = old_edit.as_ref(); + if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + new_edit.old_len(); + let new_end = new_start + new_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + continue; + } + } + + // If we still have edits by this point then they must intersect, so we compose them. + if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) { + if old_edit.new.start < new_edit.old.start { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let overshoot = new_edit.old.start - old_edit.new.start; + let old_end = cmp::min(old_start + overshoot, old_edit.old.end); + let new_end = new_start + overshoot; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; + } else { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let overshoot = old_edit.new.start - new_edit.old.start; + let old_end = old_start + overshoot; + let new_end = cmp::min(new_start + overshoot, new_edit.new.end); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start += overshoot; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + } + + if old_edit.new.end > new_edit.old.end { + let old_end = old_start + cmp::min(old_edit.old_len(), new_edit.old_len()); + let new_end = new_start + new_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start = new_edit.old.end; + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + } else { + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + cmp::min(old_edit.new_len(), new_edit.new_len()); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start = old_edit.new.end; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + } + } else { + break; + } + } + + composed + } + + pub fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn push(&mut self, edit: Edit) { + if edit.is_empty() { + return; + } + + if let Some(last) = self.0.last_mut() { + if last.old.end >= edit.old.start { + last.old.end = edit.old.end; + last.new.end = edit.new.end; + } else { + self.0.push(edit); + } + } else { + self.0.push(edit); + } + } + + pub fn old_to_new(&self, old: T) -> T { + let ix = match self.0.binary_search_by(|probe| probe.old.start.cmp(&old)) { + Ok(ix) => ix, + Err(ix) => { + if ix == 0 { + return old; + } else { + ix - 1 + } + } + }; + if let Some(edit) = self.0.get(ix) { + if old >= edit.old.end { + edit.new.end + (old - edit.old.end) + } else { + edit.new.start + } + } else { + old + } + } +} + +impl IntoIterator for Patch { + type Item = Edit; + type IntoIter = std::vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T: Clone> IntoIterator for &'a Patch { + type Item = Edit; + type IntoIter = std::iter::Cloned>>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().cloned() + } +} + +impl<'a, T: Clone> IntoIterator for &'a mut Patch { + type Item = Edit; + type IntoIter = std::iter::Cloned>>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().cloned() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::env; + + #[gpui2::test] + fn test_one_disjoint_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 0..0, + new: 0..4, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..3, + new: 5..8, + }, + ]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 5..9, + new: 5..7, + }]), + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 4..8, + new: 5..7, + }, + ]), + ); + } + + #[gpui2::test] + fn test_one_overlapping_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 3..5, + new: 3..6, + }]), + Patch(vec![Edit { + old: 1..4, + new: 1..6, + }]), + ); + } + + #[gpui2::test] + fn test_two_disjoint_and_overlapping() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 8..12, + new: 9..11, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 3..10, + new: 7..9, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..12, + new: 5..10, + }, + ]), + ); + } + + #[gpui2::test] + fn test_two_new_edits_overlapping_one_old_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..1, + }, + Edit { + old: 1..2, + new: 2..2, + }, + ]), + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 2..3, + new: 2..4, + }]), + Patch(vec![ + Edit { + old: 0..2, + new: 0..1, + }, + Edit { + old: 3..3, + new: 2..5, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..6, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..2, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..2, + }, + Edit { + old: 2..5, + new: 4..4, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..4, + }]), + ); + } + + #[gpui2::test] + fn test_two_new_edits_touching_one_old_edit() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..2, + }, + Edit { + old: 4..4, + new: 3..4, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + ); + } + + #[gpui2::test] + fn test_old_to_new() { + let patch = Patch(vec![ + Edit { + old: 2..4, + new: 2..4, + }, + Edit { + old: 7..8, + new: 7..11, + }, + ]); + assert_eq!(patch.old_to_new(0), 0); + assert_eq!(patch.old_to_new(1), 1); + assert_eq!(patch.old_to_new(2), 2); + assert_eq!(patch.old_to_new(3), 2); + assert_eq!(patch.old_to_new(4), 4); + assert_eq!(patch.old_to_new(5), 5); + assert_eq!(patch.old_to_new(6), 6); + assert_eq!(patch.old_to_new(7), 7); + assert_eq!(patch.old_to_new(8), 11); + assert_eq!(patch.old_to_new(9), 12); + } + + #[gpui2::test(iterations = 100)] + fn test_random_patch_compositions(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(20); + + let initial_chars = (0..rng.gen_range(0..=100)) + .map(|_| rng.gen_range(b'a'..=b'z') as char) + .collect::>(); + log::info!("initial chars: {:?}", initial_chars); + + // Generate two sequential patches + let mut patches = Vec::new(); + let mut expected_chars = initial_chars.clone(); + for i in 0..2 { + log::info!("patch {}:", i); + + let mut delta = 0i32; + let mut last_edit_end = 0; + let mut edits = Vec::new(); + + for _ in 0..operations { + if last_edit_end >= expected_chars.len() { + break; + } + + let end = rng.gen_range(last_edit_end..=expected_chars.len()); + let start = rng.gen_range(last_edit_end..=end); + let old_len = end - start; + + let mut new_len = rng.gen_range(0..=3); + if start == end && new_len == 0 { + new_len += 1; + } + + last_edit_end = start + new_len + 1; + + let new_chars = (0..new_len) + .map(|_| rng.gen_range(b'A'..=b'Z') as char) + .collect::>(); + log::info!( + " editing {:?}: {:?}", + start..end, + new_chars.iter().collect::() + ); + edits.push(Edit { + old: (start as i32 - delta) as u32..(end as i32 - delta) as u32, + new: start as u32..(start + new_len) as u32, + }); + expected_chars.splice(start..end, new_chars); + + delta += new_len as i32 - old_len as i32; + } + + patches.push(Patch(edits)); + } + + log::info!("old patch: {:?}", &patches[0]); + log::info!("new patch: {:?}", &patches[1]); + log::info!("initial chars: {:?}", initial_chars); + log::info!("final chars: {:?}", expected_chars); + + // Compose the patches, and verify that it has the same effect as applying the + // two patches separately. + let composed = patches[0].compose(&patches[1]); + log::info!("composed patch: {:?}", &composed); + + let mut actual_chars = initial_chars; + for edit in composed.0 { + actual_chars.splice( + edit.new.start as usize..edit.new.start as usize + edit.old.len(), + expected_chars[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + + assert_eq!(actual_chars, expected_chars); + } + + #[track_caller] + fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) { + let original = ('a'..'z').collect::>(); + let inserted = ('A'..'Z').collect::>(); + + let mut expected = original.clone(); + apply_patch(&mut expected, &old, &inserted); + apply_patch(&mut expected, &new, &inserted); + + let mut actual = original; + apply_patch(&mut actual, &composed, &expected); + assert_eq!( + actual.into_iter().collect::(), + expected.into_iter().collect::(), + "expected patch is incorrect" + ); + + assert_eq!(old.compose(&new), composed); + } + + fn apply_patch(text: &mut Vec, patch: &Patch, new_text: &[char]) { + for edit in patch.0.iter().rev() { + text.splice( + edit.old.start as usize..edit.old.end as usize, + new_text[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + } +} diff --git a/crates/text2/src/selection.rs b/crates/text2/src/selection.rs new file mode 100644 index 0000000000..480cb99d74 --- /dev/null +++ b/crates/text2/src/selection.rs @@ -0,0 +1,123 @@ +use crate::{Anchor, BufferSnapshot, TextDimension}; +use std::cmp::Ordering; +use std::ops::Range; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SelectionGoal { + None, + HorizontalPosition(f32), + HorizontalRange { start: f32, end: f32 }, + WrappedHorizontalPosition((u32, f32)), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Selection { + pub id: usize, + pub start: T, + pub end: T, + pub reversed: bool, + pub goal: SelectionGoal, +} + +impl Default for SelectionGoal { + fn default() -> Self { + Self::None + } +} + +impl Selection { + pub fn head(&self) -> T { + if self.reversed { + self.start.clone() + } else { + self.end.clone() + } + } + + pub fn tail(&self) -> T { + if self.reversed { + self.end.clone() + } else { + self.start.clone() + } + } + + pub fn map(&self, f: F) -> Selection + where + F: Fn(T) -> S, + { + Selection:: { + id: self.id, + start: f(self.start.clone()), + end: f(self.end.clone()), + reversed: self.reversed, + goal: self.goal, + } + } + + pub fn collapse_to(&mut self, point: T, new_goal: SelectionGoal) { + self.start = point.clone(); + self.end = point; + self.goal = new_goal; + self.reversed = false; + } +} + +impl Selection { + pub fn is_empty(&self) -> bool { + self.start == self.end + } + + pub fn set_head(&mut self, head: T, new_goal: SelectionGoal) { + if head.cmp(&self.tail()) < Ordering::Equal { + if !self.reversed { + self.end = self.start; + self.reversed = true; + } + self.start = head; + } else { + if self.reversed { + self.start = self.end; + self.reversed = false; + } + self.end = head; + } + self.goal = new_goal; + } + + pub fn range(&self) -> Range { + self.start..self.end + } +} + +impl Selection { + #[cfg(feature = "test-support")] + pub fn from_offset(offset: usize) -> Self { + Selection { + id: 0, + start: offset, + end: offset, + goal: SelectionGoal::None, + reversed: false, + } + } + + pub fn equals(&self, offset_range: &Range) -> bool { + self.start == offset_range.start && self.end == offset_range.end + } +} + +impl Selection { + pub fn resolve<'a, D: 'a + TextDimension>( + &'a self, + snapshot: &'a BufferSnapshot, + ) -> Selection { + Selection { + id: self.id, + start: snapshot.summary_for_anchor(&self.start), + end: snapshot.summary_for_anchor(&self.end), + reversed: self.reversed, + goal: self.goal, + } + } +} diff --git a/crates/text2/src/subscription.rs b/crates/text2/src/subscription.rs new file mode 100644 index 0000000000..b636dfcc92 --- /dev/null +++ b/crates/text2/src/subscription.rs @@ -0,0 +1,48 @@ +use crate::{Edit, Patch}; +use parking_lot::Mutex; +use std::{ + mem, + sync::{Arc, Weak}, +}; + +#[derive(Default)] +pub struct Topic(Mutex>>>>); + +pub struct Subscription(Arc>>); + +impl Topic { + pub fn subscribe(&mut self) -> Subscription { + let subscription = Subscription(Default::default()); + self.0.get_mut().push(Arc::downgrade(&subscription.0)); + subscription + } + + pub fn publish(&self, edits: impl Clone + IntoIterator>) { + publish(&mut *self.0.lock(), edits); + } + + pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator>) { + publish(self.0.get_mut(), edits); + } +} + +impl Subscription { + pub fn consume(&self) -> Patch { + mem::take(&mut *self.0.lock()) + } +} + +fn publish( + subscriptions: &mut Vec>>>, + edits: impl Clone + IntoIterator>, +) { + subscriptions.retain(|subscription| { + if let Some(subscription) = subscription.upgrade() { + let mut patch = subscription.lock(); + *patch = patch.compose(edits.clone()); + true + } else { + false + } + }); +} diff --git a/crates/text2/src/tests.rs b/crates/text2/src/tests.rs new file mode 100644 index 0000000000..96248285ea --- /dev/null +++ b/crates/text2/src/tests.rs @@ -0,0 +1,764 @@ +use super::{network::Network, *}; +use clock::ReplicaId; +use rand::prelude::*; +use std::{ + cmp::Ordering, + env, + iter::Iterator, + time::{Duration, Instant}, +}; + +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } +} + +#[test] +fn test_edit() { + let mut buffer = Buffer::new(0, 0, "abc".into()); + assert_eq!(buffer.text(), "abc"); + buffer.edit([(3..3, "def")]); + assert_eq!(buffer.text(), "abcdef"); + buffer.edit([(0..0, "ghi")]); + assert_eq!(buffer.text(), "ghiabcdef"); + buffer.edit([(5..5, "jkl")]); + assert_eq!(buffer.text(), "ghiabjklcdef"); + buffer.edit([(6..7, "")]); + assert_eq!(buffer.text(), "ghiabjlcdef"); + buffer.edit([(4..9, "mno")]); + assert_eq!(buffer.text(), "ghiamnoef"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_edits(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let reference_string_len = rng.gen_range(0..3); + let mut reference_string = RandomCharIter::new(&mut rng) + .take(reference_string_len) + .collect::(); + let mut buffer = Buffer::new(0, 0, reference_string.clone()); + LineEnding::normalize(&mut reference_string); + + buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); + let mut buffer_versions = Vec::new(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + for _i in 0..operations { + let (edits, _) = buffer.randomly_edit(&mut rng, 5); + for (old_range, new_text) in edits.iter().rev() { + reference_string.replace_range(old_range.clone(), new_text); + } + + assert_eq!(buffer.text(), reference_string); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + if rng.gen_bool(0.25) { + buffer.randomly_undo_redo(&mut rng); + reference_string = buffer.text(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + } + + let range = buffer.random_byte_range(0, &mut rng); + assert_eq!( + buffer.text_summary_for_range::(range.clone()), + TextSummary::from(&reference_string[range]) + ); + + buffer.check_invariants(); + + if rng.gen_bool(0.3) { + buffer_versions.push((buffer.clone(), buffer.subscribe())); + } + } + + for (old_buffer, subscription) in buffer_versions { + let edits = buffer + .edits_since::(&old_buffer.version) + .collect::>(); + + log::info!( + "applying edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in edits { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + + for _ in 0..5 { + let end_ix = old_buffer.clip_offset(rng.gen_range(0..=old_buffer.len()), Bias::Right); + let start_ix = old_buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let range = old_buffer.anchor_before(start_ix)..old_buffer.anchor_after(end_ix); + let mut old_text = old_buffer.text_for_range(range.clone()).collect::(); + let edits = buffer + .edits_since_in_range::(&old_buffer.version, range.clone()) + .collect::>(); + log::info!( + "applying edits since version {:?} to old text in range {:?}: {:?}: {:?}", + old_buffer.version(), + start_ix..end_ix, + old_text, + edits, + ); + + let new_text = buffer.text_for_range(range).collect::(); + for edit in edits { + old_text.replace_range( + edit.new.start..edit.new.start + edit.old_len(), + &new_text[edit.new], + ); + } + assert_eq!(old_text, new_text); + } + + let subscription_edits = subscription.consume(); + log::info!( + "applying subscription edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + subscription_edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in subscription_edits.into_inner() { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + } +} + +#[test] +fn test_line_endings() { + assert_eq!(LineEnding::detect(&"🍐✅\n".repeat(1000)), LineEnding::Unix); + assert_eq!(LineEnding::detect(&"abcd\n".repeat(1000)), LineEnding::Unix); + assert_eq!( + LineEnding::detect(&"🍐✅\r\n".repeat(1000)), + LineEnding::Windows + ); + assert_eq!( + LineEnding::detect(&"abcd\r\n".repeat(1000)), + LineEnding::Windows + ); + + let mut buffer = Buffer::new(0, 0, "one\r\ntwo\rthree".into()); + assert_eq!(buffer.text(), "one\ntwo\nthree"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); + + buffer.edit([(buffer.len()..buffer.len(), "\r\nfour")]); + buffer.edit([(0..0, "zero\r\n")]); + assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); +} + +#[test] +fn test_line_len() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abcd\nefg\nhij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs\n")]); + buffer.edit([(18..21, "\nPQ")]); + + assert_eq!(buffer.line_len(0), 4); + assert_eq!(buffer.line_len(1), 3); + assert_eq!(buffer.line_len(2), 5); + assert_eq!(buffer.line_len(3), 3); + assert_eq!(buffer.line_len(4), 4); + assert_eq!(buffer.line_len(5), 0); +} + +#[test] +fn test_common_prefix_at_position() { + let text = "a = str; b = δα"; + let buffer = Buffer::new(0, 0, text.into()); + + let offset1 = offset_after(text, "str"); + let offset2 = offset_after(text, "δα"); + + // the preceding word is a prefix of the suggestion + assert_eq!( + buffer.common_prefix_at(offset1, "string"), + range_of(text, "str"), + ); + // a suffix of the preceding word is a prefix of the suggestion + assert_eq!( + buffer.common_prefix_at(offset1, "tree"), + range_of(text, "tr"), + ); + // the preceding word is a substring of the suggestion, but not a prefix + assert_eq!( + buffer.common_prefix_at(offset1, "astro"), + empty_range_after(text, "str"), + ); + + // prefix matching is case insensitive. + assert_eq!( + buffer.common_prefix_at(offset1, "Strαngε"), + range_of(text, "str"), + ); + assert_eq!( + buffer.common_prefix_at(offset2, "ΔΑΜΝ"), + range_of(text, "δα"), + ); + + fn offset_after(text: &str, part: &str) -> usize { + text.find(part).unwrap() + part.len() + } + + fn empty_range_after(text: &str, part: &str) -> Range { + let offset = offset_after(text, part); + offset..offset + } + + fn range_of(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + part.len() + } +} + +#[test] +fn test_text_summary_for_range() { + let buffer = Buffer::new(0, 0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()); + assert_eq!( + buffer.text_summary_for_range::(1..3), + TextSummary { + len: 2, + len_utf16: OffsetUtf16(2), + lines: Point::new(1, 0), + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + } + ); + assert_eq!( + buffer.text_summary_for_range::(1..12), + TextSummary { + len: 11, + len_utf16: OffsetUtf16(11), + lines: Point::new(3, 0), + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 2, + longest_row_chars: 4, + } + ); + assert_eq!( + buffer.text_summary_for_range::(0..20), + TextSummary { + len: 20, + len_utf16: OffsetUtf16(20), + lines: Point::new(4, 1), + first_line_chars: 2, + last_line_chars: 1, + last_line_len_utf16: 1, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range::(0..22), + TextSummary { + len: 22, + len_utf16: OffsetUtf16(22), + lines: Point::new(4, 3), + first_line_chars: 2, + last_line_chars: 3, + last_line_len_utf16: 3, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range::(7..22), + TextSummary { + len: 15, + len_utf16: OffsetUtf16(15), + lines: Point::new(2, 3), + first_line_chars: 4, + last_line_chars: 3, + last_line_len_utf16: 3, + longest_row: 1, + longest_row_chars: 6, + } + ); +} + +#[test] +fn test_chars_at() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abcd\nefgh\nij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs")]); + buffer.edit([(18..21, "\nPQ")]); + + let chars = buffer.chars_at(Point::new(0, 0)); + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(1, 0)); + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(2, 0)); + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(3, 0)); + assert_eq!(chars.collect::(), "mno\nPQrs"); + + let chars = buffer.chars_at(Point::new(4, 0)); + assert_eq!(chars.collect::(), "PQrs"); + + // Regression test: + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n")]); + buffer.edit([(60..60, "\n")]); + + let chars = buffer.chars_at(Point::new(6, 0)); + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); +} + +#[test] +fn test_anchors() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abc")]); + let left_anchor = buffer.anchor_before(2); + let right_anchor = buffer.anchor_after(2); + + buffer.edit([(1..1, "def\n")]); + assert_eq!(buffer.text(), "adef\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 6); + assert_eq!(right_anchor.to_offset(&buffer), 6); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit([(2..3, "")]); + assert_eq!(buffer.text(), "adf\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 5); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit([(5..5, "ghi\n")]); + assert_eq!(buffer.text(), "adf\nbghi\nc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 9); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); + + buffer.edit([(7..9, "")]); + assert_eq!(buffer.text(), "adf\nbghc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 7); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); + + // Ensure anchoring to a point is equivalent to anchoring to an offset. + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 0 }), + buffer.anchor_before(0) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 1 }), + buffer.anchor_before(1) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 2 }), + buffer.anchor_before(2) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 3 }), + buffer.anchor_before(3) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 0 }), + buffer.anchor_before(4) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 1 }), + buffer.anchor_before(5) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 2 }), + buffer.anchor_before(6) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 3 }), + buffer.anchor_before(7) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 4 }), + buffer.anchor_before(8) + ); + + // Comparison between anchors. + let anchor_at_offset_0 = buffer.anchor_before(0); + let anchor_at_offset_1 = buffer.anchor_before(1); + let anchor_at_offset_2 = buffer.anchor_before(2); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer), + Ordering::Equal + ); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer), + Ordering::Less + ); + + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer), + Ordering::Greater + ); +} + +#[test] +fn test_anchors_at_start_and_end() { + let mut buffer = Buffer::new(0, 0, "".into()); + let before_start_anchor = buffer.anchor_before(0); + let after_end_anchor = buffer.anchor_after(0); + + buffer.edit([(0..0, "abc")]); + assert_eq!(buffer.text(), "abc"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_end_anchor.to_offset(&buffer), 3); + + let after_start_anchor = buffer.anchor_after(0); + let before_end_anchor = buffer.anchor_before(3); + + buffer.edit([(3..3, "def")]); + buffer.edit([(0..0, "ghi")]); + assert_eq!(buffer.text(), "ghiabcdef"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_start_anchor.to_offset(&buffer), 3); + assert_eq!(before_end_anchor.to_offset(&buffer), 6); + assert_eq!(after_end_anchor.to_offset(&buffer), 9); +} + +#[test] +fn test_undo_redo() { + let mut buffer = Buffer::new(0, 0, "1234".into()); + // Set group interval to zero so as to not group edits in the undo stack. + buffer.set_group_interval(Duration::from_secs(0)); + + buffer.edit([(1..1, "abx")]); + buffer.edit([(3..4, "yzef")]); + buffer.edit([(3..5, "cd")]); + assert_eq!(buffer.text(), "1abcdef234"); + + let entries = buffer.history.undo_stack.clone(); + assert_eq!(entries.len(), 3); + + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1cdef234"); + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdx234"); + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abx234"); + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1yzef234"); + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1234"); +} + +#[test] +fn test_history() { + let mut now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "123456".into()); + buffer.set_group_interval(Duration::from_millis(300)); + + let transaction_1 = buffer.start_transaction_at(now).unwrap(); + buffer.edit([(2..4, "cd")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56"); + + buffer.start_transaction_at(now); + buffer.edit([(4..5, "e")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + + now += buffer.transaction_group_interval() + Duration::from_millis(1); + buffer.start_transaction_at(now); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + + // Last transaction happened past the group interval, undo it on its own. + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + + // First two transactions happened within the group interval, undo them together. + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + // Redo the first two transactions together. + buffer.redo(); + assert_eq!(buffer.text(), "12cde6"); + + // Redo the last transaction on its own. + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); + + buffer.start_transaction_at(now); + assert!(buffer.end_transaction_at(now).is_none()); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + + // Redo stack gets cleared after performing an edit. + buffer.start_transaction_at(now); + buffer.edit([(0..0, "X")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "X12cde6"); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + // Transactions can be grouped manually. + buffer.redo(); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); + buffer.group_until_transaction(transaction_1); + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); +} + +#[test] +fn test_finalize_last_transaction() { + let now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "123456".into()); + + buffer.start_transaction_at(now); + buffer.edit([(2..4, "cd")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56"); + + buffer.finalize_last_transaction(); + buffer.start_transaction_at(now); + buffer.edit([(4..5, "e")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + + buffer.start_transaction_at(now); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + + buffer.undo(); + assert_eq!(buffer.text(), "12cd56"); + + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + buffer.redo(); + assert_eq!(buffer.text(), "12cd56"); + + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); +} + +#[test] +fn test_edited_ranges_for_transaction() { + let now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "1234567".into()); + + buffer.start_transaction_at(now); + buffer.edit([(2..4, "cd")]); + buffer.edit([(6..6, "efg")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56efg7"); + + let tx = buffer.finalize_last_transaction().unwrap().clone(); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 6..9] + ); + + buffer.edit([(5..5, "hijk")]); + assert_eq!(buffer.text(), "12cd5hijk6efg7"); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 10..13] + ); + + buffer.edit([(4..4, "l")]); + assert_eq!(buffer.text(), "12cdl5hijk6efg7"); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 11..14] + ); +} + +#[test] +fn test_concurrent_edits() { + let text = "abcdef"; + + let mut buffer1 = Buffer::new(1, 0, text.into()); + let mut buffer2 = Buffer::new(2, 0, text.into()); + let mut buffer3 = Buffer::new(3, 0, text.into()); + + let buf1_op = buffer1.edit([(1..2, "12")]); + assert_eq!(buffer1.text(), "a12cdef"); + let buf2_op = buffer2.edit([(3..4, "34")]); + assert_eq!(buffer2.text(), "abc34ef"); + let buf3_op = buffer3.edit([(5..6, "56")]); + assert_eq!(buffer3.text(), "abcde56"); + + buffer1.apply_op(buf2_op.clone()).unwrap(); + buffer1.apply_op(buf3_op.clone()).unwrap(); + buffer2.apply_op(buf1_op.clone()).unwrap(); + buffer2.apply_op(buf3_op).unwrap(); + buffer3.apply_op(buf1_op).unwrap(); + buffer3.apply_op(buf2_op).unwrap(); + + assert_eq!(buffer1.text(), "a12c34e56"); + assert_eq!(buffer2.text(), "a12c34e56"); + assert_eq!(buffer3.text(), "a12c34e56"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_concurrent_edits(mut rng: StdRng) { + let peers = env::var("PEERS") + .map(|i| i.parse().expect("invalid `PEERS` variable")) + .unwrap_or(5); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let base_text_len = rng.gen_range(0..10); + let base_text = RandomCharIter::new(&mut rng) + .take(base_text_len) + .collect::(); + let mut replica_ids = Vec::new(); + let mut buffers = Vec::new(); + let mut network = Network::new(rng.clone()); + + for i in 0..peers { + let mut buffer = Buffer::new(i as ReplicaId, 0, base_text.clone()); + buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + buffers.push(buffer); + replica_ids.push(i as u16); + network.add_peer(i as u16); + } + + log::info!("initial text: {:?}", base_text); + + let mut mutation_count = operations; + loop { + let replica_index = rng.gen_range(0..peers); + let replica_id = replica_ids[replica_index]; + let buffer = &mut buffers[replica_index]; + match rng.gen_range(0..=100) { + 0..=50 if mutation_count != 0 => { + let op = buffer.randomly_edit(&mut rng, 5).1; + network.broadcast(buffer.replica_id, vec![op]); + log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); + mutation_count -= 1; + } + 51..=70 if mutation_count != 0 => { + let ops = buffer.randomly_undo_redo(&mut rng); + network.broadcast(buffer.replica_id, ops); + mutation_count -= 1; + } + 71..=100 if network.has_unreceived(replica_id) => { + let ops = network.receive(replica_id); + if !ops.is_empty() { + log::info!( + "peer {} applying {} ops from the network.", + replica_id, + ops.len() + ); + buffer.apply_ops(ops).unwrap(); + } + } + _ => {} + } + buffer.check_invariants(); + + if mutation_count == 0 && network.is_idle() { + break; + } + } + + let first_buffer = &buffers[0]; + for buffer in &buffers[1..] { + assert_eq!( + buffer.text(), + first_buffer.text(), + "Replica {} text != Replica 0 text", + buffer.replica_id + ); + buffer.check_invariants(); + } +} diff --git a/crates/text2/src/text2.rs b/crates/text2/src/text2.rs new file mode 100644 index 0000000000..c05ea1109c --- /dev/null +++ b/crates/text2/src/text2.rs @@ -0,0 +1,2682 @@ +mod anchor; +pub mod locator; +#[cfg(any(test, feature = "test-support"))] +pub mod network; +pub mod operation_queue; +mod patch; +mod selection; +pub mod subscription; +#[cfg(test)] +mod tests; +mod undo_map; + +pub use anchor::*; +use anyhow::{anyhow, Result}; +pub use clock::ReplicaId; +use collections::{HashMap, HashSet}; +use locator::Locator; +use operation_queue::OperationQueue; +pub use patch::Patch; +use postage::{oneshot, prelude::*}; + +use lazy_static::lazy_static; +use regex::Regex; +pub use rope::*; +pub use selection::*; +use std::{ + borrow::Cow, + cmp::{self, Ordering, Reverse}, + future::Future, + iter::Iterator, + ops::{self, Deref, Range, Sub}, + str, + sync::Arc, + time::{Duration, Instant}, +}; +pub use subscription::*; +pub use sum_tree::Bias; +use sum_tree::{FilterCursor, SumTree, TreeMap}; +use undo_map::UndoMap; +use util::ResultExt; + +#[cfg(any(test, feature = "test-support"))] +use util::RandomCharIter; + +lazy_static! { + static ref LINE_SEPARATORS_REGEX: Regex = Regex::new("\r\n|\r|\u{2028}|\u{2029}").unwrap(); +} + +pub type TransactionId = clock::Lamport; + +pub struct Buffer { + snapshot: BufferSnapshot, + history: History, + deferred_ops: OperationQueue, + deferred_replicas: HashSet, + pub lamport_clock: clock::Lamport, + subscriptions: Topic, + edit_id_resolvers: HashMap>>, + wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>, +} + +#[derive(Clone)] +pub struct BufferSnapshot { + replica_id: ReplicaId, + remote_id: u64, + visible_text: Rope, + deleted_text: Rope, + line_ending: LineEnding, + undo_map: UndoMap, + fragments: SumTree, + insertions: SumTree, + pub version: clock::Global, +} + +#[derive(Clone, Debug)] +pub struct HistoryEntry { + transaction: Transaction, + first_edit_at: Instant, + last_edit_at: Instant, + suppress_grouping: bool, +} + +#[derive(Clone, Debug)] +pub struct Transaction { + pub id: TransactionId, + pub edit_ids: Vec, + pub start: clock::Global, +} + +impl HistoryEntry { + pub fn transaction_id(&self) -> TransactionId { + self.transaction.id + } +} + +struct History { + base_text: Rope, + operations: TreeMap, + insertion_slices: HashMap>, + undo_stack: Vec, + redo_stack: Vec, + transaction_depth: usize, + group_interval: Duration, +} + +#[derive(Clone, Debug)] +struct InsertionSlice { + insertion_id: clock::Lamport, + range: Range, +} + +impl History { + pub fn new(base_text: Rope) -> Self { + Self { + base_text, + operations: Default::default(), + insertion_slices: Default::default(), + undo_stack: Vec::new(), + redo_stack: Vec::new(), + transaction_depth: 0, + // Don't group transactions in tests unless we opt in, because it's a footgun. + #[cfg(any(test, feature = "test-support"))] + group_interval: Duration::ZERO, + #[cfg(not(any(test, feature = "test-support")))] + group_interval: Duration::from_millis(300), + } + } + + fn push(&mut self, op: Operation) { + self.operations.insert(op.timestamp(), op); + } + + fn start_transaction( + &mut self, + start: clock::Global, + now: Instant, + clock: &mut clock::Lamport, + ) -> Option { + self.transaction_depth += 1; + if self.transaction_depth == 1 { + let id = clock.tick(); + self.undo_stack.push(HistoryEntry { + transaction: Transaction { + id, + start, + edit_ids: Default::default(), + }, + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + Some(id) + } else { + None + } + } + + fn end_transaction(&mut self, now: Instant) -> Option<&HistoryEntry> { + assert_ne!(self.transaction_depth, 0); + self.transaction_depth -= 1; + if self.transaction_depth == 0 { + if self + .undo_stack + .last() + .unwrap() + .transaction + .edit_ids + .is_empty() + { + self.undo_stack.pop(); + None + } else { + self.redo_stack.clear(); + let entry = self.undo_stack.last_mut().unwrap(); + entry.last_edit_at = now; + Some(entry) + } + } else { + None + } + } + + fn group(&mut self) -> Option { + let mut count = 0; + let mut entries = self.undo_stack.iter(); + if let Some(mut entry) = entries.next_back() { + while let Some(prev_entry) = entries.next_back() { + if !prev_entry.suppress_grouping + && entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval + { + entry = prev_entry; + count += 1; + } else { + break; + } + } + } + self.group_trailing(count) + } + + fn group_until(&mut self, transaction_id: TransactionId) { + let mut count = 0; + for entry in self.undo_stack.iter().rev() { + if entry.transaction_id() == transaction_id { + self.group_trailing(count); + break; + } else if entry.suppress_grouping { + break; + } else { + count += 1; + } + } + } + + fn group_trailing(&mut self, n: usize) -> Option { + let new_len = self.undo_stack.len() - n; + let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len); + if let Some(last_entry) = entries_to_keep.last_mut() { + for entry in &*entries_to_merge { + for edit_id in &entry.transaction.edit_ids { + last_entry.transaction.edit_ids.push(*edit_id); + } + } + + if let Some(entry) = entries_to_merge.last_mut() { + last_entry.last_edit_at = entry.last_edit_at; + } + } + + self.undo_stack.truncate(new_len); + self.undo_stack.last().map(|e| e.transaction.id) + } + + fn finalize_last_transaction(&mut self) -> Option<&Transaction> { + self.undo_stack.last_mut().map(|entry| { + entry.suppress_grouping = true; + &entry.transaction + }) + } + + fn push_transaction(&mut self, transaction: Transaction, now: Instant) { + assert_eq!(self.transaction_depth, 0); + self.undo_stack.push(HistoryEntry { + transaction, + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + self.redo_stack.clear(); + } + + fn push_undo(&mut self, op_id: clock::Lamport) { + assert_ne!(self.transaction_depth, 0); + if let Some(Operation::Edit(_)) = self.operations.get(&op_id) { + let last_transaction = self.undo_stack.last_mut().unwrap(); + last_transaction.transaction.edit_ids.push(op_id); + } + } + + fn pop_undo(&mut self) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + if let Some(entry) = self.undo_stack.pop() { + self.redo_stack.push(entry); + self.redo_stack.last() + } else { + None + } + } + + fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + + let entry_ix = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id)?; + let entry = self.undo_stack.remove(entry_ix); + self.redo_stack.push(entry); + self.redo_stack.last() + } + + fn remove_from_undo_until(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + assert_eq!(self.transaction_depth, 0); + + let redo_stack_start_len = self.redo_stack.len(); + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + self.redo_stack + .extend(self.undo_stack.drain(entry_ix..).rev()); + } + &self.redo_stack[redo_stack_start_len..] + } + + fn forget(&mut self, transaction_id: TransactionId) -> Option { + assert_eq!(self.transaction_depth, 0); + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + Some(self.undo_stack.remove(entry_ix).transaction) + } else if let Some(entry_ix) = self + .redo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + Some(self.redo_stack.remove(entry_ix).transaction) + } else { + None + } + } + + fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> { + let entry = self + .undo_stack + .iter_mut() + .rfind(|entry| entry.transaction.id == transaction_id) + .or_else(|| { + self.redo_stack + .iter_mut() + .rfind(|entry| entry.transaction.id == transaction_id) + })?; + Some(&mut entry.transaction) + } + + fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { + if let Some(transaction) = self.forget(transaction) { + if let Some(destination) = self.transaction_mut(destination) { + destination.edit_ids.extend(transaction.edit_ids); + } + } + } + + fn pop_redo(&mut self) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + if let Some(entry) = self.redo_stack.pop() { + self.undo_stack.push(entry); + self.undo_stack.last() + } else { + None + } + } + + fn remove_from_redo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + assert_eq!(self.transaction_depth, 0); + + let undo_stack_start_len = self.undo_stack.len(); + if let Some(entry_ix) = self + .redo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + self.undo_stack + .extend(self.redo_stack.drain(entry_ix..).rev()); + } + &self.undo_stack[undo_stack_start_len..] + } +} + +struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> { + visible_cursor: rope::Cursor<'a>, + deleted_cursor: rope::Cursor<'a>, + fragments_cursor: Option>, + undos: &'a UndoMap, + since: &'a clock::Global, + old_end: D, + new_end: D, + range: Range<(&'a Locator, usize)>, + buffer_id: u64, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Edit { + pub old: Range, + pub new: Range, +} + +impl Edit +where + D: Sub + PartialEq + Copy, +{ + pub fn old_len(&self) -> D { + self.old.end - self.old.start + } + + pub fn new_len(&self) -> D { + self.new.end - self.new.start + } + + pub fn is_empty(&self) -> bool { + self.old.start == self.old.end && self.new.start == self.new.end + } +} + +impl Edit<(D1, D2)> { + pub fn flatten(self) -> (Edit, Edit) { + ( + Edit { + old: self.old.start.0..self.old.end.0, + new: self.new.start.0..self.new.end.0, + }, + Edit { + old: self.old.start.1..self.old.end.1, + new: self.new.start.1..self.new.end.1, + }, + ) + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct Fragment { + pub id: Locator, + pub timestamp: clock::Lamport, + pub insertion_offset: usize, + pub len: usize, + pub visible: bool, + pub deletions: HashSet, + pub max_undos: clock::Global, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct FragmentSummary { + text: FragmentTextSummary, + max_id: Locator, + max_version: clock::Global, + min_insertion_version: clock::Global, + max_insertion_version: clock::Global, +} + +#[derive(Copy, Default, Clone, Debug, PartialEq, Eq)] +struct FragmentTextSummary { + visible: usize, + deleted: usize, +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary { + fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { + self.visible += summary.text.visible; + self.deleted += summary.text.deleted; + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct InsertionFragment { + timestamp: clock::Lamport, + split_offset: usize, + fragment_id: Locator, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct InsertionFragmentKey { + timestamp: clock::Lamport, + split_offset: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Operation { + Edit(EditOperation), + Undo(UndoOperation), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EditOperation { + pub timestamp: clock::Lamport, + pub version: clock::Global, + pub ranges: Vec>, + pub new_text: Vec>, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UndoOperation { + pub timestamp: clock::Lamport, + pub version: clock::Global, + pub counts: HashMap, +} + +impl Buffer { + pub fn new(replica_id: u16, remote_id: u64, mut base_text: String) -> Buffer { + let line_ending = LineEnding::detect(&base_text); + LineEnding::normalize(&mut base_text); + + let history = History::new(Rope::from(base_text.as_ref())); + let mut fragments = SumTree::new(); + let mut insertions = SumTree::new(); + + let mut lamport_clock = clock::Lamport::new(replica_id); + let mut version = clock::Global::new(); + + let visible_text = history.base_text.clone(); + if !visible_text.is_empty() { + let insertion_timestamp = clock::Lamport { + replica_id: 0, + value: 1, + }; + lamport_clock.observe(insertion_timestamp); + version.observe(insertion_timestamp); + let fragment_id = Locator::between(&Locator::min(), &Locator::max()); + let fragment = Fragment { + id: fragment_id, + timestamp: insertion_timestamp, + insertion_offset: 0, + len: visible_text.len(), + visible: true, + deletions: Default::default(), + max_undos: Default::default(), + }; + insertions.push(InsertionFragment::new(&fragment), &()); + fragments.push(fragment, &None); + } + + Buffer { + snapshot: BufferSnapshot { + replica_id, + remote_id, + visible_text, + deleted_text: Rope::new(), + line_ending, + fragments, + insertions, + version, + undo_map: Default::default(), + }, + history, + deferred_ops: OperationQueue::new(), + deferred_replicas: HashSet::default(), + lamport_clock, + subscriptions: Default::default(), + edit_id_resolvers: Default::default(), + wait_for_version_txs: Default::default(), + } + } + + pub fn version(&self) -> clock::Global { + self.version.clone() + } + + pub fn snapshot(&self) -> BufferSnapshot { + self.snapshot.clone() + } + + pub fn replica_id(&self) -> ReplicaId { + self.lamport_clock.replica_id + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn deferred_ops_len(&self) -> usize { + self.deferred_ops.len() + } + + pub fn transaction_group_interval(&self) -> Duration { + self.history.group_interval + } + + pub fn edit(&mut self, edits: R) -> Operation + where + R: IntoIterator, + I: ExactSizeIterator, T)>, + S: ToOffset, + T: Into>, + { + let edits = edits + .into_iter() + .map(|(range, new_text)| (range, new_text.into())); + + self.start_transaction(); + let timestamp = self.lamport_clock.tick(); + let operation = Operation::Edit(self.apply_local_edit(edits, timestamp)); + + self.history.push(operation.clone()); + self.history.push_undo(operation.timestamp()); + self.snapshot.version.observe(operation.timestamp()); + self.end_transaction(); + operation + } + + fn apply_local_edit>>( + &mut self, + edits: impl ExactSizeIterator, T)>, + timestamp: clock::Lamport, + ) -> EditOperation { + let mut edits_patch = Patch::default(); + let mut edit_op = EditOperation { + timestamp, + version: self.version(), + ranges: Vec::with_capacity(edits.len()), + new_text: Vec::with_capacity(edits.len()), + }; + let mut new_insertions = Vec::new(); + let mut insertion_offset = 0; + let mut insertion_slices = Vec::new(); + + let mut edits = edits + .map(|(range, new_text)| (range.to_offset(&*self), new_text)) + .peekable(); + + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::(); + let mut new_fragments = + old_fragments.slice(&edits.peek().unwrap().0.start, Bias::Right, &None); + new_ropes.append(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().visible; + for (range, new_text) in edits { + let new_text = LineEnding::normalize_arc(new_text.into()); + let fragment_end = old_fragments.end(&None).visible; + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + suffix.insertion_offset += fragment_start - old_fragments.start().visible; + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let slice = old_fragments.slice(&range.start, Bias::Right, &None); + new_ropes.append(slice.summary().text); + new_fragments.append(slice, &None); + fragment_start = old_fragments.start().visible; + } + + let full_range_start = FullOffset(range.start + old_fragments.start().deleted); + + // Preserve any portion of the current fragment that precedes this range. + if fragment_start < range.start { + let mut prefix = old_fragments.item().unwrap().clone(); + prefix.len = range.start - fragment_start; + prefix.insertion_offset += fragment_start - old_fragments.start().visible; + prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + fragment_start = range.start; + } + + // Insert the new text before any existing fragments within the range. + if !new_text.is_empty() { + let new_start = new_fragments.summary().text.visible; + + let fragment = Fragment { + id: Locator::between( + &new_fragments.summary().max_id, + old_fragments + .item() + .map_or(&Locator::max(), |old_fragment| &old_fragment.id), + ), + timestamp, + insertion_offset, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }; + edits_patch.push(Edit { + old: fragment_start..fragment_start, + new: new_start..new_start + new_text.len(), + }); + insertion_slices.push(fragment.insertion_slice()); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_str(new_text.as_ref()); + new_fragments.push(fragment, &None); + insertion_offset += new_text.len(); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&None).visible; + let mut intersection = fragment.clone(); + let intersection_end = cmp::min(range.end, fragment_end); + if fragment.visible { + intersection.len = intersection_end - fragment_start; + intersection.insertion_offset += fragment_start - old_fragments.start().visible; + intersection.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); + intersection.deletions.insert(timestamp); + intersection.visible = false; + } + if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let new_start = new_fragments.summary().text.visible; + edits_patch.push(Edit { + old: fragment_start..intersection_end, + new: new_start..new_start, + }); + insertion_slices.push(intersection.insertion_slice()); + } + new_insertions.push(InsertionFragment::insert_new(&intersection)); + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&None); + } + } + + let full_range_end = FullOffset(range.end + old_fragments.start().deleted); + edit_op.ranges.push(full_range_start..full_range_end); + edit_op.new_text.push(new_text); + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + let fragment_end = old_fragments.end(&None).visible; + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + suffix.insertion_offset += fragment_start - old_fragments.start().visible; + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let suffix = old_fragments.suffix(&None); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.snapshot.fragments = new_fragments; + self.snapshot.insertions.edit(new_insertions, &()); + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.subscriptions.publish_mut(&edits_patch); + self.history + .insertion_slices + .insert(timestamp, insertion_slices); + edit_op + } + + pub fn set_line_ending(&mut self, line_ending: LineEnding) { + self.snapshot.line_ending = line_ending; + } + + pub fn apply_ops>(&mut self, ops: I) -> Result<()> { + let mut deferred_ops = Vec::new(); + for op in ops { + self.history.push(op.clone()); + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + self.flush_deferred_ops()?; + Ok(()) + } + + fn apply_op(&mut self, op: Operation) -> Result<()> { + match op { + Operation::Edit(edit) => { + if !self.version.observed(edit.timestamp) { + self.apply_remote_edit( + &edit.version, + &edit.ranges, + &edit.new_text, + edit.timestamp, + ); + self.snapshot.version.observe(edit.timestamp); + self.lamport_clock.observe(edit.timestamp); + self.resolve_edit(edit.timestamp); + } + } + Operation::Undo(undo) => { + if !self.version.observed(undo.timestamp) { + self.apply_undo(&undo)?; + self.snapshot.version.observe(undo.timestamp); + self.lamport_clock.observe(undo.timestamp); + } + } + } + self.wait_for_version_txs.retain_mut(|(version, tx)| { + if self.snapshot.version().observed_all(version) { + tx.try_send(()).ok(); + false + } else { + true + } + }); + Ok(()) + } + + fn apply_remote_edit( + &mut self, + version: &clock::Global, + ranges: &[Range], + new_text: &[Arc], + timestamp: clock::Lamport, + ) { + if ranges.is_empty() { + return; + } + + let edits = ranges.iter().zip(new_text.iter()); + let mut edits_patch = Patch::default(); + let mut insertion_slices = Vec::new(); + let cx = Some(version.clone()); + let mut new_insertions = Vec::new(); + let mut insertion_offset = 0; + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); + let mut new_fragments = old_fragments.slice( + &VersionedFullOffset::Offset(ranges[0].start), + Bias::Left, + &cx, + ); + new_ropes.append(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().0.full_offset(); + for (range, new_text) in edits { + let fragment_end = old_fragments.end(&cx).0.full_offset(); + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().0.full_offset() { + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end.0 - fragment_start.0; + suffix.insertion_offset += + fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let slice = + old_fragments.slice(&VersionedFullOffset::Offset(range.start), Bias::Left, &cx); + new_ropes.append(slice.summary().text); + new_fragments.append(slice, &None); + fragment_start = old_fragments.start().0.full_offset(); + } + + // If we are at the end of a non-concurrent fragment, advance to the next one. + let fragment_end = old_fragments.end(&cx).0.full_offset(); + if fragment_end == range.start && fragment_end > fragment_start { + let mut fragment = old_fragments.item().unwrap().clone(); + fragment.len = fragment_end.0 - fragment_start.0; + fragment.insertion_offset += fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_fragment(&fragment, fragment.visible); + new_fragments.push(fragment, &None); + old_fragments.next(&cx); + fragment_start = old_fragments.start().0.full_offset(); + } + + // Skip over insertions that are concurrent to this edit, but have a lower lamport + // timestamp. + while let Some(fragment) = old_fragments.item() { + if fragment_start == range.start && fragment.timestamp > timestamp { + new_ropes.push_fragment(fragment, fragment.visible); + new_fragments.push(fragment.clone(), &None); + old_fragments.next(&cx); + debug_assert_eq!(fragment_start, range.start); + } else { + break; + } + } + debug_assert!(fragment_start <= range.start); + + // Preserve any portion of the current fragment that precedes this range. + if fragment_start < range.start { + let mut prefix = old_fragments.item().unwrap().clone(); + prefix.len = range.start.0 - fragment_start.0; + prefix.insertion_offset += fragment_start - old_fragments.start().0.full_offset(); + prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); + fragment_start = range.start; + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + } + + // Insert the new text before any existing fragments within the range. + if !new_text.is_empty() { + let mut old_start = old_fragments.start().1; + if old_fragments.item().map_or(false, |f| f.visible) { + old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; + } + let new_start = new_fragments.summary().text.visible; + let fragment = Fragment { + id: Locator::between( + &new_fragments.summary().max_id, + old_fragments + .item() + .map_or(&Locator::max(), |old_fragment| &old_fragment.id), + ), + timestamp, + insertion_offset, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }; + edits_patch.push(Edit { + old: old_start..old_start, + new: new_start..new_start + new_text.len(), + }); + insertion_slices.push(fragment.insertion_slice()); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_str(new_text); + new_fragments.push(fragment, &None); + insertion_offset += new_text.len(); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); + let mut intersection = fragment.clone(); + let intersection_end = cmp::min(range.end, fragment_end); + if fragment.was_visible(version, &self.undo_map) { + intersection.len = intersection_end.0 - fragment_start.0; + intersection.insertion_offset += + fragment_start - old_fragments.start().0.full_offset(); + intersection.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); + intersection.deletions.insert(timestamp); + intersection.visible = false; + insertion_slices.push(intersection.insertion_slice()); + } + if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let old_start = old_fragments.start().1 + + (fragment_start.0 - old_fragments.start().0.full_offset().0); + let new_start = new_fragments.summary().text.visible; + edits_patch.push(Edit { + old: old_start..old_start + intersection.len, + new: new_start..new_start, + }); + } + new_insertions.push(InsertionFragment::insert_new(&intersection)); + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&cx); + } + } + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().0.full_offset() { + let fragment_end = old_fragments.end(&cx).0.full_offset(); + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end.0 - fragment_start.0; + suffix.insertion_offset += fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let suffix = old_fragments.suffix(&cx); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.snapshot.fragments = new_fragments; + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.snapshot.insertions.edit(new_insertions, &()); + self.history + .insertion_slices + .insert(timestamp, insertion_slices); + self.subscriptions.publish_mut(&edits_patch) + } + + fn fragment_ids_for_edits<'a>( + &'a self, + edit_ids: impl Iterator, + ) -> Vec<&'a Locator> { + // Get all of the insertion slices changed by the given edits. + let mut insertion_slices = Vec::new(); + for edit_id in edit_ids { + if let Some(slices) = self.history.insertion_slices.get(edit_id) { + insertion_slices.extend_from_slice(slices) + } + } + insertion_slices + .sort_unstable_by_key(|s| (s.insertion_id, s.range.start, Reverse(s.range.end))); + + // Get all of the fragments corresponding to these insertion slices. + let mut fragment_ids = Vec::new(); + let mut insertions_cursor = self.insertions.cursor::(); + for insertion_slice in &insertion_slices { + if insertion_slice.insertion_id != insertions_cursor.start().timestamp + || insertion_slice.range.start > insertions_cursor.start().split_offset + { + insertions_cursor.seek_forward( + &InsertionFragmentKey { + timestamp: insertion_slice.insertion_id, + split_offset: insertion_slice.range.start, + }, + Bias::Left, + &(), + ); + } + while let Some(item) = insertions_cursor.item() { + if item.timestamp != insertion_slice.insertion_id + || item.split_offset >= insertion_slice.range.end + { + break; + } + fragment_ids.push(&item.fragment_id); + insertions_cursor.next(&()); + } + } + fragment_ids.sort_unstable(); + fragment_ids + } + + fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { + self.snapshot.undo_map.insert(undo); + + let mut edits = Patch::default(); + let mut old_fragments = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let mut new_fragments = SumTree::new(); + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + + for fragment_id in self.fragment_ids_for_edits(undo.counts.keys()) { + let preceding_fragments = old_fragments.slice(&Some(fragment_id), Bias::Left, &None); + new_ropes.append(preceding_fragments.summary().text); + new_fragments.append(preceding_fragments, &None); + + if let Some(fragment) = old_fragments.item() { + let mut fragment = fragment.clone(); + let fragment_was_visible = fragment.visible; + + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.timestamp); + + let old_start = old_fragments.start().1; + let new_start = new_fragments.summary().text.visible; + if fragment_was_visible && !fragment.visible { + edits.push(Edit { + old: old_start..old_start + fragment.len, + new: new_start..new_start, + }); + } else if !fragment_was_visible && fragment.visible { + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + fragment.len, + }); + } + new_ropes.push_fragment(&fragment, fragment_was_visible); + new_fragments.push(fragment, &None); + + old_fragments.next(&None); + } + } + + let suffix = old_fragments.suffix(&None); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + + drop(old_fragments); + let (visible_text, deleted_text) = new_ropes.finish(); + self.snapshot.fragments = new_fragments; + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.subscriptions.publish_mut(&edits); + Ok(()) + } + + fn flush_deferred_ops(&mut self) -> Result<()> { + self.deferred_replicas.clear(); + let mut deferred_ops = Vec::new(); + for op in self.deferred_ops.drain().iter().cloned() { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + Ok(()) + } + + fn can_apply_op(&self, op: &Operation) -> bool { + if self.deferred_replicas.contains(&op.replica_id()) { + false + } else { + self.version.observed_all(match op { + Operation::Edit(edit) => &edit.version, + Operation::Undo(undo) => &undo.version, + }) + } + } + + pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> { + self.history.undo_stack.last() + } + + pub fn peek_redo_stack(&self) -> Option<&HistoryEntry> { + self.history.redo_stack.last() + } + + pub fn start_transaction(&mut self) -> Option { + self.start_transaction_at(Instant::now()) + } + + pub fn start_transaction_at(&mut self, now: Instant) -> Option { + self.history + .start_transaction(self.version.clone(), now, &mut self.lamport_clock) + } + + pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> { + self.end_transaction_at(Instant::now()) + } + + pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> { + if let Some(entry) = self.history.end_transaction(now) { + let since = entry.transaction.start.clone(); + let id = self.history.group().unwrap(); + Some((id, since)) + } else { + None + } + } + + pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> { + self.history.finalize_last_transaction() + } + + pub fn group_until_transaction(&mut self, transaction_id: TransactionId) { + self.history.group_until(transaction_id); + } + + pub fn base_text(&self) -> &Rope { + &self.history.base_text + } + + pub fn operations(&self) -> &TreeMap { + &self.history.operations + } + + pub fn undo(&mut self) -> Option<(TransactionId, Operation)> { + if let Some(entry) = self.history.pop_undo() { + let transaction = entry.transaction.clone(); + let transaction_id = transaction.id; + let op = self.undo_or_redo(transaction).unwrap(); + Some((transaction_id, op)) + } else { + None + } + } + + pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option { + let transaction = self + .history + .remove_from_undo(transaction_id)? + .transaction + .clone(); + self.undo_or_redo(transaction).log_err() + } + + #[allow(clippy::needless_collect)] + pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { + let transactions = self + .history + .remove_from_undo_until(transaction_id) + .iter() + .map(|entry| entry.transaction.clone()) + .collect::>(); + + transactions + .into_iter() + .map(|transaction| self.undo_or_redo(transaction).unwrap()) + .collect() + } + + pub fn forget_transaction(&mut self, transaction_id: TransactionId) { + self.history.forget(transaction_id); + } + + pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { + self.history.merge_transactions(transaction, destination); + } + + pub fn redo(&mut self) -> Option<(TransactionId, Operation)> { + if let Some(entry) = self.history.pop_redo() { + let transaction = entry.transaction.clone(); + let transaction_id = transaction.id; + let op = self.undo_or_redo(transaction).unwrap(); + Some((transaction_id, op)) + } else { + None + } + } + + #[allow(clippy::needless_collect)] + pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { + let transactions = self + .history + .remove_from_redo(transaction_id) + .iter() + .map(|entry| entry.transaction.clone()) + .collect::>(); + + transactions + .into_iter() + .map(|transaction| self.undo_or_redo(transaction).unwrap()) + .collect() + } + + fn undo_or_redo(&mut self, transaction: Transaction) -> Result { + let mut counts = HashMap::default(); + for edit_id in transaction.edit_ids { + counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); + } + + let undo = UndoOperation { + timestamp: self.lamport_clock.tick(), + version: self.version(), + counts, + }; + self.apply_undo(&undo)?; + self.snapshot.version.observe(undo.timestamp); + let operation = Operation::Undo(undo); + self.history.push(operation.clone()); + Ok(operation) + } + + pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) { + self.history.push_transaction(transaction, now); + self.history.finalize_last_transaction(); + } + + pub fn edited_ranges_for_transaction<'a, D>( + &'a self, + transaction: &'a Transaction, + ) -> impl 'a + Iterator> + where + D: TextDimension, + { + // get fragment ranges + let mut cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let offset_ranges = self + .fragment_ids_for_edits(transaction.edit_ids.iter()) + .into_iter() + .filter_map(move |fragment_id| { + cursor.seek_forward(&Some(fragment_id), Bias::Left, &None); + let fragment = cursor.item()?; + let start_offset = cursor.start().1; + let end_offset = start_offset + if fragment.visible { fragment.len } else { 0 }; + Some(start_offset..end_offset) + }); + + // combine adjacent ranges + let mut prev_range: Option> = None; + let disjoint_ranges = offset_ranges + .map(Some) + .chain([None]) + .filter_map(move |range| { + if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) { + if prev_range.end == range.start { + prev_range.end = range.end; + return None; + } + } + let result = prev_range.clone(); + prev_range = range; + result + }); + + // convert to the desired text dimension. + let mut position = D::default(); + let mut rope_cursor = self.visible_text.cursor(0); + disjoint_ranges.map(move |range| { + position.add_assign(&rope_cursor.summary(range.start)); + let start = position.clone(); + position.add_assign(&rope_cursor.summary(range.end)); + let end = position.clone(); + start..end + }) + } + + pub fn subscribe(&mut self) -> Subscription { + self.subscriptions.subscribe() + } + + pub fn wait_for_edits( + &mut self, + edit_ids: impl IntoIterator, + ) -> impl 'static + Future> { + let mut futures = Vec::new(); + for edit_id in edit_ids { + if !self.version.observed(edit_id) { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers.entry(edit_id).or_default().push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + if future.recv().await.is_none() { + Err(anyhow!("gave up waiting for edits"))?; + } + } + Ok(()) + } + } + + pub fn wait_for_anchors( + &mut self, + anchors: impl IntoIterator, + ) -> impl 'static + Future> { + let mut futures = Vec::new(); + for anchor in anchors { + if !self.version.observed(anchor.timestamp) + && anchor != Anchor::MAX + && anchor != Anchor::MIN + { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers + .entry(anchor.timestamp) + .or_default() + .push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + if future.recv().await.is_none() { + Err(anyhow!("gave up waiting for anchors"))?; + } + } + Ok(()) + } + } + + pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future> { + let mut rx = None; + if !self.snapshot.version.observed_all(&version) { + let channel = oneshot::channel(); + self.wait_for_version_txs.push((version, channel.0)); + rx = Some(channel.1); + } + async move { + if let Some(mut rx) = rx { + if rx.recv().await.is_none() { + Err(anyhow!("gave up waiting for version"))?; + } + } + Ok(()) + } + } + + pub fn give_up_waiting(&mut self) { + self.edit_id_resolvers.clear(); + self.wait_for_version_txs.clear(); + } + + fn resolve_edit(&mut self, edit_id: clock::Lamport) { + for mut tx in self + .edit_id_resolvers + .remove(&edit_id) + .into_iter() + .flatten() + { + tx.try_send(()).ok(); + } + } +} + +#[cfg(any(test, feature = "test-support"))] +impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + + pub fn check_invariants(&self) { + // Ensure every fragment is ordered by locator in the fragment tree and corresponds + // to an insertion fragment in the insertions tree. + let mut prev_fragment_id = Locator::min(); + for fragment in self.snapshot.fragments.items(&None) { + assert!(fragment.id > prev_fragment_id); + prev_fragment_id = fragment.id.clone(); + + let insertion_fragment = self + .snapshot + .insertions + .get( + &InsertionFragmentKey { + timestamp: fragment.timestamp, + split_offset: fragment.insertion_offset, + }, + &(), + ) + .unwrap(); + assert_eq!( + insertion_fragment.fragment_id, fragment.id, + "fragment: {:?}\ninsertion: {:?}", + fragment, insertion_fragment + ); + } + + let mut cursor = self.snapshot.fragments.cursor::>(); + for insertion_fragment in self.snapshot.insertions.cursor::<()>() { + cursor.seek(&Some(&insertion_fragment.fragment_id), Bias::Left, &None); + let fragment = cursor.item().unwrap(); + assert_eq!(insertion_fragment.fragment_id, fragment.id); + assert_eq!(insertion_fragment.split_offset, fragment.insertion_offset); + } + + let fragment_summary = self.snapshot.fragments.summary(); + assert_eq!( + fragment_summary.text.visible, + self.snapshot.visible_text.len() + ); + assert_eq!( + fragment_summary.text.deleted, + self.snapshot.deleted_text.len() + ); + + assert!(!self.text().contains("\r\n")); + } + + pub fn set_group_interval(&mut self, group_interval: Duration) { + self.history.group_interval = group_interval; + } + + pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { + let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); + let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); + start..end + } + + pub fn get_random_edits( + &self, + rng: &mut T, + edit_count: usize, + ) -> Vec<(Range, Arc)> + where + T: rand::Rng, + { + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= self.len()) { + break; + } + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let range = self.random_byte_range(new_start, rng); + last_end = Some(range.end); + + let new_text_len = rng.gen_range(0..10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + edits.push((range, new_text.into())); + } + edits + } + + #[allow(clippy::type_complexity)] + pub fn randomly_edit( + &mut self, + rng: &mut T, + edit_count: usize, + ) -> (Vec<(Range, Arc)>, Operation) + where + T: rand::Rng, + { + let mut edits = self.get_random_edits(rng, edit_count); + log::info!("mutating buffer {} with {:?}", self.replica_id, edits); + + let op = self.edit(edits.iter().cloned()); + if let Operation::Edit(edit) = &op { + assert_eq!(edits.len(), edit.new_text.len()); + for (edit, new_text) in edits.iter_mut().zip(&edit.new_text) { + edit.1 = new_text.clone(); + } + } else { + unreachable!() + } + + (edits, op) + } + + pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec { + use rand::prelude::*; + + let mut ops = Vec::new(); + for _ in 0..rng.gen_range(1..=5) { + if let Some(entry) = self.history.undo_stack.choose(rng) { + let transaction = entry.transaction.clone(); + log::info!( + "undoing buffer {} transaction {:?}", + self.replica_id, + transaction + ); + ops.push(self.undo_or_redo(transaction).unwrap()); + } + } + ops + } +} + +impl Deref for Buffer { + type Target = BufferSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + +impl BufferSnapshot { + pub fn as_rope(&self) -> &Rope { + &self.visible_text + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn replica_id(&self) -> ReplicaId { + self.replica_id + } + + pub fn row_count(&self) -> u32 { + self.max_point().row + 1 + } + + pub fn len(&self) -> usize { + self.visible_text.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn chars(&self) -> impl Iterator + '_ { + self.chars_at(0) + } + + pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { + self.text_for_range(range).flat_map(str::chars) + } + + pub fn reversed_chars_for_range( + &self, + range: Range, + ) -> impl Iterator + '_ { + self.reversed_chunks_in_range(range) + .flat_map(|chunk| chunk.chars().rev()) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_in_range(position..self.len()) + .flatten() + .copied() + .take(needle.len()) + .eq(needle.bytes()) + } + + pub fn common_prefix_at(&self, position: T, needle: &str) -> Range + where + T: ToOffset + TextDimension, + { + let offset = position.to_offset(self); + let common_prefix_len = needle + .char_indices() + .map(|(index, _)| index) + .chain([needle.len()]) + .take_while(|&len| len <= offset) + .filter(|&len| { + let left = self + .chars_for_range(offset - len..offset) + .flat_map(char::to_lowercase); + let right = needle[..len].chars().flat_map(char::to_lowercase); + left.eq(right) + }) + .last() + .unwrap_or(0); + let start_offset = offset - common_prefix_len; + let start = self.text_summary_for_range(0..start_offset); + start..position + } + + pub fn text(&self) -> String { + self.visible_text.to_string() + } + + pub fn line_ending(&self) -> LineEnding { + self.line_ending + } + + pub fn deleted_text(&self) -> String { + self.deleted_text.to_string() + } + + pub fn fragments(&self) -> impl Iterator { + self.fragments.iter() + } + + pub fn text_summary(&self) -> TextSummary { + self.visible_text.summary() + } + + pub fn max_point(&self) -> Point { + self.visible_text.max_point() + } + + pub fn max_point_utf16(&self) -> PointUtf16 { + self.visible_text.max_point_utf16() + } + + pub fn point_to_offset(&self, point: Point) -> usize { + self.visible_text.point_to_offset(point) + } + + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + self.visible_text.point_utf16_to_offset(point) + } + + pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped) -> usize { + self.visible_text.unclipped_point_utf16_to_offset(point) + } + + pub fn unclipped_point_utf16_to_point(&self, point: Unclipped) -> Point { + self.visible_text.unclipped_point_utf16_to_point(point) + } + + pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { + self.visible_text.offset_utf16_to_offset(offset) + } + + pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { + self.visible_text.offset_to_offset_utf16(offset) + } + + pub fn offset_to_point(&self, offset: usize) -> Point { + self.visible_text.offset_to_point(offset) + } + + pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { + self.visible_text.offset_to_point_utf16(offset) + } + + pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { + self.visible_text.point_to_point_utf16(point) + } + + pub fn version(&self) -> &clock::Global { + &self.version + } + + pub fn chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.chars_at(offset) + } + + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.reversed_chars_at(offset) + } + + pub fn reversed_chunks_in_range(&self, range: Range) -> rope::Chunks { + let range = range.start.to_offset(self)..range.end.to_offset(self); + self.visible_text.reversed_chunks_in_range(range) + } + + pub fn bytes_in_range(&self, range: Range) -> rope::Bytes<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.bytes_in_range(start..end) + } + + pub fn reversed_bytes_in_range(&self, range: Range) -> rope::Bytes<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.reversed_bytes_in_range(start..end) + } + + pub fn text_for_range(&self, range: Range) -> Chunks<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.chunks_in_range(start..end) + } + + pub fn line_len(&self, row: u32) -> u32 { + let row_start_offset = Point::new(row, 0).to_offset(self); + let row_end_offset = if row >= self.max_point().row { + self.len() + } else { + Point::new(row + 1, 0).to_offset(self) - 1 + }; + (row_end_offset - row_start_offset) as u32 + } + + pub fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + + pub fn text_summary_for_range(&self, range: Range) -> D + where + D: TextDimension, + { + self.visible_text + .cursor(range.start.to_offset(self)) + .summary(range.end.to_offset(self)) + } + + pub fn summaries_for_anchors<'a, D, A>(&'a self, anchors: A) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))) + .map(|d| d.0) + } + + pub fn summaries_for_anchors_with_payload<'a, D, A, T>( + &'a self, + anchors: A, + ) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + let mut insertion_cursor = self.insertions.cursor::(); + let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let mut text_cursor = self.visible_text.cursor(0); + let mut position = D::default(); + + anchors.map(move |(anchor, payload)| { + if *anchor == Anchor::MIN { + return (D::default(), payload); + } else if *anchor == Anchor::MAX { + return (D::from_text_summary(&self.visible_text.summary()), payload); + } + + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + + fragment_cursor.seek_forward(&Some(&insertion.fragment_id), Bias::Left, &None); + let fragment = fragment_cursor.item().unwrap(); + let mut fragment_offset = fragment_cursor.start().1; + if fragment.visible { + fragment_offset += anchor.offset - insertion.split_offset; + } + + position.add_assign(&text_cursor.summary(fragment_offset)); + (position.clone(), payload) + }) + } + + fn summary_for_anchor(&self, anchor: &Anchor) -> D + where + D: TextDimension, + { + if *anchor == Anchor::MIN { + D::default() + } else if *anchor == Anchor::MAX { + D::from_text_summary(&self.visible_text.summary()) + } else { + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + let mut insertion_cursor = self.insertions.cursor::(); + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + + let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + fragment_cursor.seek(&Some(&insertion.fragment_id), Bias::Left, &None); + let fragment = fragment_cursor.item().unwrap(); + let mut fragment_offset = fragment_cursor.start().1; + if fragment.visible { + fragment_offset += anchor.offset - insertion.split_offset; + } + self.text_summary_for_range(0..fragment_offset) + } + } + + fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { + if *anchor == Anchor::MIN { + Locator::min_ref() + } else if *anchor == Anchor::MAX { + Locator::max_ref() + } else { + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + let mut insertion_cursor = self.insertions.cursor::(); + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + &insertion.fragment_id + } + } + + pub fn anchor_before(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Left) + } + + pub fn anchor_after(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Right) + } + + pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { + self.anchor_at_offset(position.to_offset(self), bias) + } + + fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor { + if bias == Bias::Left && offset == 0 { + Anchor::MIN + } else if bias == Bias::Right && offset == self.len() { + Anchor::MAX + } else { + let mut fragment_cursor = self.fragments.cursor::(); + fragment_cursor.seek(&offset, bias, &None); + let fragment = fragment_cursor.item().unwrap(); + let overshoot = offset - *fragment_cursor.start(); + Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset + overshoot, + bias, + buffer_id: Some(self.remote_id), + } + } + } + + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + *anchor == Anchor::MIN + || *anchor == Anchor::MAX + || (Some(self.remote_id) == anchor.buffer_id && self.version.observed(anchor.timestamp)) + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + self.visible_text.clip_offset(offset, bias) + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + self.visible_text.clip_point(point, bias) + } + + pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { + self.visible_text.clip_offset_utf16(offset, bias) + } + + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { + self.visible_text.clip_point_utf16(point, bias) + } + + pub fn edits_since<'a, D>( + &'a self, + since: &'a clock::Global, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord, + { + self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + } + + pub fn anchored_edits_since<'a, D>( + &'a self, + since: &'a clock::Global, + ) -> impl 'a + Iterator, Range)> + where + D: TextDimension + Ord, + { + self.anchored_edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + } + + pub fn edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord, + { + self.anchored_edits_since_in_range(since, range) + .map(|item| item.0) + } + + pub fn anchored_edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator, Range)> + where + D: TextDimension + Ord, + { + let fragments_cursor = if *since == self.version { + None + } else { + let mut cursor = self + .fragments + .filter(move |summary| !since.observed_all(&summary.max_version)); + cursor.next(&None); + Some(cursor) + }; + let mut cursor = self + .fragments + .cursor::<(Option<&Locator>, FragmentTextSummary)>(); + + let start_fragment_id = self.fragment_id_for_anchor(&range.start); + cursor.seek(&Some(start_fragment_id), Bias::Left, &None); + let mut visible_start = cursor.start().1.visible; + let mut deleted_start = cursor.start().1.deleted; + if let Some(fragment) = cursor.item() { + let overshoot = range.start.offset - fragment.insertion_offset; + if fragment.visible { + visible_start += overshoot; + } else { + deleted_start += overshoot; + } + } + let end_fragment_id = self.fragment_id_for_anchor(&range.end); + + Edits { + visible_cursor: self.visible_text.cursor(visible_start), + deleted_cursor: self.deleted_text.cursor(deleted_start), + fragments_cursor, + undos: &self.undo_map, + since, + old_end: Default::default(), + new_end: Default::default(), + range: (start_fragment_id, range.start.offset)..(end_fragment_id, range.end.offset), + buffer_id: self.remote_id, + } + } +} + +struct RopeBuilder<'a> { + old_visible_cursor: rope::Cursor<'a>, + old_deleted_cursor: rope::Cursor<'a>, + new_visible: Rope, + new_deleted: Rope, +} + +impl<'a> RopeBuilder<'a> { + fn new(old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>) -> Self { + Self { + old_visible_cursor, + old_deleted_cursor, + new_visible: Rope::new(), + new_deleted: Rope::new(), + } + } + + fn append(&mut self, len: FragmentTextSummary) { + self.push(len.visible, true, true); + self.push(len.deleted, false, false); + } + + fn push_fragment(&mut self, fragment: &Fragment, was_visible: bool) { + debug_assert!(fragment.len > 0); + self.push(fragment.len, was_visible, fragment.visible) + } + + fn push(&mut self, len: usize, was_visible: bool, is_visible: bool) { + let text = if was_visible { + self.old_visible_cursor + .slice(self.old_visible_cursor.offset() + len) + } else { + self.old_deleted_cursor + .slice(self.old_deleted_cursor.offset() + len) + }; + if is_visible { + self.new_visible.append(text); + } else { + self.new_deleted.append(text); + } + } + + fn push_str(&mut self, text: &str) { + self.new_visible.push(text); + } + + fn finish(mut self) -> (Rope, Rope) { + self.new_visible.append(self.old_visible_cursor.suffix()); + self.new_deleted.append(self.old_deleted_cursor.suffix()); + (self.new_visible, self.new_deleted) + } +} + +impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, D, F> { + type Item = (Edit, Range); + + fn next(&mut self) -> Option { + let mut pending_edit: Option = None; + let cursor = self.fragments_cursor.as_mut()?; + + while let Some(fragment) = cursor.item() { + if fragment.id < *self.range.start.0 { + cursor.next(&None); + continue; + } else if fragment.id > *self.range.end.0 { + break; + } + + if cursor.start().visible > self.visible_cursor.offset() { + let summary = self.visible_cursor.summary(cursor.start().visible); + self.old_end.add_assign(&summary); + self.new_end.add_assign(&summary); + } + + if pending_edit + .as_ref() + .map_or(false, |(change, _)| change.new.end < self.new_end) + { + break; + } + + let start_anchor = Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset, + bias: Bias::Right, + buffer_id: Some(self.buffer_id), + }; + let end_anchor = Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset + fragment.len, + bias: Bias::Left, + buffer_id: Some(self.buffer_id), + }; + + if !fragment.was_visible(self.since, self.undos) && fragment.visible { + let mut visible_end = cursor.end(&None).visible; + if fragment.id == *self.range.end.0 { + visible_end = cmp::min( + visible_end, + cursor.start().visible + (self.range.end.1 - fragment.insertion_offset), + ); + } + + let fragment_summary = self.visible_cursor.summary(visible_end); + let mut new_end = self.new_end.clone(); + new_end.add_assign(&fragment_summary); + if let Some((edit, range)) = pending_edit.as_mut() { + edit.new.end = new_end.clone(); + range.end = end_anchor; + } else { + pending_edit = Some(( + Edit { + old: self.old_end.clone()..self.old_end.clone(), + new: self.new_end.clone()..new_end.clone(), + }, + start_anchor..end_anchor, + )); + } + + self.new_end = new_end; + } else if fragment.was_visible(self.since, self.undos) && !fragment.visible { + let mut deleted_end = cursor.end(&None).deleted; + if fragment.id == *self.range.end.0 { + deleted_end = cmp::min( + deleted_end, + cursor.start().deleted + (self.range.end.1 - fragment.insertion_offset), + ); + } + + if cursor.start().deleted > self.deleted_cursor.offset() { + self.deleted_cursor.seek_forward(cursor.start().deleted); + } + let fragment_summary = self.deleted_cursor.summary(deleted_end); + let mut old_end = self.old_end.clone(); + old_end.add_assign(&fragment_summary); + if let Some((edit, range)) = pending_edit.as_mut() { + edit.old.end = old_end.clone(); + range.end = end_anchor; + } else { + pending_edit = Some(( + Edit { + old: self.old_end.clone()..old_end.clone(), + new: self.new_end.clone()..self.new_end.clone(), + }, + start_anchor..end_anchor, + )); + } + + self.old_end = old_end; + } + + cursor.next(&None); + } + + pending_edit + } +} + +impl Fragment { + fn insertion_slice(&self) -> InsertionSlice { + InsertionSlice { + insertion_id: self.timestamp, + range: self.insertion_offset..self.insertion_offset + self.len, + } + } + + fn is_visible(&self, undos: &UndoMap) -> bool { + !undos.is_undone(self.timestamp) && self.deletions.iter().all(|d| undos.is_undone(*d)) + } + + fn was_visible(&self, version: &clock::Global, undos: &UndoMap) -> bool { + (version.observed(self.timestamp) && !undos.was_undone(self.timestamp, version)) + && self + .deletions + .iter() + .all(|d| !version.observed(*d) || undos.was_undone(*d, version)) + } +} + +impl sum_tree::Item for Fragment { + type Summary = FragmentSummary; + + fn summary(&self) -> Self::Summary { + let mut max_version = clock::Global::new(); + max_version.observe(self.timestamp); + for deletion in &self.deletions { + max_version.observe(*deletion); + } + max_version.join(&self.max_undos); + + let mut min_insertion_version = clock::Global::new(); + min_insertion_version.observe(self.timestamp); + let max_insertion_version = min_insertion_version.clone(); + if self.visible { + FragmentSummary { + max_id: self.id.clone(), + text: FragmentTextSummary { + visible: self.len, + deleted: 0, + }, + max_version, + min_insertion_version, + max_insertion_version, + } + } else { + FragmentSummary { + max_id: self.id.clone(), + text: FragmentTextSummary { + visible: 0, + deleted: self.len, + }, + max_version, + min_insertion_version, + max_insertion_version, + } + } + } +} + +impl sum_tree::Summary for FragmentSummary { + type Context = Option; + + fn add_summary(&mut self, other: &Self, _: &Self::Context) { + self.max_id.assign(&other.max_id); + self.text.visible += &other.text.visible; + self.text.deleted += &other.text.deleted; + self.max_version.join(&other.max_version); + self.min_insertion_version + .meet(&other.min_insertion_version); + self.max_insertion_version + .join(&other.max_insertion_version); + } +} + +impl Default for FragmentSummary { + fn default() -> Self { + FragmentSummary { + max_id: Locator::min(), + text: FragmentTextSummary::default(), + max_version: clock::Global::new(), + min_insertion_version: clock::Global::new(), + max_insertion_version: clock::Global::new(), + } + } +} + +impl sum_tree::Item for InsertionFragment { + type Summary = InsertionFragmentKey; + + fn summary(&self) -> Self::Summary { + InsertionFragmentKey { + timestamp: self.timestamp, + split_offset: self.split_offset, + } + } +} + +impl sum_tree::KeyedItem for InsertionFragment { + type Key = InsertionFragmentKey; + + fn key(&self) -> Self::Key { + sum_tree::Item::summary(self) + } +} + +impl InsertionFragment { + fn new(fragment: &Fragment) -> Self { + Self { + timestamp: fragment.timestamp, + split_offset: fragment.insertion_offset, + fragment_id: fragment.id.clone(), + } + } + + fn insert_new(fragment: &Fragment) -> sum_tree::Edit { + sum_tree::Edit::Insert(Self::new(fragment)) + } +} + +impl sum_tree::Summary for InsertionFragmentKey { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + *self = *summary; + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FullOffset(pub usize); + +impl ops::AddAssign for FullOffset { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +impl ops::Add for FullOffset { + type Output = Self; + + fn add(mut self, rhs: usize) -> Self::Output { + self += rhs; + self + } +} + +impl ops::Sub for FullOffset { + type Output = usize; + + fn sub(self, rhs: Self) -> Self::Output { + self.0 - rhs.0 + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize { + fn add_summary(&mut self, summary: &FragmentSummary, _: &Option) { + *self += summary.text.visible; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FullOffset { + fn add_summary(&mut self, summary: &FragmentSummary, _: &Option) { + self.0 += summary.text.visible + summary.text.deleted; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Option<&'a Locator> { + fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { + *self = Some(&summary.max_id); + } +} + +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, FragmentTextSummary> for usize { + fn cmp( + &self, + cursor_location: &FragmentTextSummary, + _: &Option, + ) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.visible) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum VersionedFullOffset { + Offset(FullOffset), + Invalid, +} + +impl VersionedFullOffset { + fn full_offset(&self) -> FullOffset { + if let Self::Offset(position) = self { + *position + } else { + panic!("invalid version") + } + } +} + +impl Default for VersionedFullOffset { + fn default() -> Self { + Self::Offset(Default::default()) + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for VersionedFullOffset { + fn add_summary(&mut self, summary: &'a FragmentSummary, cx: &Option) { + if let Self::Offset(offset) = self { + let version = cx.as_ref().unwrap(); + if version.observed_all(&summary.max_insertion_version) { + *offset += summary.text.visible + summary.text.deleted; + } else if version.observed_any(&summary.min_insertion_version) { + *self = Self::Invalid; + } + } + } +} + +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, Self> for VersionedFullOffset { + fn cmp(&self, cursor_position: &Self, _: &Option) -> cmp::Ordering { + match (self, cursor_position) { + (Self::Offset(a), Self::Offset(b)) => Ord::cmp(a, b), + (Self::Offset(_), Self::Invalid) => cmp::Ordering::Less, + (Self::Invalid, _) => unreachable!(), + } + } +} + +impl Operation { + fn replica_id(&self) -> ReplicaId { + operation_queue::Operation::lamport_timestamp(self).replica_id + } + + pub fn timestamp(&self) -> clock::Lamport { + match self { + Operation::Edit(edit) => edit.timestamp, + Operation::Undo(undo) => undo.timestamp, + } + } + + pub fn as_edit(&self) -> Option<&EditOperation> { + match self { + Operation::Edit(edit) => Some(edit), + _ => None, + } + } + + pub fn is_edit(&self) -> bool { + matches!(self, Operation::Edit { .. }) + } +} + +impl operation_queue::Operation for Operation { + fn lamport_timestamp(&self) -> clock::Lamport { + match self { + Operation::Edit(edit) => edit.timestamp, + Operation::Undo(undo) => undo.timestamp, + } + } +} + +pub trait ToOffset { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize; +} + +impl ToOffset for Point { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_to_offset(*self) + } +} + +impl ToOffset for usize { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + assert!( + *self <= snapshot.len(), + "offset {} is out of range, max allowed is {}", + self, + snapshot.len() + ); + *self + } +} + +impl ToOffset for Anchor { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.summary_for_anchor(self) + } +} + +impl<'a, T: ToOffset> ToOffset for &'a T { + fn to_offset(&self, content: &BufferSnapshot) -> usize { + (*self).to_offset(content) + } +} + +impl ToOffset for PointUtf16 { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) + } +} + +impl ToOffset for Unclipped { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.unclipped_point_utf16_to_offset(*self) + } +} + +pub trait ToPoint { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point; +} + +impl ToPoint for Anchor { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.summary_for_anchor(self) + } +} + +impl ToPoint for usize { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.offset_to_point(*self) + } +} + +impl ToPoint for Point { + fn to_point(&self, _: &BufferSnapshot) -> Point { + *self + } +} + +impl ToPoint for Unclipped { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.unclipped_point_utf16_to_point(*self) + } +} + +pub trait ToPointUtf16 { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16; +} + +impl ToPointUtf16 for Anchor { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.summary_for_anchor(self) + } +} + +impl ToPointUtf16 for usize { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.offset_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for PointUtf16 { + fn to_point_utf16(&self, _: &BufferSnapshot) -> PointUtf16 { + *self + } +} + +impl ToPointUtf16 for Point { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.point_to_point_utf16(*self) + } +} + +pub trait ToOffsetUtf16 { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16; +} + +impl ToOffsetUtf16 for Anchor { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + snapshot.summary_for_anchor(self) + } +} + +impl ToOffsetUtf16 for usize { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + snapshot.offset_to_offset_utf16(*self) + } +} + +impl ToOffsetUtf16 for OffsetUtf16 { + fn to_offset_utf16(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 { + *self + } +} + +pub trait FromAnchor { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; +} + +impl FromAnchor for Point { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +impl FromAnchor for PointUtf16 { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +impl FromAnchor for usize { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LineEnding { + Unix, + Windows, +} + +impl Default for LineEnding { + fn default() -> Self { + #[cfg(unix)] + return Self::Unix; + + #[cfg(not(unix))] + return Self::CRLF; + } +} + +impl LineEnding { + pub fn as_str(&self) -> &'static str { + match self { + LineEnding::Unix => "\n", + LineEnding::Windows => "\r\n", + } + } + + pub fn detect(text: &str) -> Self { + let mut max_ix = cmp::min(text.len(), 1000); + while !text.is_char_boundary(max_ix) { + max_ix -= 1; + } + + if let Some(ix) = text[..max_ix].find(&['\n']) { + if ix > 0 && text.as_bytes()[ix - 1] == b'\r' { + Self::Windows + } else { + Self::Unix + } + } else { + Self::default() + } + } + + pub fn normalize(text: &mut String) { + if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") { + *text = replaced; + } + } + + pub fn normalize_arc(text: Arc) -> Arc { + if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") { + replaced.into() + } else { + text + } + } +} diff --git a/crates/text2/src/undo_map.rs b/crates/text2/src/undo_map.rs new file mode 100644 index 0000000000..f95809c02e --- /dev/null +++ b/crates/text2/src/undo_map.rs @@ -0,0 +1,112 @@ +use crate::UndoOperation; +use std::cmp; +use sum_tree::{Bias, SumTree}; + +#[derive(Copy, Clone, Debug)] +struct UndoMapEntry { + key: UndoMapKey, + undo_count: u32, +} + +impl sum_tree::Item for UndoMapEntry { + type Summary = UndoMapKey; + + fn summary(&self) -> Self::Summary { + self.key + } +} + +impl sum_tree::KeyedItem for UndoMapEntry { + type Key = UndoMapKey; + + fn key(&self) -> Self::Key { + self.key + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct UndoMapKey { + edit_id: clock::Lamport, + undo_id: clock::Lamport, +} + +impl sum_tree::Summary for UndoMapKey { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + *self = cmp::max(*self, *summary); + } +} + +#[derive(Clone, Default)] +pub struct UndoMap(SumTree); + +impl UndoMap { + pub fn insert(&mut self, undo: &UndoOperation) { + let edits = undo + .counts + .iter() + .map(|(edit_id, count)| { + sum_tree::Edit::Insert(UndoMapEntry { + key: UndoMapKey { + edit_id: *edit_id, + undo_id: undo.timestamp, + }, + undo_count: *count, + }) + }) + .collect::>(); + self.0.edit(edits, &()); + } + + pub fn is_undone(&self, edit_id: clock::Lamport) -> bool { + self.undo_count(edit_id) % 2 == 1 + } + + pub fn was_undone(&self, edit_id: clock::Lamport, version: &clock::Global) -> bool { + let mut cursor = self.0.cursor::(); + cursor.seek( + &UndoMapKey { + edit_id, + undo_id: Default::default(), + }, + Bias::Left, + &(), + ); + + let mut undo_count = 0; + for entry in cursor { + if entry.key.edit_id != edit_id { + break; + } + + if version.observed(entry.key.undo_id) { + undo_count = cmp::max(undo_count, entry.undo_count); + } + } + + undo_count % 2 == 1 + } + + pub fn undo_count(&self, edit_id: clock::Lamport) -> u32 { + let mut cursor = self.0.cursor::(); + cursor.seek( + &UndoMapKey { + edit_id, + undo_id: Default::default(), + }, + Bias::Left, + &(), + ); + + let mut undo_count = 0; + for entry in cursor { + if entry.key.edit_id != edit_id { + break; + } + + undo_count = cmp::max(undo_count, entry.undo_count); + } + undo_count + } +} diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index b75e2d881f..a54e68b51a 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -63,7 +63,7 @@ settings2 = { path = "../settings2" } feature_flags2 = { path = "../feature_flags2" } sum_tree = { path = "../sum_tree" } shellexpand = "2.1.0" -text = { path = "../text" } +text2 = { path = "../text2" } # terminal_view = { path = "../terminal_view" } theme2 = { path = "../theme2" } # theme_selector = { path = "../theme_selector" } @@ -152,7 +152,7 @@ language2 = { path = "../language2", features = ["test-support"] } project2 = { path = "../project2", features = ["test-support"] } # rpc = { path = "../rpc", features = ["test-support"] } # settings = { path = "../settings", features = ["test-support"] } -# text = { path = "../text", features = ["test-support"] } +text2 = { path = "../text2", features = ["test-support"] } # util = { path = "../util", features = ["test-support"] } # workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true From 3f74f75dee94158de57a59d2b1f72a87b8091e4f Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 16:19:49 -0400 Subject: [PATCH 077/156] WIP --- crates/theme2/src/default_colors.rs | 8 +- crates/ui2/src/components/list.rs | 9 +- .../ui2/src/components/notifications_panel.rs | 376 +++++++++++++++++- crates/ui2/src/elements.rs | 2 + crates/ui2/src/elements/indicator.rs | 22 + crates/ui2/src/static_data.rs | 53 ++- 6 files changed, 440 insertions(+), 30 deletions(-) create mode 100644 crates/ui2/src/elements/indicator.rs diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 3f2f0503cd..2e62b68fae 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -199,8 +199,8 @@ impl ThemeColors { ghost_element_disabled: neutral().light_alpha(3).into(), text: neutral().light(12).into(), text_muted: neutral().light(11).into(), - text_placeholder: neutral().light(11).into(), - text_disabled: neutral().light(10).into(), + text_placeholder: neutral().light(10).into(), + text_disabled: neutral().light(9).into(), text_accent: blue().light(11).into(), icon: neutral().light(11).into(), icon_muted: neutral().light(10).into(), @@ -244,8 +244,8 @@ impl ThemeColors { ghost_element_disabled: neutral().dark_alpha(3).into(), text: neutral().dark(12).into(), text_muted: neutral().dark(11).into(), - text_placeholder: neutral().dark(11).into(), - text_disabled: neutral().dark(10).into(), + text_placeholder: neutral().dark(10).into(), + text_disabled: neutral().dark(9).into(), text_accent: blue().dark(11).into(), icon: neutral().dark(11).into(), icon_muted: neutral().dark(10).into(), diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index ad7ec2214f..50a86ff256 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -39,7 +39,7 @@ impl ListHeader { left_icon: None, meta: None, variant: ListItemVariant::default(), - toggleable: Toggleable::Toggleable(ToggleState::Toggled), + toggleable: Toggleable::NotToggleable, } } @@ -105,7 +105,6 @@ impl ListHeader { }; h_stack() - .flex_1() .w_full() .bg(cx.theme().colors().surface) // TODO: Add focus state @@ -560,7 +559,7 @@ impl ListSeparator { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - div().h_px().w_full().bg(cx.theme().colors().border) + div().h_px().w_full().bg(cx.theme().colors().border_variant) } } @@ -602,9 +601,9 @@ impl List { let is_toggled = Toggleable::is_toggled(&self.toggleable); let list_content = match (self.items.is_empty(), is_toggled) { - (_, false) => div(), (false, _) => div().children(self.items), - (true, _) => { + (true, false) => div(), + (true, true) => { div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted)) } }; diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index c102a2cf57..367e0d0ba6 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,5 +1,9 @@ -use crate::{prelude::*, static_new_notification_items, Icon, ListHeaderMeta}; -use crate::{List, ListHeader}; +use crate::{ + h_stack, prelude::*, static_new_notification_items, v_stack, Avatar, Button, Icon, IconButton, + IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, Stack, + UnreadIndicator, +}; +use crate::{ClickHandler, ListHeader}; #[derive(Component)] pub struct NotificationsPanel { @@ -16,33 +20,367 @@ impl NotificationsPanel { .id(self.id.clone()) .flex() .flex_col() - .w_full() - .h_full() + .size_full() .bg(cx.theme().colors().surface) .child( - div() - .id("header") - .w_full() - .flex() - .flex_col() + ListHeader::new("Notifications").meta(Some(ListHeaderMeta::Tools(vec![ + Icon::AtSign, + Icon::BellOff, + Icon::MailOpen, + ]))), + ) + .child(ListSeparator::new()) + .child( + v_stack() + .id("notifications-panel-scroll-view") + .py_1() .overflow_y_scroll() + .flex_1() .child( - List::new(static_new_notification_items()) - .toggle(ToggleState::Toggled) - .header( - ListHeader::new("Notifications") - .toggle(ToggleState::Toggled) - .meta(Some(ListHeaderMeta::Tools(vec![ - Icon::AtSign, - Icon::BellOff, - Icon::MailOpen, - ]))), + div() + .mx_2() + .p_1() + // TODO: Add cursor style + // .cursor(Cursor::IBeam) + .bg(cx.theme().colors().element) + .border() + .border_color(cx.theme().colors().border_variant) + .child( + Label::new("Search...") + .color(LabelColor::Placeholder) + .line_height_style(LineHeightStyle::UILabel), + ), + ) + .children(static_new_notification_items()), + ) + } +} + +pub enum NotificationItem { + Message(Notification), + // WithEdgeHeader(Notification), + WithRequiredActions(NotificationWithActions), +} + +pub enum ButtonOrIconButton { + Button(Button), + IconButton(IconButton), +} + +pub struct NotificationAction { + button: ButtonOrIconButton, + tooltip: SharedString, + /// Shows after action is chosen + /// + /// For example, if the action is "Accept" the taken message could be: + /// + /// - `(None,"Accepted")` - "Accepted" + /// + /// - `(Some(Icon::Check),"Accepted")` - ✓ "Accepted" + taken_message: (Option, SharedString), +} + +pub struct NotificationWithActions { + notification: Notification, + actions: [NotificationAction; 2], +} + +/// Represents a person with a Zed account's public profile. +/// All data in this struct should be considered public. +pub struct PublicActor { + username: SharedString, + avatar: SharedString, + is_contact: bool, +} + +pub enum ActorOrIcon { + Actor(PublicActor), + Icon(Icon), +} + +pub struct NotificationMeta { + items: Vec<(Option, SharedString, Option>)>, +} + +struct NotificationHandlers { + click: Option>, +} + +impl Default for NotificationHandlers { + fn default() -> Self { + Self { click: None } + } +} + +#[derive(Component)] +pub struct Notification { + id: ElementId, + slot: ActorOrIcon, + message: SharedString, + date_received: NaiveDateTime, + meta: Option>, + actions: Option<[NotificationAction; 2]>, + unread: bool, + new: bool, + action_taken: Option>, + handlers: NotificationHandlers, +} + +impl Notification { + fn new( + id: ElementId, + message: SharedString, + slot: ActorOrIcon, + click_action: Option>, + ) -> Self { + let handlers = if click_action.is_some() { + NotificationHandlers { + click: click_action, + } + } else { + NotificationHandlers::default() + }; + + Self { + id, + date_received: DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z") + .unwrap() + .naive_local(), + message, + meta: None, + slot, + actions: None, + unread: true, + new: false, + action_taken: None, + handlers, + } + } + + /// Creates a new notification with an actor slot. + /// + /// Requires a click action. + pub fn new_actor_message( + id: impl Into, + message: SharedString, + actor: PublicActor, + click_action: ClickHandler, + ) -> Self { + Self::new( + id.into(), + message, + ActorOrIcon::Actor(actor), + Some(click_action), + ) + } + + /// Creates a new notification with an icon slot. + /// + /// Requires a click action. + pub fn new_icon_message( + id: impl Into, + message: SharedString, + icon: Icon, + click_action: ClickHandler, + ) -> Self { + Self::new( + id.into(), + message, + ActorOrIcon::Icon(icon), + Some(click_action), + ) + } + + /// Creates a new notification with an actor slot + /// and a Call To Action row. + /// + /// Cannot take a click action due to required actions. + pub fn new_actor_with_actions( + id: impl Into, + message: SharedString, + actor: PublicActor, + click_action: ClickHandler, + actions: [NotificationAction; 2], + ) -> Self { + Self::new(id.into(), message, ActorOrIcon::Actor(actor), None).actions(actions) + } + + /// Creates a new notification with an icon slot + /// and a Call To Action row. + /// + /// Cannot take a click action due to required actions. + pub fn new_icon_with_actions( + id: impl Into, + message: SharedString, + icon: Icon, + click_action: ClickHandler, + actions: [NotificationAction; 2], + ) -> Self { + Self::new(id.into(), message, ActorOrIcon::Icon(icon), None).actions(actions) + } + + fn on_click(mut self, handler: ClickHandler) -> Self { + self.handlers.click = Some(handler); + self + } + + pub fn actions(mut self, actions: [NotificationAction; 2]) -> Self { + self.actions = Some(actions); + self + } + + pub fn meta(mut self, meta: NotificationMeta) -> Self { + self.meta = Some(meta); + self + } + + fn render_meta_items(&self, cx: &mut ViewContext) -> impl Component { + if let Some(meta) = &self.meta { + h_stack().children( + meta.items + .iter() + .map(|(icon, text, _)| { + let mut meta_el = div(); + if let Some(icon) = icon { + meta_el = meta_el.child(IconElement::new(icon.clone())); + } + meta_el.child(Label::new(text.clone()).color(LabelColor::Muted)) + }) + .collect::>(), + ) + } else { + div() + } + } + + fn render_actions(&self, cx: &mut ViewContext) -> impl Component { + // match (&self.actions, &self.action_taken) { + // // Show nothing + // (None, _) => div(), + // // Show the taken_message + // (Some(_), Some(action_taken)) => h_stack() + // .children( + // action_taken + // .taken_message + // .0 + // .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), + // ) + // .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), + // // Show the actions + // (Some(actions), None) => h_stack() + // .children(actions.iter().map(actiona.ction.tton Component::render(button),Component::render(icon_button))), + // })) + // .collect::>(), + } + + // if let Some(actions) = &self.actions { + // let action_children = actions + // .iter() + // .map(|action| match &action.button { + // ButtonOrIconButton::Button(button) => { + // div().class("action_button").child(button.label.clone()) + // } + // ButtonOrIconButton::IconButton(icon_button) => div() + // .class("action_icon_button") + // .child(icon_button.icon.to_string()), + // }) + // .collect::>(); + + // el = el.child(h_stack().children(action_children)); + // } else { + // el = el.child(h_stack().child(div())); + // } + } + + fn render_slot(&self, cx: &mut ViewContext) -> impl Component { + match &self.slot { + ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(), + ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(), + } + } + + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + div() + .relative() + .id(self.id.clone()) + .children( + Some( + div() + .absolute() + .left(px(3.0)) + .top_3() + .child(UnreadIndicator::new()), + ) + .filter(|_| self.unread), + ) + .child( + v_stack() + .gap_1() + .child( + h_stack() + .gap_2() + .child(self.render_slot(cx)) + .child(div().flex_1().child(Label::new(self.message.clone()))), + ) + .child( + h_stack() + .justify_between() + .child( + h_stack() + .gap_1() + .child( + Label::new( + self.date_received.format("%m/%d/%Y").to_string(), + ) + .color(LabelColor::Muted), + ) + .child(self.render_meta_items(cx)), + ) + .child( + + match (self.actions, self.action_taken) { + // Show nothing + (None, _) => div(), + // Show the taken_message + (Some(_), Some(action_taken)) => h_stack() + .children( + action_taken + .taken_message + .0 + .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), + ) + .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), + // Show the actions + (Some(actions), None) => h_stack() + + } + + // match (&self.actions, &self.action_taken) { + // // Show nothing + // (None, _) => div(), + // // Show the taken_message + // (Some(_), Some(action_taken)) => h_stack() + // .children( + // action_taken + // .taken_message + // .0 + // .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), + // ) + // .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), + // // Show the actions + // (Some(actions), None) => h_stack() + // .children(actions.iter().map(actiona.ction.tton Component::render(button),Component::render(icon_button))), + // })) + // .collect::>(), + ), ), ) } } +use chrono::{DateTime, NaiveDateTime}; +use gpui2::{px, Styled}; #[cfg(feature = "stories")] pub use stories::*; diff --git a/crates/ui2/src/elements.rs b/crates/ui2/src/elements.rs index c60902ae98..dfff2761a7 100644 --- a/crates/ui2/src/elements.rs +++ b/crates/ui2/src/elements.rs @@ -2,6 +2,7 @@ mod avatar; mod button; mod details; mod icon; +mod indicator; mod input; mod label; mod player; @@ -12,6 +13,7 @@ pub use avatar::*; pub use button::*; pub use details::*; pub use icon::*; +pub use indicator::*; pub use input::*; pub use label::*; pub use player::*; diff --git a/crates/ui2/src/elements/indicator.rs b/crates/ui2/src/elements/indicator.rs new file mode 100644 index 0000000000..86c83a1bf1 --- /dev/null +++ b/crates/ui2/src/elements/indicator.rs @@ -0,0 +1,22 @@ +use gpui2::px; + +use crate::prelude::*; + +#[derive(Component)] +pub struct UnreadIndicator; + +impl UnreadIndicator { + pub fn new() -> Self { + Self + } + + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + div() + .border_2() + .border_color(cx.theme().colors().surface) + .w(px(9.0)) + .h(px(9.0)) + .z_index(2) + .bg(cx.theme().status().info) + } +} diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index ebbc89832c..dc724ec5c4 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -8,8 +8,8 @@ use theme2::ActiveTheme; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListHeaderMeta, - ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, - PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, + ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, NotificationItem, PaletteItem, + Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, }; use crate::{HighlightedText, ListDetailsEntry}; @@ -326,6 +326,9 @@ pub fn static_players_with_call_status() -> Vec { ] } +pub fn static_new_notification_items_2() -> Vec> { + vec![] +} pub fn static_new_notification_items() -> Vec> { vec![ ListItem::Header(ListSubHeader::new("New")), @@ -351,6 +354,52 @@ pub fn static_new_notification_items() -> Vec> { ListItem::Details(ListDetailsEntry::new( "as-cii accepted your contact request.", )), + ListItem::Details( + ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true), + ), + ListItem::Details(ListDetailsEntry::new( + "osiewicz accepted your contact request.", + )), + ListItem::Details(ListDetailsEntry::new( + "ConradIrwin accepted your contact request.", + )), + ListItem::Details( + ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.") + .seen(true) + .meta("This stream has ended."), + ), + ListItem::Details(ListDetailsEntry::new( + "nathansobo accepted your contact request.", + )), + ListItem::Header(ListSubHeader::new("Earlier")), + ListItem::Details( + ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ + Button::new("Decline"), + Button::new("Accept").variant(crate::ButtonVariant::Filled), + ]), + ), + ListItem::Details( + ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") + .seen(true) + .meta("This stream has ended."), + ), + ListItem::Details(ListDetailsEntry::new( + "as-cii accepted your contact request.", + )), + ListItem::Details( + ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true), + ), + ListItem::Details(ListDetailsEntry::new( + "osiewicz accepted your contact request.", + )), + ListItem::Details(ListDetailsEntry::new( + "ConradIrwin accepted your contact request.", + )), + ListItem::Details( + ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.") + .seen(true) + .meta("This stream has ended."), + ), ] .into_iter() .map(From::from) From cd10ba9e063f6dde891f5c97bbe2552b2413f445 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 14:17:10 -0600 Subject: [PATCH 078/156] Use run_until_parked instead of blocked in tests --- crates/gpui2/src/executor.rs | 17 +++++++++++++++-- crates/gpui2/src/test.rs | 2 +- crates/gpui2_macros/src/test.rs | 2 +- crates/project2/src/project_tests.rs | 12 ------------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index c25eeac899..4b75bad504 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -86,7 +86,20 @@ impl BackgroundExecutor { Task::Spawned(task) } + #[cfg(any(test, feature = "test-support"))] + pub fn block_test(&self, future: impl Future) -> R { + self.block_internal(false, future) + } + pub fn block(&self, future: impl Future) -> R { + self.block_internal(true, future) + } + + pub(crate) fn block_internal( + &self, + background_only: bool, + future: impl Future, + ) -> R { pin_mut!(future); let (parker, unparker) = parking::pair(); let awoken = Arc::new(AtomicBool::new(false)); @@ -102,7 +115,7 @@ impl BackgroundExecutor { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, Poll::Pending => { - if !self.dispatcher.poll(true) { + if !self.dispatcher.poll(background_only) { if awoken.swap(false, SeqCst) { continue; } @@ -184,7 +197,7 @@ impl BackgroundExecutor { #[cfg(any(test, feature = "test-support"))] pub fn simulate_random_delay(&self) -> impl Future { - self.spawn(self.dispatcher.as_test().unwrap().simulate_random_delay()) + self.dispatcher.as_test().unwrap().simulate_random_delay() } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/gpui2/src/test.rs b/crates/gpui2/src/test.rs index 3f2697f7e3..61c70813cd 100644 --- a/crates/gpui2/src/test.rs +++ b/crates/gpui2/src/test.rs @@ -28,7 +28,7 @@ pub fn run_test( } let result = panic::catch_unwind(|| { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed)); - test_fn(dispatcher, seed); + test_fn(dispatcher.clone(), seed); }); match result { diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index 7fb499f9f1..e01f39b0be 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -136,7 +136,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { &mut |dispatcher, _seed| { let executor = gpui2::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone())); #cx_vars - executor.block(#inner_fn_name(#inner_fn_args)); + executor.block_test(#inner_fn_name(#inner_fn_args)); #cx_teardowns }, #on_failure_fn_name, diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index fd8c195101..80126d82e4 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -2942,7 +2942,6 @@ async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) { #[gpui2::test] async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { init_test(cx); - dbg!("GAH"); let fs = FakeFs::new(cx.executor().clone()); fs.insert_tree( @@ -2954,7 +2953,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { }), ) .await; - dbg!("NOOP"); let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; @@ -2964,8 +2962,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .unwrap(); let events = Arc::new(Mutex::new(Vec::new())); - dbg!("BOOP"); - // initially, the buffer isn't dirty. buffer1.update(cx, |buffer, cx| { cx.subscribe(&buffer1, { @@ -2982,7 +2978,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { buffer.edit([(1..2, "")], None, cx); }); - dbg!("ADSASD"); // after the first edit, the buffer is dirty, and emits a dirtied event. buffer1.update(cx, |buffer, cx| { @@ -3000,7 +2995,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { cx, ); }); - dbg!("1111"); // after saving, the buffer is not dirty, and emits a saved event. buffer1.update(cx, |buffer, cx| { @@ -3012,8 +3006,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { buffer.edit([(2..2, "D")], None, cx); }); - dbg!("5555555"); - // after editing again, the buffer is dirty, and emits another dirty event. buffer1.update(cx, |buffer, cx| { assert!(buffer.text() == "aBDc"); @@ -3035,7 +3027,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { assert!(!buffer.is_dirty()); }); - dbg!("666666"); assert_eq!( *events.lock(), &[language2::Event::Edited, language2::Event::DirtyChanged] @@ -3055,8 +3046,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .detach(); }); - dbg!("0000000"); - fs.remove_file("/dir/file2".as_ref(), Default::default()) .await .unwrap(); @@ -3084,7 +3073,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) { .detach(); }); - dbg!(";;;;;;"); buffer3.update(cx, |buffer, cx| { buffer.edit([(0..0, "x")], None, cx); }); From be3cc6458c27d341e284af183239db4075f49262 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 16:34:42 -0400 Subject: [PATCH 079/156] Implement Notifications Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> --- .../ui2/src/components/notifications_panel.rs | 153 +++++++----------- crates/ui2/src/elements/icon.rs | 2 + crates/ui2/src/prelude.rs | 18 +++ crates/ui2/src/static_data.rs | 37 ++++- 4 files changed, 110 insertions(+), 100 deletions(-) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 367e0d0ba6..6bcc9574c9 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,6 +1,6 @@ use crate::{ h_stack, prelude::*, static_new_notification_items, v_stack, Avatar, Button, Icon, IconButton, - IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, Stack, + IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, UnreadIndicator, }; use crate::{ClickHandler, ListHeader}; @@ -67,6 +67,18 @@ pub enum ButtonOrIconButton { IconButton(IconButton), } +impl From> for ButtonOrIconButton { + fn from(value: Button) -> Self { + Self::Button(value) + } +} + +impl From> for ButtonOrIconButton { + fn from(value: IconButton) -> Self { + Self::IconButton(value) + } +} + pub struct NotificationAction { button: ButtonOrIconButton, tooltip: SharedString, @@ -80,19 +92,25 @@ pub struct NotificationAction { taken_message: (Option, SharedString), } +impl NotificationAction { + pub fn new( + button: impl Into>, + tooltip: impl Into, + (icon, taken_message): (Option, impl Into), + ) -> Self { + Self { + button: button.into(), + tooltip: tooltip.into(), + taken_message: (icon, taken_message.into()), + } + } +} + pub struct NotificationWithActions { notification: Notification, actions: [NotificationAction; 2], } -/// Represents a person with a Zed account's public profile. -/// All data in this struct should be considered public. -pub struct PublicActor { - username: SharedString, - avatar: SharedString, - is_contact: bool, -} - pub enum ActorOrIcon { Actor(PublicActor), Icon(Icon), @@ -162,13 +180,13 @@ impl Notification { /// Requires a click action. pub fn new_actor_message( id: impl Into, - message: SharedString, + message: impl Into, actor: PublicActor, click_action: ClickHandler, ) -> Self { Self::new( id.into(), - message, + message.into(), ActorOrIcon::Actor(actor), Some(click_action), ) @@ -179,13 +197,13 @@ impl Notification { /// Requires a click action. pub fn new_icon_message( id: impl Into, - message: SharedString, + message: impl Into, icon: Icon, click_action: ClickHandler, ) -> Self { Self::new( id.into(), - message, + message.into(), ActorOrIcon::Icon(icon), Some(click_action), ) @@ -197,12 +215,11 @@ impl Notification { /// Cannot take a click action due to required actions. pub fn new_actor_with_actions( id: impl Into, - message: SharedString, + message: impl Into, actor: PublicActor, - click_action: ClickHandler, actions: [NotificationAction; 2], ) -> Self { - Self::new(id.into(), message, ActorOrIcon::Actor(actor), None).actions(actions) + Self::new(id.into(), message.into(), ActorOrIcon::Actor(actor), None).actions(actions) } /// Creates a new notification with an icon slot @@ -211,12 +228,11 @@ impl Notification { /// Cannot take a click action due to required actions. pub fn new_icon_with_actions( id: impl Into, - message: SharedString, + message: impl Into, icon: Icon, - click_action: ClickHandler, actions: [NotificationAction; 2], ) -> Self { - Self::new(id.into(), message, ActorOrIcon::Icon(icon), None).actions(actions) + Self::new(id.into(), message.into(), ActorOrIcon::Icon(icon), None).actions(actions) } fn on_click(mut self, handler: ClickHandler) -> Self { @@ -253,45 +269,6 @@ impl Notification { } } - fn render_actions(&self, cx: &mut ViewContext) -> impl Component { - // match (&self.actions, &self.action_taken) { - // // Show nothing - // (None, _) => div(), - // // Show the taken_message - // (Some(_), Some(action_taken)) => h_stack() - // .children( - // action_taken - // .taken_message - // .0 - // .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), - // ) - // .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), - // // Show the actions - // (Some(actions), None) => h_stack() - // .children(actions.iter().map(actiona.ction.tton Component::render(button),Component::render(icon_button))), - // })) - // .collect::>(), - } - - // if let Some(actions) = &self.actions { - // let action_children = actions - // .iter() - // .map(|action| match &action.button { - // ButtonOrIconButton::Button(button) => { - // div().class("action_button").child(button.label.clone()) - // } - // ButtonOrIconButton::IconButton(icon_button) => div() - // .class("action_icon_button") - // .child(icon_button.icon.to_string()), - // }) - // .collect::>(); - - // el = el.child(h_stack().children(action_children)); - // } else { - // el = el.child(h_stack().child(div())); - // } - } - fn render_slot(&self, cx: &mut ViewContext) -> impl Component { match &self.slot { ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(), @@ -336,44 +313,30 @@ impl Notification { ) .child(self.render_meta_items(cx)), ) - .child( - - match (self.actions, self.action_taken) { - // Show nothing - (None, _) => div(), - // Show the taken_message - (Some(_), Some(action_taken)) => h_stack() - .children( - action_taken - .taken_message - .0 - .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), - ) - .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), - // Show the actions - (Some(actions), None) => h_stack() - + .child(match (self.actions, self.action_taken) { + // Show nothing + (None, _) => div(), + // Show the taken_message + (Some(_), Some(action_taken)) => h_stack() + .children(action_taken.taken_message.0.map(|icon| { + IconElement::new(icon).color(crate::IconColor::Muted) + })) + .child( + Label::new(action_taken.taken_message.1.clone()) + .color(LabelColor::Muted), + ), + // Show the actions + (Some(actions), None) => { + h_stack().children(actions.map(|action| match action.button { + ButtonOrIconButton::Button(button) => { + Component::render(button) + } + ButtonOrIconButton::IconButton(icon_button) => { + Component::render(icon_button) + } + })) } - - // match (&self.actions, &self.action_taken) { - // // Show nothing - // (None, _) => div(), - // // Show the taken_message - // (Some(_), Some(action_taken)) => h_stack() - // .children( - // action_taken - // .taken_message - // .0 - // .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), - // ) - // .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), - // // Show the actions - // (Some(actions), None) => h_stack() - // .children(actions.iter().map(actiona.ction.tton Component::render(button),Component::render(icon_button))), - // })) - // .collect::>(), - - ), + }), ), ) } diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index eef80cb5ad..2038e5a000 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -49,6 +49,7 @@ pub enum Icon { AudioOff, AudioOn, Bolt, + Check, ChevronDown, ChevronLeft, ChevronRight, @@ -105,6 +106,7 @@ impl Icon { Icon::AudioOff => "icons/speaker-off.svg", Icon::AudioOn => "icons/speaker-loud.svg", Icon::Bolt => "icons/bolt.svg", + Icon::Check => "icons/check.svg", Icon::ChevronDown => "icons/chevron_down.svg", Icon::ChevronLeft => "icons/chevron_left.svg", Icon::ChevronRight => "icons/chevron_right.svg", diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index b424ce6123..53adc8c0f9 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -19,6 +19,24 @@ pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems { rems(*settings.ui_scale * UI_SCALE_RATIO * size) } +/// Represents a person with a Zed account's public profile. +/// All data in this struct should be considered public. +pub struct PublicActor { + pub username: SharedString, + pub avatar: SharedString, + pub is_contact: bool, +} + +impl PublicActor { + pub fn new(username: impl Into, avatar: impl Into) -> Self { + Self { + username: username.into(), + avatar: avatar.into(), + is_contact: false, + } + } +} + #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] pub enum FileSystemStatus { #[default] diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index dc724ec5c4..7e206f0cf6 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; use std::str::FromStr; +use std::sync::Arc; use gpui2::{AppContext, ViewContext}; use rand::Rng; @@ -7,12 +8,13 @@ use theme2::ActiveTheme; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, - HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListHeaderMeta, - ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, NotificationItem, PaletteItem, - Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, - VideoStatus, + HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader, + Livestream, MicStatus, ModifierKeys, Notification, NotificationItem, PaletteItem, Player, + PlayerCallStatus, PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, + ToggleState, VideoStatus, }; use crate::{HighlightedText, ListDetailsEntry}; +use crate::{ListItem, NotificationAction}; pub fn static_tabs_example() -> Vec { vec![ @@ -327,8 +329,33 @@ pub fn static_players_with_call_status() -> Vec { } pub fn static_new_notification_items_2() -> Vec> { - vec![] + vec![ + NotificationItem::Message(Notification::new_icon_message( + "notif-1", + "You were mentioned in a note.", + Icon::AtSign, + Arc::new(|_, _| {}), + )), + NotificationItem::Message(Notification::new_actor_with_actions( + "notif-2", + "as-cii sent you a contact request.", + PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + [ + NotificationAction::new( + Button::new("Decline"), + "Decline Request", + (Some(Icon::XCircle), "Declined"), + ), + NotificationAction::new( + Button::new("Accept").variant(crate::ButtonVariant::Filled), + "Accept Request", + (Some(Icon::Check), "Accepted"), + ), + ], + )), + ] } + pub fn static_new_notification_items() -> Vec> { vec![ ListItem::Header(ListSubHeader::new("New")), From 229ba0744e2d736ca0c6012178d7743a47327181 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 16:50:59 -0400 Subject: [PATCH 080/156] Add additional notifications and style tweaks Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> --- .../ui2/src/components/notifications_panel.rs | 25 ++-- crates/ui2/src/elements/indicator.rs | 1 + crates/ui2/src/static_data.rs | 107 ++++++++++++++++-- 3 files changed, 111 insertions(+), 22 deletions(-) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 6bcc9574c9..c53b60f213 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,6 +1,6 @@ use crate::{ - h_stack, prelude::*, static_new_notification_items, v_stack, Avatar, Button, Icon, IconButton, - IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, + h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon, + IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, UnreadIndicator, }; use crate::{ClickHandler, ListHeader}; @@ -51,17 +51,11 @@ impl NotificationsPanel { .line_height_style(LineHeightStyle::UILabel), ), ) - .children(static_new_notification_items()), + .child(v_stack().px_1().children(static_new_notification_items_2())), ) } } -pub enum NotificationItem { - Message(Notification), - // WithEdgeHeader(Notification), - WithRequiredActions(NotificationWithActions), -} - pub enum ButtonOrIconButton { Button(Button), IconButton(IconButton), @@ -106,11 +100,6 @@ impl NotificationAction { } } -pub struct NotificationWithActions { - notification: Notification, - actions: [NotificationAction; 2], -} - pub enum ActorOrIcon { Actor(PublicActor), Icon(Icon), @@ -280,21 +269,29 @@ impl Notification { div() .relative() .id(self.id.clone()) + .p_1() + .flex() + .flex_col() + .w_full() .children( Some( div() .absolute() .left(px(3.0)) .top_3() + .z_index(2) .child(UnreadIndicator::new()), ) .filter(|_| self.unread), ) .child( v_stack() + .z_index(1) .gap_1() + .w_full() .child( h_stack() + .w_full() .gap_2() .child(self.render_slot(cx)) .child(div().flex_1().child(Label::new(self.message.clone()))), diff --git a/crates/ui2/src/elements/indicator.rs b/crates/ui2/src/elements/indicator.rs index 86c83a1bf1..1f6e00e621 100644 --- a/crates/ui2/src/elements/indicator.rs +++ b/crates/ui2/src/elements/indicator.rs @@ -12,6 +12,7 @@ impl UnreadIndicator { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { div() + .rounded_full() .border_2() .border_color(cx.theme().colors().surface) .w(px(9.0)) diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 7e206f0cf6..45e4dfa423 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -9,9 +9,8 @@ use theme2::ActiveTheme; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader, - Livestream, MicStatus, ModifierKeys, Notification, NotificationItem, PaletteItem, Player, - PlayerCallStatus, PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, - ToggleState, VideoStatus, + Livestream, MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus, + PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, }; use crate::{HighlightedText, ListDetailsEntry}; use crate::{ListItem, NotificationAction}; @@ -328,15 +327,15 @@ pub fn static_players_with_call_status() -> Vec { ] } -pub fn static_new_notification_items_2() -> Vec> { +pub fn static_new_notification_items_2() -> Vec> { vec![ - NotificationItem::Message(Notification::new_icon_message( + Notification::new_icon_message( "notif-1", "You were mentioned in a note.", Icon::AtSign, Arc::new(|_, _| {}), - )), - NotificationItem::Message(Notification::new_actor_with_actions( + ), + Notification::new_actor_with_actions( "notif-2", "as-cii sent you a contact request.", PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), @@ -352,7 +351,99 @@ pub fn static_new_notification_items_2() -> Vec> (Some(Icon::Check), "Accepted"), ), ], - )), + ), + Notification::new_icon_message( + "notif-3", + "You were mentioned #design.", + Icon::MessageBubbles, + Arc::new(|_, _| {}), + ), + Notification::new_actor_with_actions( + "notif-4", + "as-cii sent you a contact request.", + PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + [ + NotificationAction::new( + Button::new("Decline"), + "Decline Request", + (Some(Icon::XCircle), "Declined"), + ), + NotificationAction::new( + Button::new("Accept").variant(crate::ButtonVariant::Filled), + "Accept Request", + (Some(Icon::Check), "Accepted"), + ), + ], + ), + Notification::new_icon_message( + "notif-5", + "You were mentioned in a note.", + Icon::AtSign, + Arc::new(|_, _| {}), + ), + Notification::new_actor_with_actions( + "notif-6", + "as-cii sent you a contact request.", + PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + [ + NotificationAction::new( + Button::new("Decline"), + "Decline Request", + (Some(Icon::XCircle), "Declined"), + ), + NotificationAction::new( + Button::new("Accept").variant(crate::ButtonVariant::Filled), + "Accept Request", + (Some(Icon::Check), "Accepted"), + ), + ], + ), + Notification::new_icon_message( + "notif-7", + "You were mentioned in a note.", + Icon::AtSign, + Arc::new(|_, _| {}), + ), + Notification::new_actor_with_actions( + "notif-8", + "as-cii sent you a contact request.", + PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + [ + NotificationAction::new( + Button::new("Decline"), + "Decline Request", + (Some(Icon::XCircle), "Declined"), + ), + NotificationAction::new( + Button::new("Accept").variant(crate::ButtonVariant::Filled), + "Accept Request", + (Some(Icon::Check), "Accepted"), + ), + ], + ), + Notification::new_icon_message( + "notif-9", + "You were mentioned in a note.", + Icon::AtSign, + Arc::new(|_, _| {}), + ), + Notification::new_actor_with_actions( + "notif-10", + "as-cii sent you a contact request.", + PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + [ + NotificationAction::new( + Button::new("Decline"), + "Decline Request", + (Some(Icon::XCircle), "Declined"), + ), + NotificationAction::new( + Button::new("Accept").variant(crate::ButtonVariant::Filled), + "Accept Request", + (Some(Icon::Check), "Accepted"), + ), + ], + ), ] } From 90facc051a2e48b42a8a9e0fe973f87464c82588 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 1 Nov 2023 15:31:37 -0600 Subject: [PATCH 081/156] beautiful diff --- crates/gpui2/src/executor.rs | 16 +- crates/project2/src/project2.rs | 5 + crates/project2/src/project_tests.rs | 953 ++++++++++++++------------- crates/project2/src/worktree.rs | 4 + crates/rpc2/src/peer.rs | 3 +- 5 files changed, 507 insertions(+), 474 deletions(-) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 4b75bad504..63f3b94c79 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -88,7 +88,16 @@ impl BackgroundExecutor { #[cfg(any(test, feature = "test-support"))] pub fn block_test(&self, future: impl Future) -> R { - self.block_internal(false, future) + let (runnable, task) = unsafe { + async_task::spawn_unchecked(future, { + let dispatcher = self.dispatcher.clone(); + move |runnable| dispatcher.dispatch_on_main_thread(runnable) + }) + }; + + runnable.schedule(); + + self.block_internal(false, task) } pub fn block(&self, future: impl Future) -> R { @@ -100,17 +109,20 @@ impl BackgroundExecutor { background_only: bool, future: impl Future, ) -> R { + dbg!("block_internal"); pin_mut!(future); let (parker, unparker) = parking::pair(); let awoken = Arc::new(AtomicBool::new(false)); let awoken2 = awoken.clone(); let waker = waker_fn(move || { + dbg!("WAKING UP."); awoken2.store(true, SeqCst); unparker.unpark(); }); let mut cx = std::task::Context::from_waker(&waker); + dbg!("BOOOP"); loop { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, @@ -131,7 +143,9 @@ impl BackgroundExecutor { panic!("parked with nothing left to run\n{:?}", backtrace_message) } } + dbg!("PARKING!"); parker.park(); + dbg!("CONTINUING!"); } } } diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 1457bd41cc..a598aac79e 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -877,14 +877,17 @@ impl Project { ) }); for path in root_paths { + dbg!(&path); let (tree, _) = project .update(cx, |project, cx| { project.find_or_create_local_worktree(path, true, cx) }) .await .unwrap(); + dbg!("aaa"); tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete()) .await; + dbg!("bbb"); } project } @@ -5990,8 +5993,10 @@ impl Project { ) -> Task, PathBuf)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) { + dbg!("shortcut"); Task::ready(Ok((tree, relative_path))) } else { + dbg!("long cut"); let worktree = self.create_local_worktree(abs_path, visible, cx); cx.background_executor() .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 80126d82e4..fba2548451 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -4,55 +4,63 @@ use futures::{future, StreamExt}; use gpui2::AppContext; use language2::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, - tree_sitter_rust, Diagnostic, FakeLspAdapter, LanguageConfig, LineEnding, OffsetRangeExt, - Point, ToPoint, + tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, + LineEnding, OffsetRangeExt, Point, ToPoint, }; use lsp2::Url; use parking_lot::Mutex; use pretty_assertions::assert_eq; use serde_json::json; -use std::task::Poll; +use std::{os, task::Poll}; use unindent::Unindent as _; -use util::assert_set_eq; +use util::{assert_set_eq, test::temp_tree}; -// #[gpui2::test] -// async fn test_symlinks(cx: &mut gpui2::TestAppContext) { -// init_test(cx); -// cx.executor().allow_parking(); +#[gpui2::test] +async fn test_symlinks(cx: &mut gpui2::TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); -// let dir = temp_tree(json!({ -// "root": { -// "apple": "", -// "banana": { -// "carrot": { -// "date": "", -// "endive": "", -// } -// }, -// "fennel": { -// "grape": "", -// } -// } -// })); + let dir = temp_tree(json!({ + "root": { + "apple": "", + "banana": { + "carrot": { + "date": "", + "endive": "", + } + }, + "fennel": { + "grape": "", + } + } + })); -// let root_link_path = dir.path().join("root_link"); -// unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); -// unix::fs::symlink( -// &dir.path().join("root/fennel"), -// &dir.path().join("root/finnochio"), -// ) -// .unwrap(); + dbg!("GOT HERE"); -// let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; -// project.update(cx, |project, cx| { -// let tree = project.worktrees().next().unwrap().read(cx); -// assert_eq!(tree.file_count(), 5); -// assert_eq!( -// tree.inode_for_path("fennel/grape"), -// tree.inode_for_path("finnochio/grape") -// ); -// }); -// } + let root_link_path = dir.path().join("root_link"); + os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); + os::unix::fs::symlink( + &dir.path().join("root/fennel"), + &dir.path().join("root/finnochio"), + ) + .unwrap(); + + dbg!("GOT HERE 2"); + + let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; + + dbg!("GOT HERE 2.5"); + project.update(cx, |project, cx| { + let tree = project.worktrees().next().unwrap().read(cx); + assert_eq!(tree.file_count(), 5); + assert_eq!( + tree.inode_for_path("fennel/grape"), + tree.inode_for_path("finnochio/grape") + ); + }); + + dbg!("GOT HERE 3"); +} #[gpui2::test] async fn test_managing_project_specific_settings(cx: &mut gpui2::TestAppContext) { @@ -2058,121 +2066,121 @@ 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) { -// init_test(cx); +#[gpui2::test] +async fn test_invalid_edits_from_lsp2(cx: &mut gpui2::TestAppContext) { + init_test(cx); -// let text = " -// use a::b; -// use a::c; + let text = " + use a::b; + use a::c; -// fn f() { -// b(); -// c(); -// } -// " -// .unindent(); + fn f() { + b(); + c(); + } + " + .unindent(); -// let fs = FakeFs::new(cx.executor().clone()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.rs": text.clone(), -// }), -// ) -// .await; + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// let buffer = project -// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) -// .await -// .unwrap(); + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); -// // Simulate the language server sending us edits in a non-ordered fashion, -// // with ranges sometimes being inverted or pointing to invalid locations. -// let edits = project -// .update(cx, |project, cx| { -// project.edits_from_lsp( -// &buffer, -// [ -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(0, 9), -// lsp2::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), -// ), -// new_text: "a::{b, c}".into(), -// }, -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(1, 0), -// lsp2::Position::new(99, 0), -// ), -// new_text: "".into(), -// }, -// lsp2::TextEdit { -// range: lsp2::Range::new( -// lsp2::Position::new(0, 9), -// lsp2::Position::new(0, 9), -// ), -// new_text: " -// fn f() { -// b(); -// c(); -// }" -// .unindent(), -// }, -// ], -// LanguageServerId(0), -// None, -// cx, -// ) -// }) -// .await -// .unwrap(); + // Simulate the language server sending us edits in a non-ordered fashion, + // with ranges sometimes being inverted or pointing to invalid locations. + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &buffer, + [ + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(0, 9), + lsp2::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), + ), + new_text: "a::{b, c}".into(), + }, + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(1, 0), + lsp2::Position::new(99, 0), + ), + new_text: "".into(), + }, + lsp2::TextEdit { + range: lsp2::Range::new( + lsp2::Position::new(0, 9), + lsp2::Position::new(0, 9), + ), + new_text: " + fn f() { + b(); + c(); + }" + .unindent(), + }, + ], + LanguageServerId(0), + None, + cx, + ) + }) + .await + .unwrap(); -// buffer.update(cx, |buffer, cx| { -// let edits = edits -// .into_iter() -// .map(|(range, text)| { -// ( -// range.start.to_point(buffer)..range.end.to_point(buffer), -// text, -// ) -// }) -// .collect::>(); + buffer.update(cx, |buffer, cx| { + let edits = edits + .into_iter() + .map(|(range, text)| { + ( + range.start.to_point(buffer)..range.end.to_point(buffer), + text, + ) + }) + .collect::>(); -// assert_eq!( -// edits, -// [ -// (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), -// (Point::new(1, 0)..Point::new(2, 0), "".into()) -// ] -// ); + assert_eq!( + edits, + [ + (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), + (Point::new(1, 0)..Point::new(2, 0), "".into()) + ] + ); -// for (range, new_text) in edits { -// buffer.edit([(range, new_text)], None, cx); -// } -// assert_eq!( -// buffer.text(), -// " -// use a::{b, c}; + for (range, new_text) in edits { + buffer.edit([(range, new_text)], None, cx); + } + assert_eq!( + buffer.text(), + " + use a::{b, c}; -// fn f() { -// b(); -// c(); -// } -// " -// .unindent() -// ); -// }); -// } + fn f() { + b(); + c(); + } + " + .unindent() + ); + }); +} fn chunks_with_diagnostics( buffer: &Buffer, @@ -2292,168 +2300,168 @@ async fn test_definition(cx: &mut gpui2::TestAppContext) { } } -// #[gpui2::test] -// async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { -// init_test(cx); +#[gpui2::test] +async fn test_completions_without_edit_ranges(cx: &mut gpui2::TestAppContext) { + init_test(cx); -// let mut language = Language::new( -// LanguageConfig { -// name: "TypeScript".into(), -// path_suffixes: vec!["ts".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_typescript()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp2::ServerCapabilities { -// completion_provider: Some(lsp2::CompletionOptions { -// trigger_characters: Some(vec![":".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + Some(tree_sitter_typescript::language_typescript()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp2::ServerCapabilities { + completion_provider: Some(lsp2::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; -// let fs = FakeFs::new(cx.executor().clone()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.ts": "", -// }), -// ) -// .await; + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "", + }), + ) + .await; -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) -// .await -// .unwrap(); + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); -// let fake_server = fake_language_servers.next().await.unwrap(); + let fake_server = fake_language_servers.next().await.unwrap(); -// let text = "let a = b.fqn"; -// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); -// let completions = project.update(cx, |project, cx| { -// project.completions(&buffer, text.len(), cx) -// }); + let text = "let a = b.fqn"; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len(), cx) + }); -// fake_server -// .handle_request::(|_, _| async move { -// Ok(Some(lsp2::CompletionResponse::Array(vec![ -// lsp2::CompletionItem { -// label: "fullyQualifiedName?".into(), -// insert_text: Some("fullyQualifiedName".into()), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await; -// let completions = completions.await.unwrap(); -// let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); -// assert_eq!(completions.len(), 1); -// assert_eq!(completions[0].new_text, "fullyQualifiedName"); -// assert_eq!( -// completions[0].old_range.to_offset(&snapshot), -// text.len() - 3..text.len() -// ); + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp2::CompletionResponse::Array(vec![ + lsp2::CompletionItem { + label: "fullyQualifiedName?".into(), + insert_text: Some("fullyQualifiedName".into()), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].new_text, "fullyQualifiedName"); + assert_eq!( + completions[0].old_range.to_offset(&snapshot), + text.len() - 3..text.len() + ); -// let text = "let a = \"atoms/cmp\""; -// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); -// let completions = project.update(cx, |project, cx| { -// project.completions(&buffer, text.len() - 1, cx) -// }); + let text = "let a = \"atoms/cmp\""; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len() - 1, cx) + }); -// fake_server -// .handle_request::(|_, _| async move { -// Ok(Some(lsp2::CompletionResponse::Array(vec![ -// lsp2::CompletionItem { -// label: "component".into(), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await; -// let completions = completions.await.unwrap(); -// let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); -// assert_eq!(completions.len(), 1); -// assert_eq!(completions[0].new_text, "component"); -// assert_eq!( -// completions[0].old_range.to_offset(&snapshot), -// text.len() - 4..text.len() - 1 -// ); -// } + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp2::CompletionResponse::Array(vec![ + lsp2::CompletionItem { + label: "component".into(), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].new_text, "component"); + assert_eq!( + completions[0].old_range.to_offset(&snapshot), + text.len() - 4..text.len() - 1 + ); +} -// #[gpui2::test] -// async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) { -// init_test(cx); +#[gpui2::test] +async fn test_completions_with_carriage_returns(cx: &mut gpui2::TestAppContext) { + init_test(cx); -// let mut language = Language::new( -// LanguageConfig { -// name: "TypeScript".into(), -// path_suffixes: vec!["ts".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_typescript()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp2::ServerCapabilities { -// completion_provider: Some(lsp2::CompletionOptions { -// trigger_characters: Some(vec![":".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + Some(tree_sitter_typescript::language_typescript()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp2::ServerCapabilities { + completion_provider: Some(lsp2::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; -// let fs = FakeFs::new(cx.executor().clone()); -// fs.insert_tree( -// "/dir", -// json!({ -// "a.ts": "", -// }), -// ) -// .await; + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "", + }), + ) + .await; -// let project = Project::test(fs, ["/dir".as_ref()], cx).await; -// project.update(cx, |project, _| project.languages.add(Arc::new(language))); -// let buffer = project -// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) -// .await -// .unwrap(); + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); -// let fake_server = fake_language_servers.next().await.unwrap(); + let fake_server = fake_language_servers.next().await.unwrap(); -// let text = "let a = b.fqn"; -// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); -// let completions = project.update(cx, |project, cx| { -// project.completions(&buffer, text.len(), cx) -// }); + let text = "let a = b.fqn"; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len(), cx) + }); -// fake_server -// .handle_request::(|_, _| async move { -// Ok(Some(lsp2::CompletionResponse::Array(vec![ -// lsp2::CompletionItem { -// label: "fullyQualifiedName?".into(), -// insert_text: Some("fully\rQualified\r\nName".into()), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await; -// let completions = completions.await.unwrap(); -// assert_eq!(completions.len(), 1); -// assert_eq!(completions[0].new_text, "fully\nQualified\nName"); -// } + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp2::CompletionResponse::Array(vec![ + lsp2::CompletionItem { + label: "fullyQualifiedName?".into(), + insert_text: Some("fully\rQualified\r\nName".into()), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + assert_eq!(completions.len(), 1); + 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) { @@ -2636,212 +2644,213 @@ 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) { -// init_test(cx); +#[gpui2::test] +async fn test_save_as(cx: &mut gpui2::TestAppContext) { + init_test(cx); -// let fs = FakeFs::new(cx.executor().clone()); -// fs.insert_tree("/dir", json!({})).await; + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree("/dir", json!({})).await; -// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; -// let languages = project.update(cx, |project, _| project.languages().clone()); -// languages.register( -// "/some/path", -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".into()], -// ..Default::default() -// }, -// tree_sitter_rust::language(), -// vec![], -// |_| Default::default(), -// ); + let languages = project.update(cx, |project, _| project.languages().clone()); + languages.register( + "/some/path", + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".into()], + ..Default::default() + }, + tree_sitter_rust::language(), + vec![], + |_| Default::default(), + ); -// let buffer = project.update(cx, |project, cx| { -// project.create_buffer("", None, cx).unwrap() -// }); -// buffer.update(cx, |buffer, cx| { -// buffer.edit([(0..0, "abc")], None, cx); -// assert!(buffer.is_dirty()); -// assert!(!buffer.has_conflict()); -// assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); -// }); -// project -// .update(cx, |project, cx| { -// project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) -// }) -// .await -// .unwrap(); -// assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); + let buffer = project.update(cx, |project, cx| { + project.create_buffer("", None, cx).unwrap() + }); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "abc")], None, cx); + assert!(buffer.is_dirty()); + assert!(!buffer.has_conflict()); + assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); + }); + project + .update(cx, |project, cx| { + project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) + }) + .await + .unwrap(); + assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); -// cx.executor().run_until_parked(); -// buffer.update(cx, |buffer, cx| { -// assert_eq!( -// buffer.file().unwrap().full_path(cx), -// Path::new("dir/file1.rs") -// ); -// assert!(!buffer.is_dirty()); -// assert!(!buffer.has_conflict()); -// assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); -// }); + cx.executor().run_until_parked(); + buffer.update(cx, |buffer, cx| { + assert_eq!( + buffer.file().unwrap().full_path(cx), + Path::new("dir/file1.rs") + ); + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); + }); -// let opened_buffer = project -// .update(cx, |project, cx| { -// project.open_local_buffer("/dir/file1.rs", cx) -// }) -// .await -// .unwrap(); -// assert_eq!(opened_buffer, buffer); -// } + let opened_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/file1.rs", cx) + }) + .await + .unwrap(); + assert_eq!(opened_buffer, buffer); +} #[gpui2::test(retries = 5)] -// async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { -// init_test(cx); -// cx.executor().allow_parking(); +async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { + init_test(cx); + // cx.executor().allow_parking(); -// let dir = temp_tree(json!({ -// "a": { -// "file1": "", -// "file2": "", -// "file3": "", -// }, -// "b": { -// "c": { -// "file4": "", -// "file5": "", -// } -// } -// })); + let dir = temp_tree(json!({ + "a": { + "file1": "", + "file2": "", + "file3": "", + }, + "b": { + "c": { + "file4": "", + "file5": "", + } + } + })); -// let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; -// let rpc = project.update(cx, |p, _| p.client.clone()); + 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 = 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| { -// project.update(cx, |project, cx| { -// let tree = project.worktrees().next().unwrap(); -// tree.read(cx) -// .entry_for_path(path) -// .unwrap_or_else(|| panic!("no entry for path {}", path)) -// .id -// }) -// }; + let buffer_for_path = |path: &'static str, cx: &mut gpui2::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| { + project.update(cx, |project, cx| { + let tree = project.worktrees().next().unwrap(); + tree.read(cx) + .entry_for_path(path) + .unwrap_or_else(|| panic!("no entry for path {}", path)) + .id + }) + }; -// let buffer2 = buffer_for_path("a/file2", cx).await; -// let buffer3 = buffer_for_path("a/file3", cx).await; -// let buffer4 = buffer_for_path("b/c/file4", cx).await; -// let buffer5 = buffer_for_path("b/c/file5", cx).await; + let buffer2 = buffer_for_path("a/file2", cx).await; + let buffer3 = buffer_for_path("a/file3", cx).await; + let buffer4 = buffer_for_path("b/c/file4", cx).await; + let buffer5 = buffer_for_path("b/c/file5", cx).await; -// let file2_id = id_for_path("a/file2", cx); -// let file3_id = id_for_path("a/file3", cx); -// let file4_id = id_for_path("b/c/file4", cx); + let file2_id = id_for_path("a/file2", cx); + let file3_id = id_for_path("a/file3", cx); + let file4_id = id_for_path("b/c/file4", cx); -// // Create a remote copy of this worktree. -// let tree = project.update(cx, |project, _| project.worktrees().next().unwrap()); + // Create a remote copy of this worktree. + let tree = project.update(cx, |project, _| project.worktrees().next().unwrap()); -// let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto()); + let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto()); -// let updates = Arc::new(Mutex::new(Vec::new())); -// tree.update(cx, |tree, cx| { -// let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, { -// let updates = updates.clone(); -// move |update| { -// updates.lock().push(update); -// async { true } -// } -// }); -// }); + let updates = Arc::new(Mutex::new(Vec::new())); + tree.update(cx, |tree, cx| { + let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, { + let updates = updates.clone(); + move |update| { + updates.lock().push(update); + async { true } + } + }); + }); -// let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); -// cx.executor().run_until_parked(); + let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); + cx.executor().run_until_parked(); -// cx.update(|cx| { -// assert!(!buffer2.read(cx).is_dirty()); -// assert!(!buffer3.read(cx).is_dirty()); -// assert!(!buffer4.read(cx).is_dirty()); -// assert!(!buffer5.read(cx).is_dirty()); -// }); + cx.update(|cx| { + assert!(!buffer2.read(cx).is_dirty()); + assert!(!buffer3.read(cx).is_dirty()); + assert!(!buffer4.read(cx).is_dirty()); + assert!(!buffer5.read(cx).is_dirty()); + }); -// // Rename and delete files and directories. -// tree.flush_fs_events(cx).await; -// std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); -// std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); -// std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); -// std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); -// tree.flush_fs_events(cx).await; + // Rename and delete files and directories. + tree.flush_fs_events(cx).await; + std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); + std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); + std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); + std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); + tree.flush_fs_events(cx).await; -// let expected_paths = vec![ -// "a", -// "a/file1", -// "a/file2.new", -// "b", -// "d", -// "d/file3", -// "d/file4", -// ]; + let expected_paths = vec![ + "a", + "a/file1", + "a/file2.new", + "b", + "d", + "d/file3", + "d/file4", + ]; -// cx.update(|app| { -// assert_eq!( -// tree.read(app) -// .paths() -// .map(|p| p.to_str().unwrap()) -// .collect::>(), -// expected_paths -// ); -// }); + cx.update(|app| { + assert_eq!( + tree.read(app) + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + expected_paths + ); + }); -// assert_eq!(id_for_path("a/file2.new", cx), file2_id); -// assert_eq!(id_for_path("d/file3", cx), file3_id); -// assert_eq!(id_for_path("d/file4", cx), file4_id); + assert_eq!(id_for_path("a/file2.new", cx), file2_id); + assert_eq!(id_for_path("d/file3", cx), file3_id); + assert_eq!(id_for_path("d/file4", cx), file4_id); -// cx.update(|cx| { -// assert_eq!( -// buffer2.read(cx).file().unwrap().path().as_ref(), -// Path::new("a/file2.new") -// ); -// assert_eq!( -// buffer3.read(cx).file().unwrap().path().as_ref(), -// Path::new("d/file3") -// ); -// assert_eq!( -// buffer4.read(cx).file().unwrap().path().as_ref(), -// Path::new("d/file4") -// ); -// assert_eq!( -// buffer5.read(cx).file().unwrap().path().as_ref(), -// Path::new("b/c/file5") -// ); + cx.update(|cx| { + assert_eq!( + buffer2.read(cx).file().unwrap().path().as_ref(), + Path::new("a/file2.new") + ); + assert_eq!( + buffer3.read(cx).file().unwrap().path().as_ref(), + Path::new("d/file3") + ); + assert_eq!( + buffer4.read(cx).file().unwrap().path().as_ref(), + Path::new("d/file4") + ); + assert_eq!( + buffer5.read(cx).file().unwrap().path().as_ref(), + Path::new("b/c/file5") + ); -// assert!(!buffer2.read(cx).file().unwrap().is_deleted()); -// assert!(!buffer3.read(cx).file().unwrap().is_deleted()); -// assert!(!buffer4.read(cx).file().unwrap().is_deleted()); -// assert!(buffer5.read(cx).file().unwrap().is_deleted()); -// }); + assert!(!buffer2.read(cx).file().unwrap().is_deleted()); + assert!(!buffer3.read(cx).file().unwrap().is_deleted()); + assert!(!buffer4.read(cx).file().unwrap().is_deleted()); + assert!(buffer5.read(cx).file().unwrap().is_deleted()); + }); -// // Update the remote worktree. Check that it becomes consistent with the -// // local worktree. -// cx.executor().run_until_parked(); + // Update the remote worktree. Check that it becomes consistent with the + // local worktree. + cx.executor().run_until_parked(); + + remote.update(cx, |remote, _| { + for update in updates.lock().drain(..) { + remote.as_remote_mut().unwrap().update_from_remote(update); + } + }); + cx.executor().run_until_parked(); + remote.update(cx, |remote, _| { + assert_eq!( + remote + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + expected_paths + ); + }); +} -// remote.update(cx, |remote, _| { -// for update in updates.lock().drain(..) { -// remote.as_remote_mut().unwrap().update_from_remote(update); -// } -// }); -// cx.executor().run_until_parked(); -// remote.update(cx, |remote, _| { -// assert_eq!( -// remote -// .paths() -// .map(|p| p.to_str().unwrap()) -// .collect::>(), -// expected_paths -// ); -// }); -// } #[gpui2::test(iterations = 10)] async fn test_buffer_identity_across_renames(cx: &mut gpui2::TestAppContext) { init_test(cx); diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index 2718b5d8f0..c15977c5e0 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -297,11 +297,15 @@ impl Worktree { // After determining whether the root entry is a file or a directory, populate the // snapshot's "root name", which will be used for the purpose of fuzzy matching. let abs_path = path.into(); + eprintln!("get root metadata"); + let metadata = fs .metadata(&abs_path) .await .context("failed to stat worktree path")?; + eprintln!("got root metadata"); + cx.build_model(move |cx: &mut ModelContext| { let root_name = abs_path .file_name() diff --git a/crates/rpc2/src/peer.rs b/crates/rpc2/src/peer.rs index 367eba2b4e..104ab1b421 100644 --- a/crates/rpc2/src/peer.rs +++ b/crates/rpc2/src/peer.rs @@ -559,7 +559,6 @@ mod tests { use async_tungstenite::tungstenite::Message as WebSocketMessage; use gpui2::TestAppContext; - #[ctor::ctor] fn init_logger() { if std::env::var("RUST_LOG").is_ok() { env_logger::init(); @@ -568,6 +567,8 @@ mod tests { #[gpui2::test(iterations = 50)] async fn test_request_response(cx: &mut TestAppContext) { + init_logger(); + let executor = cx.executor(); // create 2 clients connected to 1 server From 6ee93125d04d04a5b181f6416063a1ab83790c53 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 17:11:42 -0700 Subject: [PATCH 082/156] Fix hangs in new dispatcher Co-authored-by: Nathan Sobo --- crates/gpui2/src/app/test_context.rs | 4 ++ crates/gpui2/src/executor.rs | 31 +++++----------- crates/gpui2/src/platform.rs | 3 ++ crates/gpui2/src/platform/mac/dispatcher.rs | 23 +++++++++++- crates/gpui2/src/platform/mac/platform.rs | 4 +- crates/gpui2/src/platform/test/dispatcher.rs | 18 +++++++++ crates/gpui2/src/test.rs | 2 +- crates/gpui2_macros/src/test.rs | 2 +- crates/project2/src/project2.rs | 5 --- crates/project2/src/project_tests.rs | 39 ++++++++++++++++---- crates/project2/src/worktree.rs | 6 +-- 11 files changed, 93 insertions(+), 44 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 624ec67eac..3a7705c8cf 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -70,6 +70,10 @@ impl TestAppContext { &self.background_executor } + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor + } + pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 63f3b94c79..25e88068c3 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -88,16 +88,7 @@ impl BackgroundExecutor { #[cfg(any(test, feature = "test-support"))] pub fn block_test(&self, future: impl Future) -> R { - let (runnable, task) = unsafe { - async_task::spawn_unchecked(future, { - let dispatcher = self.dispatcher.clone(); - move |runnable| dispatcher.dispatch_on_main_thread(runnable) - }) - }; - - runnable.schedule(); - - self.block_internal(false, task) + self.block_internal(false, future) } pub fn block(&self, future: impl Future) -> R { @@ -109,20 +100,19 @@ impl BackgroundExecutor { background_only: bool, future: impl Future, ) -> R { - dbg!("block_internal"); pin_mut!(future); - let (parker, unparker) = parking::pair(); + let unparker = self.dispatcher.unparker(); let awoken = Arc::new(AtomicBool::new(false)); - let awoken2 = awoken.clone(); - let waker = waker_fn(move || { - dbg!("WAKING UP."); - awoken2.store(true, SeqCst); - unparker.unpark(); + let waker = waker_fn({ + let awoken = awoken.clone(); + move || { + awoken.store(true, SeqCst); + unparker.unpark(); + } }); let mut cx = std::task::Context::from_waker(&waker); - dbg!("BOOOP"); loop { match future.as_mut().poll(&mut cx) { Poll::Ready(result) => return result, @@ -143,9 +133,8 @@ impl BackgroundExecutor { panic!("parked with nothing left to run\n{:?}", backtrace_message) } } - dbg!("PARKING!"); - parker.park(); - dbg!("CONTINUING!"); + + self.dispatcher.park(); } } } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 6e710daf6c..705c2f83bb 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -12,6 +12,7 @@ use crate::{ use anyhow::anyhow; use async_task::Runnable; use futures::channel::oneshot; +use parking::Unparker; use seahash::SeaHasher; use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -163,6 +164,8 @@ pub trait PlatformDispatcher: Send + Sync { fn dispatch_on_main_thread(&self, runnable: Runnable); fn dispatch_after(&self, duration: Duration, runnable: Runnable); fn poll(&self, background_only: bool) -> bool; + fn park(&self); + fn unparker(&self) -> Unparker; #[cfg(any(test, feature = "test-support"))] fn as_test(&self) -> Option<&TestDispatcher> { diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index f19de6f627..f5334912c6 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -9,8 +9,11 @@ use objc::{ runtime::{BOOL, YES}, sel, sel_impl, }; +use parking::{Parker, Unparker}; +use parking_lot::Mutex; use std::{ ffi::c_void, + sync::Arc, time::{Duration, SystemTime}, }; @@ -20,7 +23,17 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t { unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t } } -pub struct MacDispatcher; +pub struct MacDispatcher { + parker: Arc>, +} + +impl MacDispatcher { + pub fn new() -> Self { + MacDispatcher { + parker: Arc::new(Mutex::new(Parker::new())), + } + } +} impl PlatformDispatcher for MacDispatcher { fn is_main_thread(&self) -> bool { @@ -71,6 +84,14 @@ impl PlatformDispatcher for MacDispatcher { fn poll(&self, _background_only: bool) -> bool { false } + + fn park(&self) { + self.parker.lock().park() + } + + fn unparker(&self) -> Unparker { + self.parker.lock().unparker() + } } extern "C" fn trampoline(runnable: *mut c_void) { diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 8dd94f052e..fdc7fd6ae5 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -165,10 +165,10 @@ pub struct MacPlatformState { impl MacPlatform { pub fn new() -> Self { - let dispatcher = Arc::new(MacDispatcher); + let dispatcher = Arc::new(MacDispatcher::new()); Self(Mutex::new(MacPlatformState { background_executor: BackgroundExecutor::new(dispatcher.clone()), - foreground_executor: ForegroundExecutor::new(dispatcher.clone()), + foreground_executor: ForegroundExecutor::new(dispatcher), text_system: Arc::new(MacTextSystem::new()), display_linker: MacDisplayLinker::new(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index e537f86311..618d8c7917 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -2,6 +2,7 @@ use crate::PlatformDispatcher; use async_task::Runnable; use backtrace::Backtrace; use collections::{HashMap, VecDeque}; +use parking::{Parker, Unparker}; use parking_lot::Mutex; use rand::prelude::*; use std::{ @@ -19,6 +20,8 @@ struct TestDispatcherId(usize); pub struct TestDispatcher { id: TestDispatcherId, state: Arc>, + parker: Arc>, + unparker: Unparker, } struct TestDispatcherState { @@ -35,6 +38,7 @@ struct TestDispatcherState { impl TestDispatcher { pub fn new(random: StdRng) -> Self { + let (parker, unparker) = parking::pair(); let state = TestDispatcherState { random, foreground: HashMap::default(), @@ -50,6 +54,8 @@ impl TestDispatcher { TestDispatcher { id: TestDispatcherId(0), state: Arc::new(Mutex::new(state)), + parker: Arc::new(Mutex::new(parker)), + unparker, } } @@ -129,6 +135,8 @@ impl Clone for TestDispatcher { Self { id: TestDispatcherId(id), state: self.state.clone(), + parker: self.parker.clone(), + unparker: self.unparker.clone(), } } } @@ -140,6 +148,7 @@ impl PlatformDispatcher for TestDispatcher { fn dispatch(&self, runnable: Runnable) { self.state.lock().background.push(runnable); + self.unparker.unpark(); } fn dispatch_on_main_thread(&self, runnable: Runnable) { @@ -149,6 +158,7 @@ impl PlatformDispatcher for TestDispatcher { .entry(self.id) .or_default() .push_back(runnable); + self.unparker.unpark(); } fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) { @@ -215,6 +225,14 @@ impl PlatformDispatcher for TestDispatcher { true } + fn park(&self) { + self.parker.lock().park(); + } + + fn unparker(&self) -> Unparker { + self.unparker.clone() + } + fn as_test(&self) -> Option<&TestDispatcher> { Some(self) } diff --git a/crates/gpui2/src/test.rs b/crates/gpui2/src/test.rs index 61c70813cd..3f2697f7e3 100644 --- a/crates/gpui2/src/test.rs +++ b/crates/gpui2/src/test.rs @@ -28,7 +28,7 @@ pub fn run_test( } let result = panic::catch_unwind(|| { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed)); - test_fn(dispatcher.clone(), seed); + test_fn(dispatcher, seed); }); match result { diff --git a/crates/gpui2_macros/src/test.rs b/crates/gpui2_macros/src/test.rs index e01f39b0be..f7e45a90f9 100644 --- a/crates/gpui2_macros/src/test.rs +++ b/crates/gpui2_macros/src/test.rs @@ -91,7 +91,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { } Some("BackgroundExecutor") => { inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new( - std::sync::Arc::new(dispatcher.clone()) + std::sync::Arc::new(dispatcher.clone()), ),)); continue; } diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index a598aac79e..1457bd41cc 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -877,17 +877,14 @@ impl Project { ) }); for path in root_paths { - dbg!(&path); let (tree, _) = project .update(cx, |project, cx| { project.find_or_create_local_worktree(path, true, cx) }) .await .unwrap(); - dbg!("aaa"); tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete()) .await; - dbg!("bbb"); } project } @@ -5993,10 +5990,8 @@ impl Project { ) -> Task, PathBuf)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) { - dbg!("shortcut"); Task::ready(Ok((tree, relative_path))) } else { - dbg!("long cut"); let worktree = self.create_local_worktree(abs_path, visible, cx); cx.background_executor() .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index fba2548451..ca6cdbccfc 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -15,6 +15,36 @@ 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) { + cx.executor().allow_parking(); + + let (tx, mut rx) = futures::channel::mpsc::unbounded(); + let _thread = std::thread::spawn(move || { + std::fs::metadata("/Users").unwrap(); + std::thread::sleep(Duration::from_millis(1000)); + tx.unbounded_send(1).unwrap(); + }); + rx.next().await.unwrap(); +} + +#[gpui2::test] +async fn test_block_via_smol(cx: &mut gpui2::TestAppContext) { + cx.executor().allow_parking(); + + let io_task = smol::unblock(move || { + println!("sleeping on thread {:?}", std::thread::current().id()); + std::thread::sleep(Duration::from_millis(10)); + 1 + }); + + let task = cx.foreground_executor().spawn(async move { + io_task.await; + }); + + task.await; +} + #[gpui2::test] async fn test_symlinks(cx: &mut gpui2::TestAppContext) { init_test(cx); @@ -35,8 +65,6 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { } })); - dbg!("GOT HERE"); - let root_link_path = dir.path().join("root_link"); os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); os::unix::fs::symlink( @@ -45,11 +73,8 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { ) .unwrap(); - dbg!("GOT HERE 2"); - let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; - dbg!("GOT HERE 2.5"); project.update(cx, |project, cx| { let tree = project.worktrees().next().unwrap().read(cx); assert_eq!(tree.file_count(), 5); @@ -58,8 +83,6 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) { tree.inode_for_path("finnochio/grape") ); }); - - dbg!("GOT HERE 3"); } #[gpui2::test] @@ -2706,7 +2729,7 @@ async fn test_save_as(cx: &mut gpui2::TestAppContext) { #[gpui2::test(retries = 5)] async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { init_test(cx); - // cx.executor().allow_parking(); + cx.executor().allow_parking(); let dir = temp_tree(json!({ "a": { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index c15977c5e0..f824466e49 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -297,15 +297,12 @@ impl Worktree { // After determining whether the root entry is a file or a directory, populate the // snapshot's "root name", which will be used for the purpose of fuzzy matching. let abs_path = path.into(); - eprintln!("get root metadata"); let metadata = fs .metadata(&abs_path) .await .context("failed to stat worktree path")?; - eprintln!("got root metadata"); - cx.build_model(move |cx: &mut ModelContext| { let root_name = abs_path .file_name() @@ -4067,13 +4064,12 @@ impl WorktreeModelHandle for Model { fs.create_file(&root_path.join(filename), Default::default()) .await .unwrap(); - cx.executor().run_until_parked(); + assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some())); fs.remove_file(&root_path.join(filename), Default::default()) .await .unwrap(); - cx.executor().run_until_parked(); assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none())); cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) From 401ddc6f49d01cf3d64c5e438da4eb8c104ef0f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Nov 2023 17:45:38 -0700 Subject: [PATCH 083/156] WIP - flush_fs_events --- crates/gpui2/src/app/model_context.rs | 6 ++-- crates/gpui2/src/app/test_context.rs | 41 +++++++++++++++++++++++++-- crates/project2/src/worktree.rs | 16 ++++++----- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index ee8c8871e6..f6982cdc1f 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -40,15 +40,15 @@ impl<'a, T: 'static> ModelContext<'a, T> { self.model_state.clone() } - pub fn observe( + pub fn observe( &mut self, entity: &E, mut on_notify: impl FnMut(&mut T, E, &mut ModelContext<'_, T>) + 'static, ) -> Subscription where T: 'static, - T2: 'static, - E: Entity, + W: 'static, + E: Entity, { let this = self.weak_model(); let entity_id = entity.entity_id(); diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 3a7705c8cf..d4a63c4e37 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -3,8 +3,9 @@ use crate::{ ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; -use futures::SinkExt; -use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc}; +use anyhow::anyhow; +use futures::{SinkExt, StreamExt}; +use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] pub struct TestAppContext { @@ -158,4 +159,40 @@ impl TestAppContext { .detach(); rx } + + pub async fn condition( + &mut self, + model: &Model, + mut predicate: impl FnMut(&mut T, &mut ModelContext) -> bool, + ) { + let (mut tx, mut rx) = futures::channel::mpsc::unbounded::<()>(); + let timer = self.executor().timer(Duration::from_secs(3)); + + let subscriptions = model.update(self, move |_, cx| { + ( + cx.observe(model, move |_, _, _| { + // let _ = tx.send(()); + }), + cx.subscribe(model, move |_, _, _, _| { + let _ = tx.send(()); + }), + ) + }); + + use futures::FutureExt as _; + use smol::future::FutureExt as _; + + async { + while rx.next().await.is_some() { + if model.update(self, &mut predicate) { + return Ok(()); + } + } + drop(subscriptions); + unreachable!() + } + .race(timer.map(|_| Err(anyhow!("condition timed out")))) + .await + .unwrap(); + } } diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index f824466e49..f146bf7948 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -17,7 +17,7 @@ use futures::{ }, select_biased, task::Poll, - FutureExt, Stream, StreamExt, + FutureExt as _, Stream, StreamExt, }; use fuzzy2::CharBag; use git::{DOT_GIT, GITIGNORE}; @@ -4053,7 +4053,8 @@ impl WorktreeModelHandle for Model { &self, cx: &'a mut gpui2::TestAppContext, ) -> futures::future::LocalBoxFuture<'a, ()> { - let filename = "fs-event-sentinel"; + let file_name = "fs-event-sentinel"; + let tree = self.clone(); let (fs, root_path) = self.update(cx, |tree, _| { let tree = tree.as_local().unwrap(); @@ -4061,16 +4062,17 @@ impl WorktreeModelHandle for Model { }); async move { - fs.create_file(&root_path.join(filename), Default::default()) + fs.create_file(&root_path.join(file_name), Default::default()) .await .unwrap(); + cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some()) + .await; - assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some())); - - fs.remove_file(&root_path.join(filename), Default::default()) + fs.remove_file(&root_path.join(file_name), Default::default()) .await .unwrap(); - assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none())); + cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none()) + .await; cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; From 69aafe9ff6319f2afb8eec3a91630e2394fa9807 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 02:10:50 +0100 Subject: [PATCH 084/156] Adjust `ColorScale` representation (#3204) This PR adjusts the representations of `ColorScale`s to allow us to remove an unsafe `From` impl when converting from the statically-defined representation of the scale. Release Notes: - N/A --- crates/theme2/src/default_colors.rs | 218 ++++++++++++++++------------ crates/theme2/src/scale.rs | 22 ++- 2 files changed, 147 insertions(+), 93 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 8fb38e9661..4dc30e6736 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,9 +1,12 @@ -use gpui2::{hsla, Rgba}; +use std::num::ParseIntError; + +use gpui2::{hsla, Hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, + ColorScale, }; impl Default for SystemColors { @@ -269,31 +272,35 @@ impl ThemeColors { } } -struct DefaultColorScaleSet { +type StaticColorScale = [&'static str; 12]; + +struct StaticColorScaleSet { scale: &'static str, - light: [&'static str; 12], - light_alpha: [&'static str; 12], - dark: [&'static str; 12], - dark_alpha: [&'static str; 12], + light: StaticColorScale, + light_alpha: StaticColorScale, + dark: StaticColorScale, + dark_alpha: StaticColorScale, } -impl From for ColorScaleSet { - fn from(default: DefaultColorScaleSet) -> Self { - Self::new( - default.scale, - default - .light - .map(|color| Rgba::try_from(color).unwrap().into()), - default - .light_alpha - .map(|color| Rgba::try_from(color).unwrap().into()), - default - .dark - .map(|color| Rgba::try_from(color).unwrap().into()), - default - .dark_alpha - .map(|color| Rgba::try_from(color).unwrap().into()), - ) +impl TryFrom for ColorScaleSet { + type Error = ParseIntError; + + fn try_from(value: StaticColorScaleSet) -> Result { + fn to_color_scale(scale: StaticColorScale) -> Result { + scale + .into_iter() + .map(|color| Rgba::try_from(color).map(Hsla::from)) + .collect::, _>>() + .map(ColorScale::from_iter) + } + + Ok(Self::new( + value.scale, + to_color_scale(value.light)?, + to_color_scale(value.light_alpha)?, + to_color_scale(value.dark)?, + to_color_scale(value.dark_alpha)?, + )) } } @@ -336,7 +343,7 @@ pub fn default_color_scales() -> ColorScales { } fn gray() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Gray", light: [ "#fcfcfcff", @@ -395,11 +402,12 @@ fn gray() -> ColorScaleSet { "#ffffffed", ], } - .into() + .try_into() + .unwrap() } fn mauve() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Mauve", light: [ "#fdfcfdff", @@ -458,11 +466,12 @@ fn mauve() -> ColorScaleSet { "#fdfdffef", ], } - .into() + .try_into() + .unwrap() } fn slate() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Slate", light: [ "#fcfcfdff", @@ -521,11 +530,12 @@ fn slate() -> ColorScaleSet { "#fcfdffef", ], } - .into() + .try_into() + .unwrap() } fn sage() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Sage", light: [ "#fbfdfcff", @@ -584,11 +594,12 @@ fn sage() -> ColorScaleSet { "#fdfffeed", ], } - .into() + .try_into() + .unwrap() } fn olive() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Olive", light: [ "#fcfdfcff", @@ -647,11 +658,12 @@ fn olive() -> ColorScaleSet { "#fdfffded", ], } - .into() + .try_into() + .unwrap() } fn sand() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Sand", light: [ "#fdfdfcff", @@ -710,11 +722,12 @@ fn sand() -> ColorScaleSet { "#fffffded", ], } - .into() + .try_into() + .unwrap() } fn gold() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Gold", light: [ "#fdfdfcff", @@ -773,11 +786,12 @@ fn gold() -> ColorScaleSet { "#fef7ede7", ], } - .into() + .try_into() + .unwrap() } fn bronze() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Bronze", light: [ "#fdfcfcff", @@ -836,11 +850,12 @@ fn bronze() -> ColorScaleSet { "#fff1e9ec", ], } - .into() + .try_into() + .unwrap() } fn brown() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Brown", light: [ "#fefdfcff", @@ -899,11 +914,12 @@ fn brown() -> ColorScaleSet { "#feecd4f2", ], } - .into() + .try_into() + .unwrap() } fn yellow() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Yellow", light: [ "#fdfdf9ff", @@ -962,11 +978,12 @@ fn yellow() -> ColorScaleSet { "#fef6baf6", ], } - .into() + .try_into() + .unwrap() } fn amber() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Amber", light: [ "#fefdfbff", @@ -1025,11 +1042,12 @@ fn amber() -> ColorScaleSet { "#ffe7b3ff", ], } - .into() + .try_into() + .unwrap() } fn orange() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Orange", light: [ "#fefcfbff", @@ -1088,11 +1106,12 @@ fn orange() -> ColorScaleSet { "#ffe0c2ff", ], } - .into() + .try_into() + .unwrap() } fn tomato() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Tomato", light: [ "#fffcfcff", @@ -1151,11 +1170,12 @@ fn tomato() -> ColorScaleSet { "#ffd6cefb", ], } - .into() + .try_into() + .unwrap() } fn red() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Red", light: [ "#fffcfcff", @@ -1214,11 +1234,12 @@ fn red() -> ColorScaleSet { "#ffd1d9ff", ], } - .into() + .try_into() + .unwrap() } fn ruby() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Ruby", light: [ "#fffcfdff", @@ -1277,11 +1298,12 @@ fn ruby() -> ColorScaleSet { "#ffd3e2fe", ], } - .into() + .try_into() + .unwrap() } fn crimson() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Crimson", light: [ "#fffcfdff", @@ -1340,11 +1362,12 @@ fn crimson() -> ColorScaleSet { "#ffd5eafd", ], } - .into() + .try_into() + .unwrap() } fn pink() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Pink", light: [ "#fffcfeff", @@ -1403,11 +1426,12 @@ fn pink() -> ColorScaleSet { "#ffd3ecfd", ], } - .into() + .try_into() + .unwrap() } fn plum() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Plum", light: [ "#fefcffff", @@ -1466,11 +1490,12 @@ fn plum() -> ColorScaleSet { "#feddfef4", ], } - .into() + .try_into() + .unwrap() } fn purple() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Purple", light: [ "#fefcfeff", @@ -1529,11 +1554,12 @@ fn purple() -> ColorScaleSet { "#f1ddfffa", ], } - .into() + .try_into() + .unwrap() } fn violet() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Violet", light: [ "#fdfcfeff", @@ -1592,11 +1618,12 @@ fn violet() -> ColorScaleSet { "#e3defffe", ], } - .into() + .try_into() + .unwrap() } fn iris() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Iris", light: [ "#fdfdffff", @@ -1655,11 +1682,12 @@ fn iris() -> ColorScaleSet { "#e1e0fffe", ], } - .into() + .try_into() + .unwrap() } fn indigo() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Indigo", light: [ "#fdfdfeff", @@ -1718,11 +1746,12 @@ fn indigo() -> ColorScaleSet { "#d6e1ffff", ], } - .into() + .try_into() + .unwrap() } fn blue() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Blue", light: [ "#fbfdffff", @@ -1781,11 +1810,12 @@ fn blue() -> ColorScaleSet { "#c2e6ffff", ], } - .into() + .try_into() + .unwrap() } fn cyan() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Cyan", light: [ "#fafdfeff", @@ -1844,11 +1874,12 @@ fn cyan() -> ColorScaleSet { "#bbf3fef7", ], } - .into() + .try_into() + .unwrap() } fn teal() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Teal", light: [ "#fafefdff", @@ -1907,11 +1938,12 @@ fn teal() -> ColorScaleSet { "#b8ffebef", ], } - .into() + .try_into() + .unwrap() } fn jade() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Jade", light: [ "#fbfefdff", @@ -1970,11 +2002,12 @@ fn jade() -> ColorScaleSet { "#b8ffe1ef", ], } - .into() + .try_into() + .unwrap() } fn green() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Green", light: [ "#fbfefcff", @@ -2033,11 +2066,12 @@ fn green() -> ColorScaleSet { "#bbffd7f0", ], } - .into() + .try_into() + .unwrap() } fn grass() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Grass", light: [ "#fbfefbff", @@ -2096,11 +2130,12 @@ fn grass() -> ColorScaleSet { "#ceffceef", ], } - .into() + .try_into() + .unwrap() } fn lime() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Lime", light: [ "#fcfdfaff", @@ -2159,11 +2194,12 @@ fn lime() -> ColorScaleSet { "#e9febff7", ], } - .into() + .try_into() + .unwrap() } fn mint() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Mint", light: [ "#f9fefdff", @@ -2222,11 +2258,12 @@ fn mint() -> ColorScaleSet { "#cbfee9f5", ], } - .into() + .try_into() + .unwrap() } fn sky() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Sky", light: [ "#f9feffff", @@ -2285,11 +2322,12 @@ fn sky() -> ColorScaleSet { "#c2f3ffff", ], } - .into() + .try_into() + .unwrap() } fn black() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Black", light: [ "#0000000d", @@ -2348,11 +2386,12 @@ fn black() -> ColorScaleSet { "#000000f2", ], } - .into() + .try_into() + .unwrap() } fn white() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "White", light: [ "#ffffff0d", @@ -2411,5 +2450,6 @@ fn white() -> ColorScaleSet { "#fffffff2", ], } - .into() + .try_into() + .unwrap() } diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index 21c8592d81..a22036df8d 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -2,7 +2,24 @@ use gpui2::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; -pub type ColorScale = [Hsla; 12]; +/// A one-based step in a [`ColorScale`]. +pub type ColorScaleStep = usize; + +pub struct ColorScale(Vec); + +impl FromIterator for ColorScale { + fn from_iter>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl std::ops::Index for ColorScale { + type Output = Hsla; + + fn index(&self, index: ColorScaleStep) -> &Self::Output { + &self.0[index - 1] + } +} pub struct ColorScales { pub gray: ColorScaleSet, @@ -85,9 +102,6 @@ impl IntoIterator for ColorScales { } } -/// A one-based step in a [`ColorScale`]. -pub type ColorScaleStep = usize; - pub struct ColorScaleSet { name: SharedString, light: ColorScale, From 220228c183d7a2f5dcf161586f004196f306f895 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 21:15:06 -0400 Subject: [PATCH 085/156] Fix underflow when indexing into `ColorScale`s --- crates/theme2/src/scale.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index a22036df8d..c02f078c89 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -132,19 +132,19 @@ impl ColorScaleSet { } pub fn light(&self, step: ColorScaleStep) -> Hsla { - self.light[step - 1] + self.light[step] } pub fn light_alpha(&self, step: ColorScaleStep) -> Hsla { - self.light_alpha[step - 1] + self.light_alpha[step] } pub fn dark(&self, step: ColorScaleStep) -> Hsla { - self.dark[step - 1] + self.dark[step] } pub fn dark_alpha(&self, step: ColorScaleStep) -> Hsla { - self.dark_alpha[step - 1] + self.dark_alpha[step] } pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { From 72d060108d23de34db50efeef51047d47989b42a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 02:52:42 +0100 Subject: [PATCH 086/156] Make indexing into `ColorScale`s safe (#3205) This PR makes indexing into `ColorScale`s safe by constraining the `ColorScaleStep`s to a set of known values. Release Notes: - N/A --- crates/storybook2/src/stories/colors.rs | 13 +- crates/theme2/src/default_colors.rs | 2 +- crates/theme2/src/scale.rs | 162 +++++++++++++++++++++--- 3 files changed, 155 insertions(+), 22 deletions(-) diff --git a/crates/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index 0dd56071c8..c1c65d62fa 100644 --- a/crates/storybook2/src/stories/colors.rs +++ b/crates/storybook2/src/stories/colors.rs @@ -1,6 +1,6 @@ use crate::story::Story; use gpui2::{px, Div, Render}; -use theme2::default_color_scales; +use theme2::{default_color_scales, ColorScaleStep}; use ui::prelude::*; pub struct ColorsStory; @@ -30,9 +30,14 @@ impl Render for ColorsStory { .line_height(px(24.)) .child(scale.name().to_string()), ) - .child(div().flex().gap_1().children( - (1..=12).map(|step| div().flex().size_6().bg(scale.step(cx, step))), - )) + .child( + div() + .flex() + .gap_1() + .children(ColorScaleStep::ALL.map(|step| { + div().flex().size_6().bg(scale.step(cx, step)) + })), + ) })), ) } diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 4dc30e6736..335b6801d5 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -23,7 +23,7 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: gpui2::black(), + conflict: red().dark().step_11(), created: gpui2::black(), deleted: gpui2::black(), error: gpui2::black(), diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index a22036df8d..bd28e43db9 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -3,7 +3,62 @@ use gpui2::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; /// A one-based step in a [`ColorScale`]. -pub type ColorScaleStep = usize; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct ColorScaleStep(usize); + +impl ColorScaleStep { + /// The first step in a [`ColorScale`]. + pub const ONE: Self = Self(1); + + /// The second step in a [`ColorScale`]. + pub const TWO: Self = Self(2); + + /// The third step in a [`ColorScale`]. + pub const THREE: Self = Self(3); + + /// The fourth step in a [`ColorScale`]. + pub const FOUR: Self = Self(4); + + /// The fifth step in a [`ColorScale`]. + pub const FIVE: Self = Self(5); + + /// The sixth step in a [`ColorScale`]. + pub const SIX: Self = Self(6); + + /// The seventh step in a [`ColorScale`]. + pub const SEVEN: Self = Self(7); + + /// The eighth step in a [`ColorScale`]. + pub const EIGHT: Self = Self(8); + + /// The ninth step in a [`ColorScale`]. + pub const NINE: Self = Self(9); + + /// The tenth step in a [`ColorScale`]. + pub const TEN: Self = Self(10); + + /// The eleventh step in a [`ColorScale`]. + pub const ELEVEN: Self = Self(11); + + /// The twelfth step in a [`ColorScale`]. + pub const TWELVE: Self = Self(12); + + /// All of the steps in a [`ColorScale`]. + pub const ALL: [ColorScaleStep; 12] = [ + Self::ONE, + Self::TWO, + Self::THREE, + Self::FOUR, + Self::FIVE, + Self::SIX, + Self::SEVEN, + Self::EIGHT, + Self::NINE, + Self::TEN, + Self::ELEVEN, + Self::TWELVE, + ]; +} pub struct ColorScale(Vec); @@ -13,11 +68,84 @@ impl FromIterator for ColorScale { } } -impl std::ops::Index for ColorScale { - type Output = Hsla; +impl ColorScale { + /// Returns the specified step in the [`ColorScale`]. + #[inline] + pub fn step(&self, step: ColorScaleStep) -> Hsla { + // Steps are one-based, so we need convert to the zero-based vec index. + self.0[step.0 - 1] + } - fn index(&self, index: ColorScaleStep) -> &Self::Output { - &self.0[index - 1] + /// Returns the first step in the [`ColorScale`]. + #[inline] + pub fn step_1(&self) -> Hsla { + self.step(ColorScaleStep::ONE) + } + + /// Returns the second step in the [`ColorScale`]. + #[inline] + pub fn step_2(&self) -> Hsla { + self.step(ColorScaleStep::TWO) + } + + /// Returns the third step in the [`ColorScale`]. + #[inline] + pub fn step_3(&self) -> Hsla { + self.step(ColorScaleStep::THREE) + } + + /// Returns the fourth step in the [`ColorScale`]. + #[inline] + pub fn step_4(&self) -> Hsla { + self.step(ColorScaleStep::FOUR) + } + + /// Returns the fifth step in the [`ColorScale`]. + #[inline] + pub fn step_5(&self) -> Hsla { + self.step(ColorScaleStep::FIVE) + } + + /// Returns the sixth step in the [`ColorScale`]. + #[inline] + pub fn step_6(&self) -> Hsla { + self.step(ColorScaleStep::SIX) + } + + /// Returns the seventh step in the [`ColorScale`]. + #[inline] + pub fn step_7(&self) -> Hsla { + self.step(ColorScaleStep::SEVEN) + } + + /// Returns the eighth step in the [`ColorScale`]. + #[inline] + pub fn step_8(&self) -> Hsla { + self.step(ColorScaleStep::EIGHT) + } + + /// Returns the ninth step in the [`ColorScale`]. + #[inline] + pub fn step_9(&self) -> Hsla { + self.step(ColorScaleStep::NINE) + } + + /// Returns the tenth step in the [`ColorScale`]. + #[inline] + pub fn step_10(&self) -> Hsla { + self.step(ColorScaleStep::TEN) + } + + /// Returns the eleventh step in the [`ColorScale`]. + #[inline] + pub fn step_11(&self) -> Hsla { + self.step(ColorScaleStep::ELEVEN) + } + + /// Returns the twelfth step in the [`ColorScale`]. + #[inline] + pub fn step_12(&self) -> Hsla { + self.step(ColorScaleStep::TWELVE) } } @@ -131,33 +259,33 @@ impl ColorScaleSet { &self.name } - pub fn light(&self, step: ColorScaleStep) -> Hsla { - self.light[step - 1] + pub fn light(&self) -> &ColorScale { + &self.light } - pub fn light_alpha(&self, step: ColorScaleStep) -> Hsla { - self.light_alpha[step - 1] + pub fn light_alpha(&self) -> &ColorScale { + &self.light_alpha } - pub fn dark(&self, step: ColorScaleStep) -> Hsla { - self.dark[step - 1] + pub fn dark(&self) -> &ColorScale { + &self.dark } - pub fn dark_alpha(&self, step: ColorScaleStep) -> Hsla { - self.dark_alpha[step - 1] + pub fn dark_alpha(&self) -> &ColorScale { + &self.dark_alpha } pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { match cx.theme().appearance { - Appearance::Light => self.light(step), - Appearance::Dark => self.dark(step), + Appearance::Light => self.light().step(step), + Appearance::Dark => self.dark().step(step), } } pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { match cx.theme().appearance { - Appearance::Light => self.light_alpha(step), - Appearance::Dark => self.dark_alpha(step), + Appearance::Light => self.light_alpha.step(step), + Appearance::Dark => self.dark_alpha.step(step), } } } From b9ac1e43cdae9f3c19bd62281e61c281c612c630 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 22:00:16 -0400 Subject: [PATCH 087/156] Update scale accessors --- crates/theme2/src/default_colors.rs | 355 +++++++++++++++------------- 1 file changed, 188 insertions(+), 167 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 6ef71009c5..15f4d6548e 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -27,17 +27,17 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: red().dark(11).into(), - created: grass().dark(11).into(), - deleted: red().dark(11).into(), - error: red().dark(11).into(), - hidden: neutral().dark(11).into(), - ignored: neutral().dark(11).into(), - info: blue().dark(11).into(), - modified: yellow().dark(11).into(), - renamed: blue().dark(11).into(), - success: grass().dark(11).into(), - warning: yellow().dark(11).into(), + conflict: red().dark().step_11().into(), + created: grass().dark().step_11().into(), + deleted: red().dark().step_11().into(), + error: red().dark().step_11().into(), + hidden: neutral().dark().step_11().into(), + ignored: neutral().dark().step_11().into(), + info: blue().dark().step_11().into(), + modified: yellow().dark().step_11().into(), + renamed: blue().dark().step_11().into(), + success: grass().dark().step_11().into(), + warning: yellow().dark().step_11().into(), } } } @@ -45,12 +45,12 @@ impl Default for StatusColors { impl Default for GitStatusColors { fn default() -> Self { Self { - conflict: orange().dark(11), - created: grass().dark(11), - deleted: red().dark(11), - ignored: neutral().dark(11), - modified: yellow().dark(11), - renamed: blue().dark(11), + conflict: orange().dark().step_11(), + created: grass().dark().step_11(), + deleted: red().dark().step_11(), + ignored: neutral().dark().step_11(), + modified: yellow().dark().step_11(), + renamed: blue().dark().step_11(), } } } @@ -86,45 +86,57 @@ impl SyntaxTheme { pub fn default_light() -> Self { Self { highlights: vec![ - ("attribute".into(), cyan().light(11).into()), - ("boolean".into(), tomato().light(11).into()), - ("comment".into(), neutral().light(11).into()), - ("comment.doc".into(), iris().light(12).into()), - ("constant".into(), red().light(7).into()), - ("constructor".into(), red().light(7).into()), - ("embedded".into(), red().light(7).into()), - ("emphasis".into(), red().light(7).into()), - ("emphasis.strong".into(), red().light(7).into()), - ("enum".into(), red().light(7).into()), - ("function".into(), red().light(7).into()), - ("hint".into(), red().light(7).into()), - ("keyword".into(), orange().light(11).into()), - ("label".into(), red().light(7).into()), - ("link_text".into(), red().light(7).into()), - ("link_uri".into(), red().light(7).into()), - ("number".into(), red().light(7).into()), - ("operator".into(), red().light(7).into()), - ("predictive".into(), red().light(7).into()), - ("preproc".into(), red().light(7).into()), - ("primary".into(), red().light(7).into()), - ("property".into(), red().light(7).into()), - ("punctuation".into(), neutral().light(11).into()), - ("punctuation.bracket".into(), neutral().light(11).into()), - ("punctuation.delimiter".into(), neutral().light(11).into()), - ("punctuation.list_marker".into(), blue().light(11).into()), - ("punctuation.special".into(), red().light(7).into()), - ("string".into(), jade().light(11).into()), - ("string.escape".into(), red().light(7).into()), - ("string.regex".into(), tomato().light(11).into()), - ("string.special".into(), red().light(7).into()), - ("string.special.symbol".into(), red().light(7).into()), - ("tag".into(), red().light(7).into()), - ("text.literal".into(), red().light(7).into()), - ("title".into(), red().light(7).into()), - ("type".into(), red().light(7).into()), - ("variable".into(), red().light(7).into()), - ("variable.special".into(), red().light(7).into()), - ("variant".into(), red().light(7).into()), + ("attribute".into(), cyan().light().step_11().into()), + ("boolean".into(), tomato().light().step_11().into()), + ("comment".into(), neutral().light().step_11().into()), + ("comment.doc".into(), iris().light().step_12().into()), + ("constant".into(), red().light().step_7().into()), + ("constructor".into(), red().light().step_7().into()), + ("embedded".into(), red().light().step_7().into()), + ("emphasis".into(), red().light().step_7().into()), + ("emphasis.strong".into(), red().light().step_7().into()), + ("enum".into(), red().light().step_7().into()), + ("function".into(), red().light().step_7().into()), + ("hint".into(), red().light().step_7().into()), + ("keyword".into(), orange().light().step_11().into()), + ("label".into(), red().light().step_7().into()), + ("link_text".into(), red().light().step_7().into()), + ("link_uri".into(), red().light().step_7().into()), + ("number".into(), red().light().step_7().into()), + ("operator".into(), red().light().step_7().into()), + ("predictive".into(), red().light().step_7().into()), + ("preproc".into(), red().light().step_7().into()), + ("primary".into(), red().light().step_7().into()), + ("property".into(), red().light().step_7().into()), + ("punctuation".into(), neutral().light().step_11().into()), + ( + "punctuation.bracket".into(), + neutral().light().step_11().into(), + ), + ( + "punctuation.delimiter".into(), + neutral().light().step_11().into(), + ), + ( + "punctuation.list_marker".into(), + blue().light().step_11().into(), + ), + ("punctuation.special".into(), red().light().step_7().into()), + ("string".into(), jade().light().step_11().into()), + ("string.escape".into(), red().light().step_7().into()), + ("string.regex".into(), tomato().light().step_11().into()), + ("string.special".into(), red().light().step_7().into()), + ( + "string.special.symbol".into(), + red().light().step_7().into(), + ), + ("tag".into(), red().light().step_7().into()), + ("text.literal".into(), red().light().step_7().into()), + ("title".into(), red().light().step_7().into()), + ("type".into(), red().light().step_7().into()), + ("variable".into(), red().light().step_7().into()), + ("variable.special".into(), red().light().step_7().into()), + ("variant".into(), red().light().step_7().into()), ], } } @@ -132,45 +144,54 @@ impl SyntaxTheme { pub fn default_dark() -> Self { Self { highlights: vec![ - ("attribute".into(), cyan().dark(11).into()), - ("boolean".into(), tomato().dark(11).into()), - ("comment".into(), neutral().dark(11).into()), - ("comment.doc".into(), iris().dark(12).into()), - ("constant".into(), red().dark(7).into()), - ("constructor".into(), red().dark(7).into()), - ("embedded".into(), red().dark(7).into()), - ("emphasis".into(), red().dark(7).into()), - ("emphasis.strong".into(), red().dark(7).into()), - ("enum".into(), red().dark(7).into()), - ("function".into(), red().dark(7).into()), - ("hint".into(), red().dark(7).into()), - ("keyword".into(), orange().dark(11).into()), - ("label".into(), red().dark(7).into()), - ("link_text".into(), red().dark(7).into()), - ("link_uri".into(), red().dark(7).into()), - ("number".into(), red().dark(7).into()), - ("operator".into(), red().dark(7).into()), - ("predictive".into(), red().dark(7).into()), - ("preproc".into(), red().dark(7).into()), - ("primary".into(), red().dark(7).into()), - ("property".into(), red().dark(7).into()), - ("punctuation".into(), neutral().dark(11).into()), - ("punctuation.bracket".into(), neutral().dark(11).into()), - ("punctuation.delimiter".into(), neutral().dark(11).into()), - ("punctuation.list_marker".into(), blue().dark(11).into()), - ("punctuation.special".into(), red().dark(7).into()), - ("string".into(), jade().dark(11).into()), - ("string.escape".into(), red().dark(7).into()), - ("string.regex".into(), tomato().dark(11).into()), - ("string.special".into(), red().dark(7).into()), - ("string.special.symbol".into(), red().dark(7).into()), - ("tag".into(), red().dark(7).into()), - ("text.literal".into(), red().dark(7).into()), - ("title".into(), red().dark(7).into()), - ("type".into(), red().dark(7).into()), - ("variable".into(), red().dark(7).into()), - ("variable.special".into(), red().dark(7).into()), - ("variant".into(), red().dark(7).into()), + ("attribute".into(), cyan().dark().step_11().into()), + ("boolean".into(), tomato().dark().step_11().into()), + ("comment".into(), neutral().dark().step_11().into()), + ("comment.doc".into(), iris().dark().step_12().into()), + ("constant".into(), red().dark().step_7().into()), + ("constructor".into(), red().dark().step_7().into()), + ("embedded".into(), red().dark().step_7().into()), + ("emphasis".into(), red().dark().step_7().into()), + ("emphasis.strong".into(), red().dark().step_7().into()), + ("enum".into(), red().dark().step_7().into()), + ("function".into(), red().dark().step_7().into()), + ("hint".into(), red().dark().step_7().into()), + ("keyword".into(), orange().dark().step_11().into()), + ("label".into(), red().dark().step_7().into()), + ("link_text".into(), red().dark().step_7().into()), + ("link_uri".into(), red().dark().step_7().into()), + ("number".into(), red().dark().step_7().into()), + ("operator".into(), red().dark().step_7().into()), + ("predictive".into(), red().dark().step_7().into()), + ("preproc".into(), red().dark().step_7().into()), + ("primary".into(), red().dark().step_7().into()), + ("property".into(), red().dark().step_7().into()), + ("punctuation".into(), neutral().dark().step_11().into()), + ( + "punctuation.bracket".into(), + neutral().dark().step_11().into(), + ), + ( + "punctuation.delimiter".into(), + neutral().dark().step_11().into(), + ), + ( + "punctuation.list_marker".into(), + blue().dark().step_11().into(), + ), + ("punctuation.special".into(), red().dark().step_7().into()), + ("string".into(), jade().dark().step_11().into()), + ("string.escape".into(), red().dark().step_7().into()), + ("string.regex".into(), tomato().dark().step_11().into()), + ("string.special".into(), red().dark().step_7().into()), + ("string.special.symbol".into(), red().dark().step_7().into()), + ("tag".into(), red().dark().step_7().into()), + ("text.literal".into(), red().dark().step_7().into()), + ("title".into(), red().dark().step_7().into()), + ("type".into(), red().dark().step_7().into()), + ("variable".into(), red().dark().step_7().into()), + ("variable.special".into(), red().dark().step_7().into()), + ("variant".into(), red().dark().step_7().into()), ], } } @@ -181,44 +202,44 @@ impl ThemeColors { let system = SystemColors::default(); Self { - border: neutral().light(6).into(), - border_variant: neutral().light(5).into(), - border_focused: blue().light(5).into(), + border: neutral().light().step_6().into(), + border_variant: neutral().light().step_5().into(), + border_focused: blue().light().step_5().into(), border_transparent: system.transparent, - elevated_surface: neutral().light(2).into(), - surface: neutral().light(2).into(), - background: neutral().light(1).into(), - element: neutral().light(3).into(), - element_hover: neutral().light(4).into(), - element_active: neutral().light(5).into(), - element_selected: neutral().light(5).into(), - element_disabled: neutral().light_alpha(3).into(), - element_placeholder: neutral().light(11).into(), - element_drop_target: blue().light_alpha(2).into(), + elevated_surface: neutral().light().step_2().into(), + surface: neutral().light().step_2().into(), + background: neutral().light().step_1().into(), + element: neutral().light().step_3().into(), + element_hover: neutral().light().step_4().into(), + element_active: neutral().light().step_5().into(), + element_selected: neutral().light().step_5().into(), + element_disabled: neutral().light_alpha().step_3().into(), + element_placeholder: neutral().light().step_11().into(), + element_drop_target: blue().light_alpha().step_2().into(), ghost_element: system.transparent, - ghost_element_hover: neutral().light(4).into(), - ghost_element_active: neutral().light(5).into(), - ghost_element_selected: neutral().light(5).into(), - ghost_element_disabled: neutral().light_alpha(3).into(), - text: neutral().light(12).into(), - text_muted: neutral().light(11).into(), - text_placeholder: neutral().light(10).into(), - text_disabled: neutral().light(9).into(), - text_accent: blue().light(11).into(), - icon: neutral().light(11).into(), - icon_muted: neutral().light(10).into(), - icon_disabled: neutral().light(9).into(), - icon_placeholder: neutral().light(10).into(), - icon_accent: blue().light(11).into(), - status_bar: neutral().light(2).into(), - title_bar: neutral().light(2).into(), - toolbar: neutral().light(1).into(), - tab_bar: neutral().light(2).into(), - tab_active: neutral().light(1).into(), - tab_inactive: neutral().light(2).into(), - editor: neutral().light(1).into(), - editor_subheader: neutral().light(2).into(), - editor_active_line: neutral().light_alpha(3).into(), + ghost_element_hover: neutral().light().step_4().into(), + ghost_element_active: neutral().light().step_5().into(), + ghost_element_selected: neutral().light().step_5().into(), + ghost_element_disabled: neutral().light_alpha().step_3().into(), + text: neutral().light().step_12().into(), + text_muted: neutral().light().step_11().into(), + text_placeholder: neutral().light().step_10().into(), + text_disabled: neutral().light().step_9().into(), + text_accent: blue().light().step_11().into(), + icon: neutral().light().step_11().into(), + icon_muted: neutral().light().step_10().into(), + icon_disabled: neutral().light().step_9().into(), + icon_placeholder: neutral().light().step_10().into(), + icon_accent: blue().light().step_11().into(), + status_bar: neutral().light().step_2().into(), + title_bar: neutral().light().step_2().into(), + toolbar: neutral().light().step_1().into(), + tab_bar: neutral().light().step_2().into(), + tab_active: neutral().light().step_1().into(), + tab_inactive: neutral().light().step_2().into(), + editor: neutral().light().step_1().into(), + editor_subheader: neutral().light().step_2().into(), + editor_active_line: neutral().light_alpha().step_3().into(), } } @@ -226,44 +247,44 @@ impl ThemeColors { let system = SystemColors::default(); Self { - border: neutral().dark(6).into(), - border_variant: neutral().dark(5).into(), - border_focused: blue().dark(5).into(), + border: neutral().dark().step_6().into(), + border_variant: neutral().dark().step_5().into(), + border_focused: blue().dark().step_5().into(), border_transparent: system.transparent, - elevated_surface: neutral().dark(2).into(), - surface: neutral().dark(2).into(), - background: neutral().dark(1).into(), - element: neutral().dark(3).into(), - element_hover: neutral().dark(4).into(), - element_active: neutral().dark(5).into(), - element_selected: neutral().dark(5).into(), - element_disabled: neutral().dark_alpha(3).into(), - element_placeholder: neutral().dark(11).into(), - element_drop_target: blue().dark_alpha(2).into(), + elevated_surface: neutral().dark().step_2().into(), + surface: neutral().dark().step_2().into(), + background: neutral().dark().step_1().into(), + element: neutral().dark().step_3().into(), + element_hover: neutral().dark().step_4().into(), + element_active: neutral().dark().step_5().into(), + element_selected: neutral().dark().step_5().into(), + element_disabled: neutral().dark_alpha().step_3().into(), + element_placeholder: neutral().dark().step_11().into(), + element_drop_target: blue().dark_alpha().step_2().into(), ghost_element: system.transparent, - ghost_element_hover: neutral().dark(4).into(), - ghost_element_active: neutral().dark(5).into(), - ghost_element_selected: neutral().dark(5).into(), - ghost_element_disabled: neutral().dark_alpha(3).into(), - text: neutral().dark(12).into(), - text_muted: neutral().dark(11).into(), - text_placeholder: neutral().dark(10).into(), - text_disabled: neutral().dark(9).into(), - text_accent: blue().dark(11).into(), - icon: neutral().dark(11).into(), - icon_muted: neutral().dark(10).into(), - icon_disabled: neutral().dark(9).into(), - icon_placeholder: neutral().dark(10).into(), - icon_accent: blue().dark(11).into(), - status_bar: neutral().dark(2).into(), - title_bar: neutral().dark(2).into(), - toolbar: neutral().dark(1).into(), - tab_bar: neutral().dark(2).into(), - tab_active: neutral().dark(1).into(), - tab_inactive: neutral().dark(2).into(), - editor: neutral().dark(1).into(), - editor_subheader: neutral().dark(2).into(), - editor_active_line: neutral().dark_alpha(3).into(), + ghost_element_hover: neutral().dark().step_4().into(), + ghost_element_active: neutral().dark().step_5().into(), + ghost_element_selected: neutral().dark().step_5().into(), + ghost_element_disabled: neutral().dark_alpha().step_3().into(), + text: neutral().dark().step_12().into(), + text_muted: neutral().dark().step_11().into(), + text_placeholder: neutral().dark().step_10().into(), + text_disabled: neutral().dark().step_9().into(), + text_accent: blue().dark().step_11().into(), + icon: neutral().dark().step_11().into(), + icon_muted: neutral().dark().step_10().into(), + icon_disabled: neutral().dark().step_9().into(), + icon_placeholder: neutral().dark().step_10().into(), + icon_accent: blue().dark().step_11().into(), + status_bar: neutral().dark().step_2().into(), + title_bar: neutral().dark().step_2().into(), + toolbar: neutral().dark().step_1().into(), + tab_bar: neutral().dark().step_2().into(), + tab_active: neutral().dark().step_1().into(), + tab_inactive: neutral().dark().step_2().into(), + editor: neutral().dark().step_1().into(), + editor_subheader: neutral().dark().step_2().into(), + editor_active_line: neutral().dark_alpha().step_3().into(), } } } From aa14552feec49059729337630330f5a44c7cae45 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 22:01:59 -0400 Subject: [PATCH 088/156] Remove unneeded `.into`s --- crates/theme2/src/default_colors.rs | 166 ++++++++++++++-------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 15f4d6548e..8b7137683c 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -27,17 +27,17 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: red().dark().step_11().into(), - created: grass().dark().step_11().into(), - deleted: red().dark().step_11().into(), - error: red().dark().step_11().into(), - hidden: neutral().dark().step_11().into(), - ignored: neutral().dark().step_11().into(), - info: blue().dark().step_11().into(), - modified: yellow().dark().step_11().into(), - renamed: blue().dark().step_11().into(), - success: grass().dark().step_11().into(), - warning: yellow().dark().step_11().into(), + conflict: red().dark().step_11(), + created: grass().dark().step_11(), + deleted: red().dark().step_11(), + error: red().dark().step_11(), + hidden: neutral().dark().step_11(), + ignored: neutral().dark().step_11(), + info: blue().dark().step_11(), + modified: yellow().dark().step_11(), + renamed: blue().dark().step_11(), + success: grass().dark().step_11(), + warning: yellow().dark().step_11(), } } } @@ -202,44 +202,44 @@ impl ThemeColors { let system = SystemColors::default(); Self { - border: neutral().light().step_6().into(), - border_variant: neutral().light().step_5().into(), - border_focused: blue().light().step_5().into(), + border: neutral().light().step_6(), + border_variant: neutral().light().step_5(), + border_focused: blue().light().step_5(), border_transparent: system.transparent, - elevated_surface: neutral().light().step_2().into(), - surface: neutral().light().step_2().into(), - background: neutral().light().step_1().into(), - element: neutral().light().step_3().into(), - element_hover: neutral().light().step_4().into(), - element_active: neutral().light().step_5().into(), - element_selected: neutral().light().step_5().into(), - element_disabled: neutral().light_alpha().step_3().into(), - element_placeholder: neutral().light().step_11().into(), - element_drop_target: blue().light_alpha().step_2().into(), + elevated_surface: neutral().light().step_2(), + surface: neutral().light().step_2(), + background: neutral().light().step_1(), + element: neutral().light().step_3(), + element_hover: neutral().light().step_4(), + element_active: neutral().light().step_5(), + element_selected: neutral().light().step_5(), + element_disabled: neutral().light_alpha().step_3(), + element_placeholder: neutral().light().step_11(), + element_drop_target: blue().light_alpha().step_2(), ghost_element: system.transparent, - ghost_element_hover: neutral().light().step_4().into(), - ghost_element_active: neutral().light().step_5().into(), - ghost_element_selected: neutral().light().step_5().into(), - ghost_element_disabled: neutral().light_alpha().step_3().into(), - text: neutral().light().step_12().into(), - text_muted: neutral().light().step_11().into(), - text_placeholder: neutral().light().step_10().into(), - text_disabled: neutral().light().step_9().into(), - text_accent: blue().light().step_11().into(), - icon: neutral().light().step_11().into(), - icon_muted: neutral().light().step_10().into(), - icon_disabled: neutral().light().step_9().into(), - icon_placeholder: neutral().light().step_10().into(), - icon_accent: blue().light().step_11().into(), - status_bar: neutral().light().step_2().into(), - title_bar: neutral().light().step_2().into(), - toolbar: neutral().light().step_1().into(), - tab_bar: neutral().light().step_2().into(), - tab_active: neutral().light().step_1().into(), - tab_inactive: neutral().light().step_2().into(), - editor: neutral().light().step_1().into(), - editor_subheader: neutral().light().step_2().into(), - editor_active_line: neutral().light_alpha().step_3().into(), + ghost_element_hover: neutral().light().step_4(), + ghost_element_active: neutral().light().step_5(), + ghost_element_selected: neutral().light().step_5(), + ghost_element_disabled: neutral().light_alpha().step_3(), + text: neutral().light().step_12(), + text_muted: neutral().light().step_11(), + text_placeholder: neutral().light().step_10(), + text_disabled: neutral().light().step_9(), + text_accent: blue().light().step_11(), + icon: neutral().light().step_11(), + icon_muted: neutral().light().step_10(), + icon_disabled: neutral().light().step_9(), + icon_placeholder: neutral().light().step_10(), + icon_accent: blue().light().step_11(), + status_bar: neutral().light().step_2(), + title_bar: neutral().light().step_2(), + toolbar: neutral().light().step_1(), + tab_bar: neutral().light().step_2(), + tab_active: neutral().light().step_1(), + tab_inactive: neutral().light().step_2(), + editor: neutral().light().step_1(), + editor_subheader: neutral().light().step_2(), + editor_active_line: neutral().light_alpha().step_3(), } } @@ -247,44 +247,44 @@ impl ThemeColors { let system = SystemColors::default(); Self { - border: neutral().dark().step_6().into(), - border_variant: neutral().dark().step_5().into(), - border_focused: blue().dark().step_5().into(), + border: neutral().dark().step_6(), + border_variant: neutral().dark().step_5(), + border_focused: blue().dark().step_5(), border_transparent: system.transparent, - elevated_surface: neutral().dark().step_2().into(), - surface: neutral().dark().step_2().into(), - background: neutral().dark().step_1().into(), - element: neutral().dark().step_3().into(), - element_hover: neutral().dark().step_4().into(), - element_active: neutral().dark().step_5().into(), - element_selected: neutral().dark().step_5().into(), - element_disabled: neutral().dark_alpha().step_3().into(), - element_placeholder: neutral().dark().step_11().into(), - element_drop_target: blue().dark_alpha().step_2().into(), + elevated_surface: neutral().dark().step_2(), + surface: neutral().dark().step_2(), + background: neutral().dark().step_1(), + element: neutral().dark().step_3(), + element_hover: neutral().dark().step_4(), + element_active: neutral().dark().step_5(), + element_selected: neutral().dark().step_5(), + element_disabled: neutral().dark_alpha().step_3(), + element_placeholder: neutral().dark().step_11(), + element_drop_target: blue().dark_alpha().step_2(), ghost_element: system.transparent, - ghost_element_hover: neutral().dark().step_4().into(), - ghost_element_active: neutral().dark().step_5().into(), - ghost_element_selected: neutral().dark().step_5().into(), - ghost_element_disabled: neutral().dark_alpha().step_3().into(), - text: neutral().dark().step_12().into(), - text_muted: neutral().dark().step_11().into(), - text_placeholder: neutral().dark().step_10().into(), - text_disabled: neutral().dark().step_9().into(), - text_accent: blue().dark().step_11().into(), - icon: neutral().dark().step_11().into(), - icon_muted: neutral().dark().step_10().into(), - icon_disabled: neutral().dark().step_9().into(), - icon_placeholder: neutral().dark().step_10().into(), - icon_accent: blue().dark().step_11().into(), - status_bar: neutral().dark().step_2().into(), - title_bar: neutral().dark().step_2().into(), - toolbar: neutral().dark().step_1().into(), - tab_bar: neutral().dark().step_2().into(), - tab_active: neutral().dark().step_1().into(), - tab_inactive: neutral().dark().step_2().into(), - editor: neutral().dark().step_1().into(), - editor_subheader: neutral().dark().step_2().into(), - editor_active_line: neutral().dark_alpha().step_3().into(), + ghost_element_hover: neutral().dark().step_4(), + ghost_element_active: neutral().dark().step_5(), + ghost_element_selected: neutral().dark().step_5(), + ghost_element_disabled: neutral().dark_alpha().step_3(), + text: neutral().dark().step_12(), + text_muted: neutral().dark().step_11(), + text_placeholder: neutral().dark().step_10(), + text_disabled: neutral().dark().step_9(), + text_accent: blue().dark().step_11(), + icon: neutral().dark().step_11(), + icon_muted: neutral().dark().step_10(), + icon_disabled: neutral().dark().step_9(), + icon_placeholder: neutral().dark().step_10(), + icon_accent: blue().dark().step_11(), + status_bar: neutral().dark().step_2(), + title_bar: neutral().dark().step_2(), + toolbar: neutral().dark().step_1(), + tab_bar: neutral().dark().step_2(), + tab_active: neutral().dark().step_1(), + tab_inactive: neutral().dark().step_2(), + editor: neutral().dark().step_1(), + editor_subheader: neutral().dark().step_2(), + editor_active_line: neutral().dark_alpha().step_3(), } } } From 53066df52200225d732c298cdf130256cade64a9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 20:14:40 -0600 Subject: [PATCH 089/156] Get project2 tests green --- crates/gpui2/src/app/test_context.rs | 44 ++++++++++++++++------------ crates/project2/src/project_tests.rs | 5 ++-- crates/project2/src/worktree.rs | 1 + crates/ui2/src/components/panes.rs | 2 +- crates/ui2/src/components/tab.rs | 2 +- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index d4a63c4e37..d7bf2b7087 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -3,8 +3,8 @@ use crate::{ ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; -use anyhow::anyhow; -use futures::{SinkExt, StreamExt}; +use anyhow::{anyhow, bail}; +use futures::{SinkExt, Stream, StreamExt}; use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] @@ -140,7 +140,25 @@ impl TestAppContext { } } - pub fn subscribe( + pub fn notifications(&mut self, entity: &Model) -> impl Stream { + let (tx, rx) = futures::channel::mpsc::unbounded(); + + entity.update(self, move |_, cx: &mut ModelContext| { + cx.observe(entity, { + let tx = tx.clone(); + move |_, _, _| { + let _ = tx.unbounded_send(()); + } + }) + .detach(); + + cx.on_release(move |_, _| tx.close_channel()).detach(); + }); + + rx + } + + pub fn events( &mut self, entity: &Model, ) -> futures::channel::mpsc::UnboundedReceiver @@ -160,36 +178,24 @@ impl TestAppContext { rx } - pub async fn condition( + pub async fn condition( &mut self, model: &Model, mut predicate: impl FnMut(&mut T, &mut ModelContext) -> bool, ) { - let (mut tx, mut rx) = futures::channel::mpsc::unbounded::<()>(); let timer = self.executor().timer(Duration::from_secs(3)); - - let subscriptions = model.update(self, move |_, cx| { - ( - cx.observe(model, move |_, _, _| { - // let _ = tx.send(()); - }), - cx.subscribe(model, move |_, _, _, _| { - let _ = tx.send(()); - }), - ) - }); + let mut notifications = self.notifications(model); use futures::FutureExt as _; use smol::future::FutureExt as _; async { - while rx.next().await.is_some() { + while notifications.next().await.is_some() { if model.update(self, &mut predicate) { return Ok(()); } } - drop(subscriptions); - unreachable!() + bail!("model dropped") } .race(timer.map(|_| Err(anyhow!("condition timed out")))) .await diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index ca6cdbccfc..5a2f82c375 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -947,7 +947,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui2::TestAppContext) { .await .unwrap(); - let mut events = cx.subscribe(&project); + let mut events = cx.events(&project); let fake_server = fake_servers.next().await.unwrap(); assert_eq!( @@ -1078,7 +1078,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui2::TestApp project.update(cx, |project, cx| { project.restart_language_servers_for_buffers([buffer], cx); }); - let mut events = cx.subscribe(&project); + let mut events = cx.events(&project); // Simulate the newly started server sending more diagnostics. let fake_server = fake_servers.next().await.unwrap(); @@ -2788,6 +2788,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { }); let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx)); + cx.executor().run_until_parked(); cx.update(|cx| { diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index f146bf7948..060fefe6b3 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -4065,6 +4065,7 @@ impl WorktreeModelHandle for Model { fs.create_file(&root_path.join(file_name), Default::default()) .await .unwrap(); + cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some()) .await; diff --git a/crates/ui2/src/components/panes.rs b/crates/ui2/src/components/panes.rs index 854786ebaa..5318e3f3bb 100644 --- a/crates/ui2/src/components/panes.rs +++ b/crates/ui2/src/components/panes.rs @@ -51,7 +51,7 @@ impl Pane { .id("drag-target") .drag_over::(|d| d.bg(red())) .on_drop(|_, files: View, cx| { - dbg!("dropped files!", files.read(cx)); + eprintln!("dropped files! {:?}", files.read(cx)); }) .absolute() .inset_0(), diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index d784ec0174..c89ed2d7eb 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -130,7 +130,7 @@ impl Tab { .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone())) .drag_over::(|d| d.bg(black())) .on_drop(|_view, state: View, cx| { - dbg!(state.read(cx)); + eprintln!("{:?}", state.read(cx)); }) .px_2() .py_0p5() From 57dfc5068760f72416d8194e7de3f61628d3d4d5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 21:04:17 -0600 Subject: [PATCH 090/156] Get language2 tests passing by not blocking on a foreground task --- crates/gpui2/src/app/test_context.rs | 10 ++++------ crates/gpui2_macros/src/derive_component.rs | 4 ---- crates/language2/src/buffer.rs | 2 +- crates/language2/src/buffer_tests.rs | 1 + 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index d7bf2b7087..6affabdd23 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -4,7 +4,7 @@ use crate::{ WindowContext, }; use anyhow::{anyhow, bail}; -use futures::{SinkExt, Stream, StreamExt}; +use futures::{Stream, StreamExt}; use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] @@ -165,13 +165,11 @@ impl TestAppContext { where T::Event: 'static + Clone, { - let (mut tx, rx) = futures::channel::mpsc::unbounded(); + let (tx, rx) = futures::channel::mpsc::unbounded(); entity .update(self, |_, cx: &mut ModelContext| { - cx.subscribe(entity, move |_, _, event, cx| { - cx.background_executor() - .block(tx.send(event.clone())) - .unwrap(); + cx.subscribe(entity, move |_model, _handle, event, _cx| { + let _ = tx.unbounded_send(event.clone()); }) }) .detach(); diff --git a/crates/gpui2_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index b5887e5620..d1919c8bc4 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -36,10 +36,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { } }; - if name == "CollabPanel" { - println!("{}", expanded) - } - TokenStream::from(expanded) } diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index d8e0149460..3999f275f2 100644 --- a/crates/language2/src/buffer.rs +++ b/crates/language2/src/buffer.rs @@ -434,7 +434,7 @@ impl Buffer { )); let text_operations = self.text.operations().clone(); - cx.spawn(|_| async move { + cx.background_executor().spawn(async move { let since = since.unwrap_or_default(); operations.extend( text_operations diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index 2012509878..16306fe2ce 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/crates/language2/src/buffer_tests.rs @@ -1943,6 +1943,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { .detach(); buffer }); + buffers.push(buffer); replica_ids.push(i as ReplicaId); network.lock().add_peer(i as ReplicaId); From 2079cd641e9ebf9c73e2aaefd4bec246712e33eb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 21:16:41 -0600 Subject: [PATCH 091/156] Fix post-merge compile errors --- crates/call2/src/room.rs | 21 ++++++++++---------- crates/live_kit_client2/examples/test_app.rs | 4 ++-- crates/live_kit_client2/src/test.rs | 6 +++--- crates/multi_buffer2/src/multi_buffer2.rs | 4 ++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index ea4adf0385..deeec1df24 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -134,7 +134,7 @@ impl Room { } }); - let _maintain_video_tracks = cx.spawn_on_main({ + let _maintain_video_tracks = cx.spawn({ let room = room.clone(); move |this, mut cx| async move { let mut track_video_changes = room.remote_video_track_updates(); @@ -153,7 +153,7 @@ impl Room { } }); - let _maintain_audio_tracks = cx.spawn_on_main({ + let _maintain_audio_tracks = cx.spawn({ let room = room.clone(); |this, mut cx| async move { let mut track_audio_changes = room.remote_audio_track_updates(); @@ -1301,7 +1301,9 @@ impl Room { live_kit.room.unpublish_track(publication); } else { if muted { - cx.executor().spawn(publication.set_mute(muted)).detach(); + cx.background_executor() + .spawn(publication.set_mute(muted)) + .detach(); } live_kit.microphone_track = LocalTrack::Published { track_publication: publication, @@ -1344,7 +1346,7 @@ impl Room { return Task::ready(Err(anyhow!("live-kit was not initialized"))); }; - cx.spawn_on_main(move |this, mut cx| async move { + cx.spawn(move |this, mut cx| async move { let publish_track = async { let displays = displays.await?; let display = displays @@ -1387,7 +1389,9 @@ impl Room { live_kit.room.unpublish_track(publication); } else { if muted { - cx.executor().spawn(publication.set_mute(muted)).detach(); + cx.background_executor() + .spawn(publication.set_mute(muted)) + .detach(); } live_kit.screen_track = LocalTrack::Published { track_publication: publication, @@ -1454,14 +1458,11 @@ impl Room { .remote_audio_track_publications(&participant.user.id.to_string()) { let deafened = live_kit.deafened; - tasks.push( - cx.executor() - .spawn_on_main(move || track.set_enabled(!deafened)), - ); + tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened))); } } - Ok(cx.executor().spawn_on_main(|| async { + Ok(cx.foreground_executor().spawn(async move { if let Some(mute_task) = mute_task { mute_task.await?; } diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs index ad10a4c95d..4062441a06 100644 --- a/crates/live_kit_client2/examples/test_app.rs +++ b/crates/live_kit_client2/examples/test_app.rs @@ -42,7 +42,7 @@ fn main() { let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); - cx.spawn_on_main(|cx| async move { + cx.spawn(|cx| async move { let user_a_token = token::create( &live_kit_key, &live_kit_secret, @@ -104,7 +104,7 @@ fn main() { } println!("Pausing for 5 seconds to test audio, make some noise!"); - let timer = cx.executor().timer(Duration::from_secs(5)); + let timer = cx.background_executor().timer(Duration::from_secs(5)); timer.await; let remote_audio_track = room_b .remote_audio_tracks("test-participant-1") diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index f1c3d39b8e..10c97e8d81 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/crates/live_kit_client2/src/test.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; -use gpui2::Executor; +use gpui2::BackgroundExecutor; use live_kit_server::token; use media::core_video::CVImageBuffer; use parking_lot::Mutex; @@ -16,7 +16,7 @@ pub struct TestServer { pub api_key: String, pub secret_key: String, rooms: Mutex>, - executor: Arc, + executor: Arc, } impl TestServer { @@ -24,7 +24,7 @@ impl TestServer { url: String, api_key: String, secret_key: String, - executor: Arc, + executor: Arc, ) -> Result> { let mut servers = SERVERS.lock(); if servers.contains_key(&url) { diff --git a/crates/multi_buffer2/src/multi_buffer2.rs b/crates/multi_buffer2/src/multi_buffer2.rs index c5827b8b13..b5a7ced517 100644 --- a/crates/multi_buffer2/src/multi_buffer2.rs +++ b/crates/multi_buffer2/src/multi_buffer2.rs @@ -878,7 +878,7 @@ impl MultiBuffer { cx.spawn(move |this, mut cx| async move { let mut excerpt_ranges = Vec::new(); let mut range_counts = Vec::new(); - cx.executor() + cx.background_executor() .scoped(|scope| { scope.spawn(async { let (ranges, counts) = @@ -4177,7 +4177,7 @@ mod tests { let guest_buffer = cx.build_model(|cx| { let state = host_buffer.read(cx).to_proto(); let ops = cx - .executor() + .background_executor() .block(host_buffer.read(cx).serialize_ops(None, cx)); let mut buffer = Buffer::from_proto(1, state, None).unwrap(); buffer From 64ee1bb7a5c16d804302eb49a142c20d828105fc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Nov 2023 21:19:06 -0600 Subject: [PATCH 092/156] Fix prod compile error --- crates/project2/src/project2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 1457bd41cc..05434b93b4 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -8649,7 +8649,7 @@ impl Project { .get(&(worktree, default_prettier_dir.to_path_buf())) .cloned(); let fs = Arc::clone(&self.fs); - cx.spawn_on_main(move |this, mut cx| async move { + cx.spawn(move |this, mut cx| async move { if let Some(previous_installation_process) = previous_installation_process { previous_installation_process.await; } From ff277009139b840f153f02a536019bd59c865d23 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:08:47 +0100 Subject: [PATCH 093/156] 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 094/156] 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 095/156] 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 096/156] 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 64ad8943ba699c9b8da54621eca68b87fcbb4312 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:41:49 +0100 Subject: [PATCH 097/156] Remove more Send bounds and simplify view rendering --- crates/gpui2/src/action.rs | 4 +- crates/gpui2/src/app.rs | 209 ++++++----- crates/gpui2/src/app/async_context.rs | 118 ++++-- crates/gpui2/src/app/entity_map.rs | 10 +- crates/gpui2/src/app/model_context.rs | 30 +- crates/gpui2/src/app/test_context.rs | 32 +- crates/gpui2/src/assets.rs | 2 +- crates/gpui2/src/element.rs | 6 +- crates/gpui2/src/elements/div.rs | 1 - crates/gpui2/src/elements/text.rs | 3 - crates/gpui2/src/focusable.rs | 12 +- crates/gpui2/src/geometry.rs | 12 + crates/gpui2/src/gpui2.rs | 62 ++- crates/gpui2/src/platform.rs | 66 +++- crates/gpui2/src/platform/mac/display.rs | 44 ++- crates/gpui2/src/view.rs | 188 ++-------- crates/gpui2/src/window.rs | 455 +++++++++++++++-------- 17 files changed, 720 insertions(+), 534 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 265ce59a02..48a9324b05 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, - BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, FocusEvent, FocusHandle, - FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, Pixels, Platform, Point, Render, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, - TextSystem, View, Window, WindowContext, WindowHandle, WindowId, + 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, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, + TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, + WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -114,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>, @@ -214,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(); @@ -255,37 +255,31 @@ impl AppContext { result } - pub(crate) fn read_window( - &self, - id: WindowId, - read: impl FnOnce(&WindowContext) -> R, - ) -> Result { - let window = self - .windows - .get(id) - .ok_or_else(|| anyhow!("window not found"))? - .as_ref() - .unwrap(); - Ok(read(&WindowContext::immutable(self, &window))) + pub fn windows(&self) -> Vec { + self.windows + .values() + .filter_map(|window| Some(window.as_ref()?.handle.clone())) + .collect() } 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::mutable(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); @@ -305,7 +299,7 @@ impl AppContext { 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 @@ -386,8 +380,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(); @@ -407,18 +404,18 @@ 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 { + dirty_window_handle.update(self, |_, cx| cx.draw()).unwrap(); } } @@ -435,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); } } @@ -446,27 +443,27 @@ impl AppContext { /// For now, we simply blur the window if this happens, but we may want to support invoking /// a window blur handler to restore focus to some logical element. 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| { - 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; + for window_handle in self.windows() { + 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(); } } @@ -483,30 +480,35 @@ impl AppContext { .retain(&emitter, |handler| handler(event.as_ref(), self)); } - fn apply_focus_changed_effect(&mut self, window_id: WindowId, focused: Option) { - self.update_window(window_id, |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); + fn apply_focus_changed_effect( + &mut self, + window_handle: AnyWindowHandle, + focused: Option, + ) { + 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) { @@ -680,6 +682,24 @@ impl AppContext { self.globals_by_type.insert(global_type, lease.global); } + pub fn observe_release( + &mut self, + handle: &E, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, + ) -> Subscription + where + E: Entity, + T: 'static, + { + 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); } @@ -733,7 +753,6 @@ impl AppContext { } impl Context for AppContext { - 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 @@ -741,11 +760,11 @@ 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(); - 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) }) } @@ -755,18 +774,38 @@ 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); - 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 }) } + + fn update_window(&mut self, handle: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + self.update(|cx| { + let mut window = cx + .windows + .get_mut(handle.id) + .ok_or_else(|| anyhow!("window not found"))? + .take() + .unwrap(); + + 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); + + Ok(result) + }) + } } /// These effects are processed at the end of each application update cycle. @@ -779,7 +818,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 fb941b91b8..01af7ae194 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,8 +1,8 @@ use crate::{ - AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, - ModelContext, Result, Task, WindowContext, + AnyView, AnyWindowHandle, AppContext, BackgroundExecutor, Context, ForegroundExecutor, Model, + ModelContext, Render, Result, Task, View, ViewContext, VisualContext, WindowContext, }; -use anyhow::anyhow; +use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; use std::{cell::RefCell, future::Future, rc::Weak}; @@ -14,12 +14,11 @@ pub struct AsyncAppContext { } impl Context for AsyncAppContext { - 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, @@ -35,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 @@ -44,6 +43,15 @@ impl Context for AsyncAppContext { let mut app = app.borrow_mut(); Ok(app.update_model(handle, update)) } + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + let app = self.app.upgrade().context("app was released")?; + let mut lock = app.borrow_mut(); + lock.update_window(window, f) + } } impl AsyncAppContext { @@ -74,30 +82,17 @@ impl AsyncAppContext { Ok(f(&mut *lock)) } - pub fn read_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(&WindowContext) -> R, - ) -> Result { - let app = self - .app - .upgrade() - .ok_or_else(|| anyhow!("app was released"))?; - let app_context = app.borrow(); - app_context.read_window(handle.id, update) - } - 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 @@ -161,22 +156,22 @@ 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)) - .ok(); + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { + self.window.update(self, |_, cx| cx.on_next_frame(f)).ok(); } pub fn read_global( - &self, + &mut self, read: impl FnOnce(&G, &WindowContext) -> R, ) -> Result { - self.app - .read_window(self.window, |cx| read(cx.global(), cx)) + self.window.update(self, |_, cx| read(cx.global(), cx)) } pub fn update_global( @@ -186,32 +181,79 @@ 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(&self, f: impl FnOnce(AsyncWindowContext) -> Fut + 'static) -> Task + where + Fut: Future + 'static, + R: 'static, + { + let this = self.clone(); + self.foreground_executor.spawn(async move { f(this).await }) } } impl Context for AsyncWindowContext { - 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, { - 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( &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.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 + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + self.app.update_window(window, update) + } +} + +impl VisualContext for AsyncWindowContext { + fn build_view( + &mut self, + build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static, + { + self.window + .update(self, |_, cx| cx.build_view(build_view_state)) + } + + fn update_view( + &mut self, + view: &View, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, + ) -> Self::Result { + self.window + .update(self, |_, cx| cx.update_view(view, update)) + } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: Render, + { + self.window + .update(self, |_, cx| cx.replace_root_view(build_view)) } } diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index 4a4b178e1e..e626f8c409 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}; @@ -169,6 +169,10 @@ impl AnyModel { self.entity_id } + pub fn entity_type(&self) -> TypeId { + self.entity_type + } + pub fn downgrade(&self) -> AnyWeakModel { AnyWeakModel { entity_id: self.entity_id, @@ -329,7 +333,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, @@ -475,7 +479,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 f6982cdc1f..cb25adfb63 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, Model, Reference, - Subscription, Task, WeakModel, + AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, + EventEmitter, Model, Subscription, Task, WeakModel, WindowContext, }; +use anyhow::Result; use derive_more::{Deref, DerefMut}; use futures::FutureExt; use std::{ @@ -14,16 +15,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 { @@ -95,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, @@ -112,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, @@ -215,12 +213,11 @@ where } impl<'a, T> Context for ModelContext<'a, T> { - 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) } @@ -228,10 +225,17 @@ 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 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 6affabdd23..27275d3a04 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,7 +1,7 @@ use crate::{ - AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, - ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, - WindowContext, + AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, + EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, + TestPlatform, WindowContext, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -15,12 +15,11 @@ pub struct TestAppContext { } impl Context for TestAppContext { - 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, @@ -32,11 +31,19 @@ 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) } + + fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + let mut lock = self.app.borrow_mut(); + lock.update_window(window, f) + } } impl TestAppContext { @@ -80,22 +87,13 @@ impl TestAppContext { cx.update(f) } - pub fn read_window( - &self, - handle: AnyWindowHandle, - read: impl FnOnce(&WindowContext) -> R, - ) -> R { - let app_context = self.app.borrow(); - app_context.read_window(handle.id, read).unwrap() - } - 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/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/element.rs b/crates/gpui2/src/element.rs index e41dc4ebd5..4890b79a9a 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -219,7 +219,7 @@ impl Element for Option where V: 'static, E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { type ElementState = AnyElement; @@ -263,7 +263,7 @@ impl Component for Option where V: 'static, E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -274,7 +274,7 @@ impl Component for F where V: 'static, E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + 'static, + F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, { fn render(self) -> AnyElement { AnyElement::new(Some(self)) 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/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/gpui2.rs b/crates/gpui2/src/gpui2.rs index 755df91b93..49cc3cebc2 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -68,34 +68,36 @@ use derive_more::{Deref, DerefMut}; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - ops::{Deref, DerefMut}, }; use taffy::TaffyLayoutEngine; type AnyBox = Box; pub trait Context { - 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( + fn update_model( &mut self, handle: &Model, - update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R, - ) -> Self::Result; + 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 WindowContext<'_>) -> T; } pub trait VisualContext: Context { - type ViewContext<'a, 'w, V>; - fn build_view( &mut self, - build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where V: 'static; @@ -103,12 +105,19 @@ 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 ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: Render; } pub trait Entity: Sealed { - type Weak: 'static + Send; + type Weak: 'static; fn entity_id(&self) -> EntityId; fn downgrade(&self) -> Self::Weak; @@ -128,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 @@ -145,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) } } @@ -208,30 +217,3 @@ impl>> From for SharedString { Self(value.into()) } } - -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, - } - } -} diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 705c2f83bb..2dd4d8b666 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -9,12 +9,14 @@ use crate::{ ForegroundExecutor, 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 parking::Unparker; 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; @@ -27,6 +29,7 @@ use std::{ str::FromStr, sync::Arc, }; +use uuid::Uuid; pub use keystroke::*; #[cfg(target_os = "macos")] @@ -106,6 +109,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; } @@ -372,6 +378,64 @@ 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) + } + }; + + statement.bind( + ®ion.map(|region| { + ( + region.origin.x, + region.origin.y, + region.size.width, + region.size.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/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/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 06b20a3088..3cc4fdd4e3 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,12 +1,12 @@ use crate::{ private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, - BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, LayoutId, Model, Pixels, - Size, ViewContext, VisualContext, WeakModel, WindowContext, + BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, + Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; use std::{ any::{Any, TypeId}, - marker::PhantomData, + hash::{Hash, Hasher}, }; pub trait Render: 'static + Sized { @@ -52,7 +52,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, @@ -73,55 +73,23 @@ impl Clone for View { } } -impl Component for View { - fn render(self) -> AnyElement { - AnyElement::new(EraseViewState { - view: self, - parent_view_state_type: PhantomData, - }) +impl Hash for View { + fn hash(&self, state: &mut H) { + self.model.hash(state); } } -impl Element<()> for View -where - V: Render, -{ - type ElementState = AnyElement; - - fn id(&self) -> Option { - Some(ElementId::View(self.model.entity_id)) +impl PartialEq for View { + fn eq(&self, other: &Self) -> bool { + self.model == other.model } +} - 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 - }) - } +impl Eq for View {} - 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)) +impl Component for View { + fn render(self) -> AnyElement { + AnyElement::new(AnyView::from(self)) } } @@ -134,13 +102,17 @@ impl WeakView { Entity::upgrade_from(self) } - 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 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() } } @@ -152,115 +124,19 @@ impl Clone 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 Hash for WeakView { + fn hash(&self, state: &mut H) { + self.model.hash(state); } } -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) +impl PartialEq for WeakView { + fn eq(&self, other: &Self) -> bool { + self.model == other.model } } -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() - } -} +impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { @@ -292,7 +168,7 @@ impl AnyView { } } - pub(crate) fn entity_type(&self) -> TypeId { + pub fn entity_type(&self) -> TypeId { self.model.entity_type } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e7cfcf45ee..5f2de2e428 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -5,11 +5,11 @@ use crate::{ Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, - Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, + Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakModel, WeakView, WindowOptions, SUBPIXEL_VARIANTS, + 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; @@ -20,6 +20,7 @@ use std::{ borrow::{Borrow, BorrowMut, Cow}, fmt::Debug, future::Future, + hash::{Hash, Hasher}, marker::PhantomData, mem, sync::{ @@ -156,7 +157,7 @@ impl Drop for FocusHandle { // Holds the state for a specific window. pub struct Window { - handle: AnyWindowHandle, + pub(crate) handle: AnyWindowHandle, platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, @@ -201,23 +202,25 @@ 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.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(); } })); 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)) + handle + .update(&mut cx, |_, cx| cx.dispatch_event(event)) .log_err() .unwrap_or(true) }) @@ -296,24 +299,14 @@ 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(crate) app: Reference<'a, AppContext>, - pub(crate) window: Reference<'w, Window>, +pub struct WindowContext<'a> { + pub(crate) app: &'a mut AppContext, + pub(crate) window: &'a mut 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 { - Self { - app: Reference::Mutable(app), - window: Reference::Mutable(window), - } +impl<'a> WindowContext<'a> { + 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. @@ -345,10 +338,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(); @@ -360,15 +352,53 @@ 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(); } + /// 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) { + let handle = self.window.handle; + self.app.defer(move |cx| { + handle.update(cx, |_, cx| f(cx)).ok(); + }); + } + + pub fn subscribe( + &mut self, + entity: &E, + mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + 'static, + ) -> Subscription + where + Emitter: EventEmitter, + E: Entity, + { + let entity_id = entity.entity_id(); + let entity = entity.downgrade(); + let window_handle = self.window.handle; + self.app.event_listeners.insert( + entity_id, + Box::new(move |event, cx| { + 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) + }), + ) + } + /// Create an `AsyncWindowContext`, which has a static lifetime and can be held across /// await points in async code. pub fn to_async(&self) -> AsyncWindowContext { @@ -376,7 +406,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { } /// 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; @@ -387,12 +417,12 @@ impl<'a, 'w> WindowContext<'a, 'w> { 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, 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) @@ -1114,12 +1144,12 @@ 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<'_>) + '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| window_handle.update(cx, |_, cx| f(cx)).is_ok()), ) } @@ -1202,43 +1232,52 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } -impl Context for WindowContext<'_, '_> { - type ModelContext<'a, T> = ModelContext<'a, T>; +impl Context for WindowContext<'_> { 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, { 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) } 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( &mut *entity, - &mut ModelContext::mutable(&mut *self.app, model.downgrade()), + &mut ModelContext::new(&mut *self.app, model.downgrade()), ); self.entities.end_lease(entity); result } + + fn update_window(&mut self, window: AnyWindowHandle, update: F) -> Result + where + F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, + { + if window == self.window.handle { + let root_view = self.window.root_view.clone().unwrap(); + Ok(update(root_view, self)) + } else { + window.update(self.app, update) + } + } } -impl VisualContext for WindowContext<'_, '_> { - type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>; - +impl VisualContext for WindowContext<'_> { 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, @@ -1247,7 +1286,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 @@ -1257,17 +1296,35 @@ 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::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 } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 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, 'w> std::ops::Deref for WindowContext<'a, 'w> { +impl<'a> std::ops::Deref for WindowContext<'a> { type Target = AppContext; fn deref(&self) -> &Self::Target { @@ -1275,19 +1332,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 } @@ -1455,13 +1512,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 } @@ -1469,52 +1526,48 @@ impl BorrowMut for WindowContext<'_, '_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} -pub struct ViewContext<'a, 'w, V> { - window_cx: WindowContext<'a, 'w>, - view: WeakView, +pub struct ViewContext<'a, V> { + window_cx: WindowContext<'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 mutable( - app: &'a mut AppContext, - window: &'w mut Window, - view: WeakView, - ) -> 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), + window_cx: WindowContext::new(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() } @@ -1525,40 +1578,50 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, 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().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) { + let view = self.view().downgrade(); + self.window_cx.defer(move |cx| { + view.update(cx, f).ok(); + }); + } + 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, - V: Any + Send, + V: 'static, 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; self.app.observers.insert( entity_id, Box::new(move |cx| { - cx.update_window(window_handle.id, |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) }), ) } @@ -1566,44 +1629,44 @@ 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>) + 'static, ) -> Subscription where 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; self.app.event_listeners.insert( entity_id, Box::new(move |event, cx| { - cx.update_window(window_handle.id, |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) }), ) } 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) + 'static, ) -> Subscription { let window_handle = self.window.handle; self.app.release_listeners.insert( 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 _ = cx.update_window(window_handle.id, |cx| on_release(this, cx)); + let _ = window_handle.update(cx, |_, cx| on_release(this, cx)); }), ) } @@ -1611,21 +1674,21 @@ 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>) + 'static, ) -> Subscription where - V: Any + Send, + V: 'static, 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( entity_id, Box::new(move |entity, cx| { let entity = entity.downcast_mut().expect("invalid entity type"); - let _ = cx.update_window(window_handle.id, |cx| { + let _ = window_handle.update(cx, |_, cx| { view.update(cx, |this, cx| on_release(this, entity, cx)) }); }), @@ -1641,9 +1704,9 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, 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(); + let handle = self.view().downgrade(); self.window.focus_listeners.push(Box::new(move |event, cx| { handle .update(cx, |view, cx| listener(view, event, cx)) @@ -1659,12 +1722,12 @@ 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], phase: DispatchPhase, - cx: &mut WindowContext<'_, '_>| { + cx: &mut WindowContext<'_>| { handle .update(cx, |view, cx| { listener(view, event, context_stack, phase, cx) @@ -1745,16 +1808,13 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { R: 'static, Fut: Future + 'static, { - let view = self.view(); - self.window_cx.spawn(move |_, cx| { - let result = f(view, cx); - async move { result.await } - }) + let view = self.view().downgrade(); + self.window_cx.spawn(move |_, cx| f(view, cx)) } 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); @@ -1764,17 +1824,16 @@ 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>) + 'static, ) -> Subscription { - let window_id = self.window.handle.id; - let handle = self.view(); + let window_handle = self.window.handle; + let view = self.view().downgrade(); self.global_observers.insert( TypeId::of::(), Box::new(move |cx| { - cx.update_window(window_id, |cx| { - handle.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) }), ) } @@ -1783,7 +1842,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> { &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + '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); @@ -1792,10 +1851,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, { pub fn emit(&mut self, event: V::Event) { let emitter = self.view.model.entity_id; @@ -1806,13 +1865,12 @@ where } } -impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> { - type ModelContext<'b, U> = ModelContext<'b, U>; +impl Context for ViewContext<'_, V> { 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) } @@ -1820,18 +1878,23 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, 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 WindowContext<'_>) -> T, + { + self.window_cx.update_window(window, update) + } } -impl VisualContext for ViewContext<'_, '_, V> { - type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>; - +impl VisualContext for ViewContext<'_, V> { 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) } @@ -1839,21 +1902,31 @@ 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 { self.window_cx.update_view(view, update) } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, + ) -> Self::Result> + where + W: Render, + { + self.window_cx.replace_root_view(build_view) + } } -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>; 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 } @@ -1868,42 +1941,74 @@ impl WindowId { } } -#[derive(PartialEq, Eq)] +#[derive(Deref, DerefMut)] pub struct WindowHandle { - id: WindowId, + #[deref] + #[deref_mut] + pub(crate) any_handle: AnyWindowHandle, state_type: PhantomData, } -impl Copy for WindowHandle {} - -impl Clone for WindowHandle { - fn clone(&self) -> Self { - WindowHandle { - id: self.id, - 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, + } + } + + pub fn update( + self, + cx: &mut C, + update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, + ) -> Result + where + 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(cx.update_view(&view, update)) + })? + } +} + +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 { - AnyWindowHandle { - id: self.id, - state_type: TypeId::of::(), - } +impl PartialEq for WindowHandle { + fn eq(&self, other: &Self) -> bool { + self.any_handle == other.any_handle } } -#[derive(Copy, Clone, PartialEq, Eq)] +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, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, state_type: TypeId, @@ -1913,6 +2018,28 @@ 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 + } + } + + pub fn update( + self, + cx: &mut C, + update: impl FnOnce(AnyView, &mut WindowContext<'_>) -> R, + ) -> Result + where + C: Context, + { + cx.update_window(self, update) + } } #[cfg(any(test, feature = "test-support"))] From 5e50430299006fee53dcecc5b7ba9133c0b2e397 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:47:21 +0100 Subject: [PATCH 098/156] Fix compile errors --- crates/feature_flags2/src/feature_flags2.rs | 2 +- crates/gpui2_macros/src/derive_component.rs | 2 +- 2 files changed, 2 insertions(+), 2 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_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 d5f0e91faa28bd2ea9b6680c7b5a919276ae0fde Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:56:45 +0100 Subject: [PATCH 099/156] Remove stray todo --- crates/gpui2/src/app/async_context.rs | 5 ++--- crates/gpui2/src/platform.rs | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 01af7ae194..f08c7ed0a9 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -184,13 +184,12 @@ impl AsyncWindowContext { self.window.update(self, |_, cx| cx.update_global(update)) } - pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut + 'static) -> Task + pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task where Fut: Future + 'static, R: 'static, { - let this = self.clone(); - self.foreground_executor.spawn(async move { f(this).await }) + self.foreground_executor.spawn(f(self.clone())) } } diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 2dd4d8b666..9a6768342f 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -5,9 +5,10 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, - ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, - RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, + point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, + FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, + Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, + SharedString, Size, }; use anyhow::{anyhow, bail}; use async_task::Runnable; @@ -422,12 +423,15 @@ impl Column for WindowBounds { "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!() + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + let x: f64 = x; + let y: f64 = y; + let width: f64 = width; + let height: f64 = height; + WindowBounds::Fixed(Bounds { + origin: point(x.into(), y.into()), + size: size(width.into(), height.into()), + }) } _ => bail!("Window State did not have a valid string"), }; From 9c7b45f38bdc140e8778543375ded0220478a5df Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 09:58:53 +0100 Subject: [PATCH 100/156] Add back Send and Sync to AssetSource --- crates/gpui2/src/assets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 51338d785ca4cfb2d16a5845cc6a87ac4ae64048 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 10:09:08 +0100 Subject: [PATCH 101/156] 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 32db64a049502d37c671f07e5c5b89c36d895a1b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 10:53:28 +0100 Subject: [PATCH 102/156] Introduce more GPUI2 APIs needed for transitioning the workspace --- crates/gpui2/src/app.rs | 77 +++++++++++-------------- crates/gpui2/src/app/async_context.rs | 16 +++-- crates/gpui2/src/app/test_context.rs | 9 --- crates/gpui2/src/platform.rs | 15 +---- crates/gpui2/src/platform/mac/window.rs | 15 ++--- crates/gpui2/src/view.rs | 4 ++ crates/gpui2/src/window.rs | 44 ++++++++------ 7 files changed, 80 insertions(+), 100 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 48a9324b05..5a6e360802 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -16,13 +16,13 @@ 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, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, - TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, - WindowId, + PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, + SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, + View, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; -use futures::{future::LocalBoxFuture, Future}; +use futures::{channel::oneshot, future::LocalBoxFuture, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use std::{ @@ -31,7 +31,7 @@ use std::{ marker::PhantomData, mem, ops::{Deref, DerefMut}, - path::PathBuf, + path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, @@ -262,38 +262,13 @@ impl AppContext { .collect() } - pub(crate) fn update_window( - &mut self, - handle: AnyWindowHandle, - update: impl FnOnce(AnyView, &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 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); - - Ok(result) - }) - } - /// 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 WindowContext) -> View + 'static, + build_root_view: impl FnOnce(&mut WindowContext) -> View, ) -> WindowHandle { self.update(|cx| { let id = cx.windows.insert(None); @@ -306,47 +281,63 @@ impl AppContext { }) } - pub(crate) fn platform(&self) -> &Rc { - &self.platform - } - /// Instructs the platform to activate the application by bringing it to the foreground. pub fn activate(&self, ignoring_other_apps: bool) { - self.platform().activate(ignoring_other_apps); + self.platform.activate(ignoring_other_apps); + } + + /// Returns the list of currently active displays. + pub fn displays(&self) -> Vec> { + self.platform.displays() } /// Writes data to the platform clipboard. pub fn write_to_clipboard(&self, item: ClipboardItem) { - self.platform().write_to_clipboard(item) + self.platform.write_to_clipboard(item) } /// Reads data from the platform clipboard. pub fn read_from_clipboard(&self) -> Option { - self.platform().read_from_clipboard() + self.platform.read_from_clipboard() } /// Writes credentials to the platform keychain. pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { - self.platform().write_credentials(url, username, password) + self.platform.write_credentials(url, username, password) } /// Reads credentials from the platform keychain. pub fn read_credentials(&self, url: &str) -> Result)>> { - self.platform().read_credentials(url) + self.platform.read_credentials(url) } /// Deletes credentials from the platform keychain. pub fn delete_credentials(&self, url: &str) -> Result<()> { - self.platform().delete_credentials(url) + self.platform.delete_credentials(url) } /// Directs the platform's default browser to open the given URL. pub fn open_url(&self, url: &str) { - self.platform().open_url(url); + self.platform.open_url(url); } pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { - self.platform().path_for_auxiliary_executable(name) + self.platform.path_for_auxiliary_executable(name) + } + + pub fn prompt_for_paths( + &self, + options: PathPromptOptions, + ) -> oneshot::Receiver>> { + self.platform.prompt_for_paths(options) + } + + pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { + self.platform.prompt_for_new_path(directory) + } + + pub fn reveal_path(&self, path: &Path) { + self.platform.reveal_path(path) } pub(crate) fn push_effect(&mut self, effect: Effect) { diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index f08c7ed0a9..4bbab43446 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,6 +1,7 @@ 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}; @@ -82,17 +83,20 @@ impl AsyncAppContext { Ok(f(&mut *lock)) } - pub fn update_window( + pub fn open_window( &self, - handle: AnyWindowHandle, - update: impl FnOnce(AnyView, &mut WindowContext) -> R, - ) -> Result { + options: crate::WindowOptions, + build_root_view: impl FnOnce(&mut WindowContext) -> View, + ) -> Result> + where + V: Render, + { let app = self .app .upgrade() .ok_or_else(|| anyhow!("app was released"))?; - let mut app_context = app.borrow_mut(); - app_context.update_window(handle, update) + let mut lock = app.borrow_mut(); + Ok(lock.open_window(options, build_root_view)) } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 27275d3a04..aaf42dd4a2 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -87,15 +87,6 @@ impl TestAppContext { cx.update(f) } - pub fn update_window( - &self, - handle: AnyWindowHandle, - update: impl FnOnce(AnyView, &mut WindowContext) -> R, - ) -> R { - let mut app = self.app.borrow_mut(); - app.update_window(handle, update).unwrap() - } - pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 9a6768342f..cdce67d8c1 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -138,12 +138,7 @@ pub(crate) trait PlatformWindow { fn mouse_position(&self) -> Point; fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: Box); - fn prompt( - &self, - level: WindowPromptLevel, - msg: &str, - answers: &[&str], - ) -> oneshot::Receiver; + fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); fn set_edited(&mut self, edited: bool); @@ -454,14 +449,6 @@ impl Default for WindowAppearance { } } -#[derive(Copy, Clone, Debug, PartialEq, Default)] -pub enum WindowPromptLevel { - #[default] - Info, - Warning, - Critical, -} - #[derive(Copy, Clone, Debug)] pub struct PathPromptOptions { pub files: bool, diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index bf62e2e0dc..77675e3c27 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -4,7 +4,7 @@ use crate::{ FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, - Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, + Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, PromptLevel, }; use block::ConcreteBlock; use cocoa::{ @@ -742,12 +742,7 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().input_handler = Some(input_handler); } - fn prompt( - &self, - level: WindowPromptLevel, - msg: &str, - answers: &[&str], - ) -> oneshot::Receiver { + fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver { // macOs applies overrides to modal window buttons after they are added. // Two most important for this logic are: // * Buttons with "Cancel" title will be displayed as the last buttons in the modal @@ -777,9 +772,9 @@ impl PlatformWindow for MacWindow { let alert: id = msg_send![class!(NSAlert), alloc]; let alert: id = msg_send![alert, init]; let alert_style = match level { - WindowPromptLevel::Info => 1, - WindowPromptLevel::Warning => 0, - WindowPromptLevel::Critical => 2, + PromptLevel::Info => 1, + PromptLevel::Warning => 0, + PromptLevel::Critical => 2, }; let _: () = msg_send![alert, setAlertStyle: alert_style]; let _: () = msg_send![alert, setMessageText: ns_string(msg)]; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 3cc4fdd4e3..d81df5b21c 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -98,6 +98,10 @@ pub struct WeakView { } impl WeakView { + pub fn entity_id(&self) -> EntityId { + self.model.entity_id + } + pub fn upgrade(&self) -> Option> { Entity::upgrade_from(self) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5f2de2e428..0202b7521e 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -4,14 +4,15 @@ 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, Quad, - Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, - Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, + MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, + PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, + Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; +use futures::channel::oneshot; use parking_lot::RwLock; use slotmap::SlotMap; use smallvec::SmallVec; @@ -195,7 +196,7 @@ impl Window { options: WindowOptions, cx: &mut AppContext, ) -> Self { - let platform_window = cx.platform().open_window(handle, options); + let platform_window = cx.platform.open_window(handle, options); let display_id = platform_window.display().id(); let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); @@ -419,7 +420,7 @@ impl<'a> WindowContext<'a> { } else { let mut async_cx = self.to_async(); self.next_frame_callbacks.insert(display_id, vec![f]); - self.platform().set_display_link_output_callback( + self.platform.set_display_link_output_callback( display_id, Box::new(move |_current_time, _output_time| { let _ = async_cx.update(|_, cx| { @@ -434,32 +435,26 @@ impl<'a> WindowContext<'a> { } if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { - cx.platform().stop_display_link(display_id); + cx.platform.stop_display_link(display_id); } }); }), ); } - self.platform().start_display_link(display_id); + self.platform.start_display_link(display_id); } /// Spawn the future returned by the given closure on the application thread pool. /// The closure is provided a handle to the current window and an `AsyncWindowContext` for /// use within your future. - pub fn spawn( - &mut self, - f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut, - ) -> Task + pub fn spawn(&mut self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task where R: 'static, Fut: Future + 'static, { - let window = self.window.handle; - self.app.spawn(move |app| { - let cx = AsyncWindowContext::new(app, window); - f(window, cx) - }) + self.app + .spawn(|app| f(AsyncWindowContext::new(app, self.window.handle))) } /// Update the global of the given type. The given closure is given simultaneous mutable @@ -1153,6 +1148,19 @@ impl<'a> WindowContext<'a> { ) } + pub fn activate_window(&self) { + self.window.platform_window.activate(); + } + + pub fn prompt( + &self, + level: PromptLevel, + msg: &str, + answers: &[&str], + ) -> oneshot::Receiver { + self.window.platform_window.prompt(level, msg, answers) + } + fn dispatch_action( &mut self, action: Box, @@ -1809,7 +1817,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { Fut: Future + 'static, { let view = self.view().downgrade(); - self.window_cx.spawn(move |_, cx| f(view, cx)) + self.window_cx.spawn(|cx| f(view, cx)) } pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R From 76c675a63b01937d45eb5f367c8b8f35fc0bdd34 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 10:57:29 +0100 Subject: [PATCH 103/156] :lipstick: --- crates/gpui2/src/platform/mac/window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index 77675e3c27..52dcf31603 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -3,8 +3,8 @@ use crate::{ display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, - Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, PromptLevel, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + PromptLevel, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, }; use block::ConcreteBlock; use cocoa::{ From 32dded551c7edf742ac9484f7ff84f4dce884a93 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 11:05:24 +0100 Subject: [PATCH 104/156] 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 105/156] 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 106/156] 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 107/156] 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 f724b6d032c8ff0fff237ae2b8ab0bbee63b64e9 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 2 Nov 2023 09:05:16 -0400 Subject: [PATCH 108/156] Cleanly truncate Discord release notes --- .github/workflows/release_actions.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 550eda882b..a72abf300d 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -16,7 +16,7 @@ jobs: fi echo "::set-output name=URL::$URL" - name: Get content - uses: 2428392/gh-truncate-string-action@v1.2.0 + uses: 2428392/gh-truncate-string-action@v1.3.0 id: get-content with: stringToTruncate: | @@ -24,6 +24,7 @@ jobs: ${{ github.event.release.body }} maxLength: 2000 + truncationSymbol: "..." - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 with: From 971563fd489667624038c117ac8623b4e6d420fc Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 2 Nov 2023 09:05:29 -0400 Subject: [PATCH 109/156] Format YAML --- .github/workflows/release_actions.yml | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index a72abf300d..c1d2457ed4 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -6,27 +6,27 @@ jobs: discord_release: runs-on: ubuntu-latest steps: - - name: Get release URL - id: get-release-url - run: | - if [ "${{ github.event.release.prerelease }}" == "true" ]; then - URL="https://zed.dev/releases/preview/latest" - else - URL="https://zed.dev/releases/stable/latest" - fi - echo "::set-output name=URL::$URL" - - name: Get content - uses: 2428392/gh-truncate-string-action@v1.3.0 - id: get-content - with: - stringToTruncate: | - 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released! + - name: Get release URL + id: get-release-url + run: | + if [ "${{ github.event.release.prerelease }}" == "true" ]; then + URL="https://zed.dev/releases/preview/latest" + else + URL="https://zed.dev/releases/stable/latest" + fi + echo "::set-output name=URL::$URL" + - name: Get content + uses: 2428392/gh-truncate-string-action@v1.3.0 + id: get-content + with: + stringToTruncate: | + 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released! - ${{ github.event.release.body }} - maxLength: 2000 - truncationSymbol: "..." - - name: Discord Webhook Action - uses: tsickert/discord-webhook@v5.3.0 - with: - webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - content: ${{ steps.get-content.outputs.string }} + ${{ github.event.release.body }} + maxLength: 2000 + truncationSymbol: "..." + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v5.3.0 + with: + webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} + content: ${{ steps.get-content.outputs.string }} From b5fe0d72ee4328d9d7a6d9cf9ec0e371a7d0a3f1 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 2 Nov 2023 09:34:18 -0400 Subject: [PATCH 110/156] authenticate with completion provider on new inline assists --- crates/assistant/src/assistant_panel.rs | 15 +++++++++------ crates/assistant/src/codegen.rs | 14 ++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 03eb3c238f..022c228790 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -259,7 +259,13 @@ impl AssistantPanel { cx: &mut ViewContext, ) { let this = if let Some(this) = workspace.panel::(cx) { - if this.update(cx, |assistant, _| assistant.has_credentials()) { + if this.update(cx, |assistant, cx| { + if !assistant.has_credentials() { + assistant.load_credentials(cx); + }; + + assistant.has_credentials() + }) { this } else { workspace.focus_panel::(cx); @@ -320,13 +326,10 @@ impl AssistantPanel { }; let inline_assist_id = post_inc(&mut self.next_inline_assist_id); - let provider = Arc::new(OpenAICompletionProvider::new( - "gpt-4", - cx.background().clone(), - )); + let provider = self.completion_provider.clone(); // Retrieve Credentials Authenticates the Provider - // provider.retrieve_credentials(cx); + provider.retrieve_credentials(cx); let codegen = cx.add_model(|cx| { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index f62c91fcb7..da7beda2dc 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -6,7 +6,7 @@ use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; use multi_buffer; -use std::{cmp, future, ops::Range, sync::Arc}; +use std::{cmp, future, ops::Range}; pub enum Event { Finished, @@ -20,7 +20,7 @@ pub enum CodegenKind { } pub struct Codegen { - provider: Arc, + provider: Box, buffer: ModelHandle, snapshot: MultiBufferSnapshot, kind: CodegenKind, @@ -40,7 +40,7 @@ impl Codegen { pub fn new( buffer: ModelHandle, kind: CodegenKind, - provider: Arc, + provider: Box, cx: &mut ModelContext, ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); @@ -367,6 +367,8 @@ fn strip_invalid_spans_from_codeblock( #[cfg(test)] mod tests { + use std::sync::Arc; + use super::*; use ai::test::FakeCompletionProvider; use futures::stream::{self}; @@ -412,7 +414,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) }); - let provider = Arc::new(FakeCompletionProvider::new()); + let provider = Box::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -478,7 +480,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 6)) }); - let provider = Arc::new(FakeCompletionProvider::new()); + let provider = Box::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -544,7 +546,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 2)) }); - let provider = Arc::new(FakeCompletionProvider::new()); + let provider = Box::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), From 52e195b47c734c90deb238700e73610972d933a0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Nov 2023 07:46:49 -0600 Subject: [PATCH 111/156] 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 d5b6300fd7edb6441516be5004887a5090e5a599 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 2 Nov 2023 10:08:47 -0400 Subject: [PATCH 112/156] moved from Boxes to Arcs for shared access of completion providers across the assistant panel and inline assistant --- crates/ai/src/test.rs | 11 ++++++++++- crates/assistant/src/assistant_panel.rs | 20 ++++++++++---------- crates/assistant/src/codegen.rs | 14 ++++++++------ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/crates/ai/src/test.rs b/crates/ai/src/test.rs index d4165f3cca..3f331da117 100644 --- a/crates/ai/src/test.rs +++ b/crates/ai/src/test.rs @@ -153,10 +153,17 @@ impl FakeCompletionProvider { pub fn send_completion(&self, completion: impl Into) { let mut tx = self.last_completion_tx.lock(); - tx.as_mut().unwrap().try_send(completion.into()).unwrap(); + + println!("COMPLETION TX: {:?}", &tx); + + let a = tx.as_mut().unwrap(); + a.try_send(completion.into()).unwrap(); + + // tx.as_mut().unwrap().try_send(completion.into()).unwrap(); } pub fn finish_completion(&self) { + println!("FINISHING COMPLETION"); self.last_completion_tx.lock().take().unwrap(); } } @@ -181,8 +188,10 @@ impl CompletionProvider for FakeCompletionProvider { &self, _prompt: Box, ) -> BoxFuture<'static, anyhow::Result>>> { + println!("COMPLETING"); let (tx, rx) = mpsc::channel(1); *self.last_completion_tx.lock() = Some(tx); + println!("TX: {:?}", *self.last_completion_tx.lock()); async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed() } fn box_clone(&self) -> Box { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 022c228790..6ab96093a7 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -142,7 +142,7 @@ pub struct AssistantPanel { zoomed: bool, has_focus: bool, toolbar: ViewHandle, - completion_provider: Box, + completion_provider: Arc, api_key_editor: Option>, languages: Arc, fs: Arc, @@ -204,7 +204,7 @@ impl AssistantPanel { let semantic_index = SemanticIndex::global(cx); // Defaulting currently to GPT4, allow for this to be set via config. - let completion_provider = Box::new(OpenAICompletionProvider::new( + let completion_provider = Arc::new(OpenAICompletionProvider::new( "gpt-4", cx.background().clone(), )); @@ -1442,7 +1442,7 @@ struct Conversation { pending_save: Task>, path: Option, _subscriptions: Vec, - completion_provider: Box, + completion_provider: Arc, } impl Entity for Conversation { @@ -1453,7 +1453,7 @@ impl Conversation { fn new( language_registry: Arc, cx: &mut ModelContext, - completion_provider: Box, + completion_provider: Arc, ) -> Self { let markdown = language_registry.language_for_name("Markdown"); let buffer = cx.add_model(|cx| { @@ -1547,7 +1547,7 @@ impl Conversation { None => Some(Uuid::new_v4().to_string()), }; let model = saved_conversation.model; - let completion_provider: Box = Box::new( + let completion_provider: Arc = Arc::new( OpenAICompletionProvider::new(model.full_name(), cx.background().clone()), ); completion_provider.retrieve_credentials(cx); @@ -2204,7 +2204,7 @@ struct ConversationEditor { impl ConversationEditor { fn new( - completion_provider: Box, + completion_provider: Arc, language_registry: Arc, fs: Arc, workspace: WeakViewHandle, @@ -3409,7 +3409,7 @@ mod tests { init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); @@ -3538,7 +3538,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); @@ -3636,7 +3636,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); @@ -3719,7 +3719,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); init(cx); let registry = Arc::new(LanguageRegistry::test()); - let completion_provider = Box::new(FakeCompletionProvider::new()); + let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider)); let buffer = conversation.read(cx).buffer.clone(); diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index da7beda2dc..25c9deef7f 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -6,7 +6,7 @@ use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; use multi_buffer; -use std::{cmp, future, ops::Range}; +use std::{cmp, future, ops::Range, sync::Arc}; pub enum Event { Finished, @@ -20,7 +20,7 @@ pub enum CodegenKind { } pub struct Codegen { - provider: Box, + provider: Arc, buffer: ModelHandle, snapshot: MultiBufferSnapshot, kind: CodegenKind, @@ -40,7 +40,7 @@ impl Codegen { pub fn new( buffer: ModelHandle, kind: CodegenKind, - provider: Box, + provider: Arc, cx: &mut ModelContext, ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); @@ -414,7 +414,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) }); - let provider = Box::new(FakeCompletionProvider::new()); + let provider = Arc::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -439,6 +439,7 @@ mod tests { let max_len = cmp::min(new_text.len(), 10); let len = rng.gen_range(1..=max_len); let (chunk, suffix) = new_text.split_at(len); + println!("CHUNK: {:?}", &chunk); provider.send_completion(chunk); new_text = suffix; deterministic.run_until_parked(); @@ -480,7 +481,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 6)) }); - let provider = Box::new(FakeCompletionProvider::new()); + let provider = Arc::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -546,7 +547,7 @@ mod tests { let snapshot = buffer.snapshot(cx); snapshot.anchor_before(Point::new(1, 2)) }); - let provider = Box::new(FakeCompletionProvider::new()); + let provider = Arc::new(FakeCompletionProvider::new()); let codegen = cx.add_model(|cx| { Codegen::new( buffer.clone(), @@ -571,6 +572,7 @@ mod tests { let max_len = cmp::min(new_text.len(), 10); let len = rng.gen_range(1..=max_len); let (chunk, suffix) = new_text.split_at(len); + println!("{:?}", &chunk); provider.send_completion(chunk); new_text = suffix; deterministic.run_until_parked(); From cde7b244bc471abcd6a040338f9f7c7c35df744e Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 Nov 2023 10:11:25 -0400 Subject: [PATCH 113/156] Checkpoint - impl format_distance --- crates/ui2/src/lib.rs | 1 + crates/ui2/src/utils.rs | 3 + crates/ui2/src/utils/format_distance.rs | 166 ++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 crates/ui2/src/utils.rs create mode 100644 crates/ui2/src/utils/format_distance.rs diff --git a/crates/ui2/src/lib.rs b/crates/ui2/src/lib.rs index c1da5e410d..5d0a57c6d9 100644 --- a/crates/ui2/src/lib.rs +++ b/crates/ui2/src/lib.rs @@ -23,6 +23,7 @@ mod elevation; pub mod prelude; pub mod settings; mod static_data; +pub mod utils; pub use components::*; pub use elements::*; diff --git a/crates/ui2/src/utils.rs b/crates/ui2/src/utils.rs new file mode 100644 index 0000000000..573a1333ef --- /dev/null +++ b/crates/ui2/src/utils.rs @@ -0,0 +1,3 @@ +mod format_distance; + +pub use format_distance::*; diff --git a/crates/ui2/src/utils/format_distance.rs b/crates/ui2/src/utils/format_distance.rs new file mode 100644 index 0000000000..6835e530d2 --- /dev/null +++ b/crates/ui2/src/utils/format_distance.rs @@ -0,0 +1,166 @@ +use chrono::NaiveDateTime; + +fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { + let duration = date.signed_duration_since(base_date); + duration.num_seconds() +} + +fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String { + let suffix = if distance < 0 { " from now" } else { " ago" }; + + let d = distance.abs(); + + let minutes = d / 60; + let hours = d / 3600; + let days = d / 86400; + let months = d / 2592000; + let years = d / 31536000; + + let string = if d < 5 && include_seconds { + "less than 5 seconds".to_string() + } else if d < 10 && include_seconds { + "less than 10 seconds".to_string() + } else if d < 20 && include_seconds { + "less than 20 seconds".to_string() + } else if d < 40 && include_seconds { + "half a minute".to_string() + } else if d < 60 && include_seconds { + "less than a minute".to_string() + } else if d < 90 && include_seconds { + "1 minute".to_string() + } else if d < 30 { + "less than a minute".to_string() + } else if d < 90 { + "1 minute".to_string() + } else if d < 2700 { + format!("{} minutes", minutes) + } else if d < 5400 { + "about 1 hour".to_string() + } else if d < 86400 { + format!("about {} hours", hours) + } else if d < 172800 { + "1 day".to_string() + } else if d < 2592000 { + format!("{} days", days) + } else if d < 5184000 { + "about 1 month".to_string() + } else if d < 7776000 { + "about 2 months".to_string() + } else if d < 31536000 { + format!("{} months", months) + } else if d < 39408000 { + "about 1 year".to_string() + } else if d < 47318400 { + "over 1 year".to_string() + } else if d < 63072000 { + "almost 2 years".to_string() + } else { + format!("{} years", years) + }; + + if add_suffix { + return format!("{}{}", string, suffix); + } else { + string + } +} + +pub fn naive_format_distance( + date: NaiveDateTime, + base_date: NaiveDateTime, + include_seconds: bool, + add_suffix: bool, +) -> String { + let distance = distance_in_seconds(date, base_date); + + distance_string(distance, include_seconds, add_suffix) +} + +pub fn naive_format_distance_from_now( + datetime: NaiveDateTime, + include_seconds: bool, + add_suffix: bool, +) -> String { + let now = chrono::offset::Local::now().naive_local(); + + naive_format_distance(datetime, now, include_seconds, add_suffix) +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::NaiveDateTime; + + // #[test] + // fn test_naive_format_distance() { + // let date = NaiveDateTime::from_timestamp_opt(0, 0); + // let base_date = NaiveDateTime::from_timestamp_opt(9400, 0); + + // assert_eq!( + // "about 2 hours ago", + // naive_format_distance( + // date.expect("invalid datetime"), + // base_date.expect("invalid datetime"), + // false, + // true + // ) + // ); + // } + + // #[test] + // fn test_naive_format_distance_with_seconds() { + // let date = NaiveDateTime::from_timestamp_opt(0, 0); + // let base_date = NaiveDateTime::from_timestamp_opt(10, 0); + + // assert_eq!( + // "less than 30 seconds ago", + // naive_format_distance( + // date.expect("invalid datetime"), + // base_date.expect("invalid datetime"), + // true, + // true + // ) + // ); + // } + + // #[test] + // fn test_naive_format_distance_with_future_date() { + // let date = NaiveDateTime::from_timestamp_opt(3400, 0); + // let base_date = NaiveDateTime::from_timestamp_opt(00, 0); + + // assert_eq!( + // "about 56 from now", + // naive_format_distance( + // date.expect("invalid datetime"), + // base_date.expect("invalid datetime"), + // false, + // true + // ) + // ); + // } + + #[test] + fn test_naive_format_distance_string() { + assert_eq!(distance_string(3, false, false), "less than a minute"); + assert_eq!(distance_string(7, false, false), "less than a minute"); + assert_eq!(distance_string(13, false, false), "less than a minute"); + assert_eq!(distance_string(21, false, false), "less than a minute"); + assert_eq!(distance_string(45, false, false), "1 minute"); + assert_eq!(distance_string(61, false, false), "1 minute"); + assert_eq!(distance_string(1920, false, false), "32 minutes"); + assert_eq!(distance_string(3902, false, false), "about 1 hour"); + assert_eq!(distance_string(18002, false, false), "about 5 hours"); + assert_eq!(distance_string(86470, false, false), "1 day"); + assert_eq!(distance_string(345880, false, false), "4 days"); + } + + #[test] + fn test_naive_format_distance_string_include_seconds() { + assert_eq!(distance_string(3, true, false), "less than 5 seconds"); + assert_eq!(distance_string(7, true, false), "less than 10 seconds"); + assert_eq!(distance_string(13, true, false), "less than 20 seconds"); + assert_eq!(distance_string(21, true, false), "half a minute"); + assert_eq!(distance_string(45, true, false), "less than a minute"); + assert_eq!(distance_string(61, true, false), "1 minute"); + } +} From b76c1179794d3b1e6d0ade20676a0f68e319e72e Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 Nov 2023 10:37:33 -0400 Subject: [PATCH 114/156] Update naive_format_distance and tests --- crates/ui2/src/utils/format_distance.rs | 103 +++++++++++++----------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/crates/ui2/src/utils/format_distance.rs b/crates/ui2/src/utils/format_distance.rs index 6835e530d2..8a33619910 100644 --- a/crates/ui2/src/utils/format_distance.rs +++ b/crates/ui2/src/utils/format_distance.rs @@ -2,7 +2,7 @@ use chrono::NaiveDateTime; fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { let duration = date.signed_duration_since(base_date); - duration.num_seconds() + -duration.num_seconds() } fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String { @@ -46,16 +46,25 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St "about 1 month".to_string() } else if d < 7776000 { "about 2 months".to_string() - } else if d < 31536000 { + } else if d < 31540000 { format!("{} months", months) - } else if d < 39408000 { + } else if d < 39425000 { "about 1 year".to_string() - } else if d < 47318400 { + } else if d < 55195000 { "over 1 year".to_string() - } else if d < 63072000 { + } else if d < 63080000 { "almost 2 years".to_string() } else { - format!("{} years", years) + let years = d / 31536000; + let remaining_months = (d % 31536000) / 2592000; + + if remaining_months < 3 { + format!("about {} years", years) + } else if remaining_months < 9 { + format!("over {} years", years) + } else { + format!("almost {} years", years + 1) + } }; if add_suffix { @@ -91,53 +100,42 @@ mod tests { use super::*; use chrono::NaiveDateTime; - // #[test] - // fn test_naive_format_distance() { - // let date = NaiveDateTime::from_timestamp_opt(0, 0); - // let base_date = NaiveDateTime::from_timestamp_opt(9400, 0); + #[test] + fn test_naive_format_distance() { + let date = + NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"); + let base_date = + NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"); - // assert_eq!( - // "about 2 hours ago", - // naive_format_distance( - // date.expect("invalid datetime"), - // base_date.expect("invalid datetime"), - // false, - // true - // ) - // ); - // } + assert_eq!( + "about 2 hours", + naive_format_distance(date, base_date, false, false) + ); + } - // #[test] - // fn test_naive_format_distance_with_seconds() { - // let date = NaiveDateTime::from_timestamp_opt(0, 0); - // let base_date = NaiveDateTime::from_timestamp_opt(10, 0); + #[test] + fn test_naive_format_distance_with_suffix() { + let date = + NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"); + let base_date = + NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"); - // assert_eq!( - // "less than 30 seconds ago", - // naive_format_distance( - // date.expect("invalid datetime"), - // base_date.expect("invalid datetime"), - // true, - // true - // ) - // ); - // } + assert_eq!( + "about 2 hours from now", + naive_format_distance(date, base_date, false, true) + ); + } - // #[test] - // fn test_naive_format_distance_with_future_date() { - // let date = NaiveDateTime::from_timestamp_opt(3400, 0); - // let base_date = NaiveDateTime::from_timestamp_opt(00, 0); + #[test] + fn test_naive_format_distance_from_now() { + let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ") + .expect("Invalid NaiveDateTime for date"); - // assert_eq!( - // "about 56 from now", - // naive_format_distance( - // date.expect("invalid datetime"), - // base_date.expect("invalid datetime"), - // false, - // true - // ) - // ); - // } + assert_eq!( + "over 54 years ago", + naive_format_distance_from_now(date, false, true) + ); + } #[test] fn test_naive_format_distance_string() { @@ -152,6 +150,15 @@ mod tests { assert_eq!(distance_string(18002, false, false), "about 5 hours"); assert_eq!(distance_string(86470, false, false), "1 day"); assert_eq!(distance_string(345880, false, false), "4 days"); + assert_eq!(distance_string(2764800, false, false), "about 1 month"); + assert_eq!(distance_string(5184000, false, false), "about 2 months"); + assert_eq!(distance_string(10368000, false, false), "4 months"); + assert_eq!(distance_string(34694000, false, false), "about 1 year"); + assert_eq!(distance_string(47310000, false, false), "over 1 year"); + assert_eq!(distance_string(61503000, false, false), "almost 2 years"); + assert_eq!(distance_string(160854000, false, false), "about 5 years"); + assert_eq!(distance_string(236550000, false, false), "over 7 years"); + assert_eq!(distance_string(249166000, false, false), "almost 8 years"); } #[test] From 46e81da4e1453d8caf355a402123420e0683203f Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 2 Nov 2023 11:00:47 -0400 Subject: [PATCH 115/156] Uncomment db2 tests --- crates/db2/src/db2.rs | 226 +++++++++++++++++++++--------------------- crates/db2/src/kvp.rs | 46 ++++----- 2 files changed, 138 insertions(+), 134 deletions(-) diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs index fe79dfbb0c..e052d59d12 100644 --- a/crates/db2/src/db2.rs +++ b/crates/db2/src/db2.rs @@ -190,138 +190,142 @@ where .detach() } -// #[cfg(test)] -// mod tests { -// use std::thread; +#[cfg(test)] +mod tests { + use std::thread; -// use sqlez::domain::Domain; -// use sqlez_macros::sql; -// use tempdir::TempDir; + use sqlez::domain::Domain; + use sqlez_macros::sql; + use tempdir::TempDir; -// use crate::open_db; + use crate::open_db; -// // Test bad migration panics -// #[gpui::test] -// #[should_panic] -// async fn test_bad_migration_panics() { -// enum BadDB {} + // Test bad migration panics + #[gpui2::test] + #[should_panic] + async fn test_bad_migration_panics() { + enum BadDB {} -// impl Domain for BadDB { -// fn name() -> &'static str { -// "db_tests" -// } + impl Domain for BadDB { + fn name() -> &'static str { + "db_tests" + } -// fn migrations() -> &'static [&'static str] { -// &[ -// sql!(CREATE TABLE test(value);), -// // failure because test already exists -// sql!(CREATE TABLE test(value);), -// ] -// } -// } + fn migrations() -> &'static [&'static str] { + &[ + sql!(CREATE TABLE test(value);), + // failure because test already exists + sql!(CREATE TABLE test(value);), + ] + } + } -// let tempdir = TempDir::new("DbTests").unwrap(); -// let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; -// } + let tempdir = TempDir::new("DbTests").unwrap(); + let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; + } -// /// Test that DB exists but corrupted (causing recreate) -// #[gpui::test] -// async fn test_db_corruption() { -// enum CorruptedDB {} + /// Test that DB exists but corrupted (causing recreate) + #[gpui2::test] + async fn test_db_corruption(cx: &mut gpui2::TestAppContext) { + cx.executor().allow_parking(); -// impl Domain for CorruptedDB { -// fn name() -> &'static str { -// "db_tests" -// } + enum CorruptedDB {} -// fn migrations() -> &'static [&'static str] { -// &[sql!(CREATE TABLE test(value);)] -// } -// } + impl Domain for CorruptedDB { + fn name() -> &'static str { + "db_tests" + } -// enum GoodDB {} + fn migrations() -> &'static [&'static str] { + &[sql!(CREATE TABLE test(value);)] + } + } -// impl Domain for GoodDB { -// fn name() -> &'static str { -// "db_tests" //Notice same name -// } + enum GoodDB {} -// fn migrations() -> &'static [&'static str] { -// &[sql!(CREATE TABLE test2(value);)] //But different migration -// } -// } + impl Domain for GoodDB { + fn name() -> &'static str { + "db_tests" //Notice same name + } -// let tempdir = TempDir::new("DbTests").unwrap(); -// { -// let corrupt_db = -// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; -// assert!(corrupt_db.persistent()); -// } + fn migrations() -> &'static [&'static str] { + &[sql!(CREATE TABLE test2(value);)] //But different migration + } + } -// let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; -// assert!( -// good_db.select_row::("SELECT * FROM test2").unwrap()() -// .unwrap() -// .is_none() -// ); -// } + let tempdir = TempDir::new("DbTests").unwrap(); + { + let corrupt_db = + open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; + assert!(corrupt_db.persistent()); + } -// /// Test that DB exists but corrupted (causing recreate) -// #[gpui::test(iterations = 30)] -// async fn test_simultaneous_db_corruption() { -// enum CorruptedDB {} + let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; + assert!( + good_db.select_row::("SELECT * FROM test2").unwrap()() + .unwrap() + .is_none() + ); + } -// impl Domain for CorruptedDB { -// fn name() -> &'static str { -// "db_tests" -// } + /// Test that DB exists but corrupted (causing recreate) + #[gpui2::test(iterations = 30)] + async fn test_simultaneous_db_corruption(cx: &mut gpui2::TestAppContext) { + cx.executor().allow_parking(); -// fn migrations() -> &'static [&'static str] { -// &[sql!(CREATE TABLE test(value);)] -// } -// } + enum CorruptedDB {} -// enum GoodDB {} + impl Domain for CorruptedDB { + fn name() -> &'static str { + "db_tests" + } -// impl Domain for GoodDB { -// fn name() -> &'static str { -// "db_tests" //Notice same name -// } + fn migrations() -> &'static [&'static str] { + &[sql!(CREATE TABLE test(value);)] + } + } -// fn migrations() -> &'static [&'static str] { -// &[sql!(CREATE TABLE test2(value);)] //But different migration -// } -// } + enum GoodDB {} -// let tempdir = TempDir::new("DbTests").unwrap(); -// { -// // Setup the bad database -// let corrupt_db = -// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; -// assert!(corrupt_db.persistent()); -// } + impl Domain for GoodDB { + fn name() -> &'static str { + "db_tests" //Notice same name + } -// // Try to connect to it a bunch of times at once -// let mut guards = vec![]; -// for _ in 0..10 { -// let tmp_path = tempdir.path().to_path_buf(); -// let guard = thread::spawn(move || { -// let good_db = smol::block_on(open_db::( -// tmp_path.as_path(), -// &util::channel::ReleaseChannel::Dev, -// )); -// assert!( -// good_db.select_row::("SELECT * FROM test2").unwrap()() -// .unwrap() -// .is_none() -// ); -// }); + fn migrations() -> &'static [&'static str] { + &[sql!(CREATE TABLE test2(value);)] //But different migration + } + } -// guards.push(guard); -// } + let tempdir = TempDir::new("DbTests").unwrap(); + { + // Setup the bad database + let corrupt_db = + open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; + assert!(corrupt_db.persistent()); + } -// for guard in guards.into_iter() { -// assert!(guard.join().is_ok()); -// } -// } -// } + // Try to connect to it a bunch of times at once + let mut guards = vec![]; + for _ in 0..10 { + let tmp_path = tempdir.path().to_path_buf(); + let guard = thread::spawn(move || { + let good_db = smol::block_on(open_db::( + tmp_path.as_path(), + &util::channel::ReleaseChannel::Dev, + )); + assert!( + good_db.select_row::("SELECT * FROM test2").unwrap()() + .unwrap() + .is_none() + ); + }); + + guards.push(guard); + } + + for guard in guards.into_iter() { + assert!(guard.join().is_ok()); + } + } +} diff --git a/crates/db2/src/kvp.rs b/crates/db2/src/kvp.rs index 254d91689d..b4445e3586 100644 --- a/crates/db2/src/kvp.rs +++ b/crates/db2/src/kvp.rs @@ -31,32 +31,32 @@ impl KeyValueStore { } } -// #[cfg(test)] -// mod tests { -// use crate::kvp::KeyValueStore; +#[cfg(test)] +mod tests { + use crate::kvp::KeyValueStore; -// #[gpui::test] -// async fn test_kvp() { -// let db = KeyValueStore(crate::open_test_db("test_kvp").await); + #[gpui2::test] + async fn test_kvp() { + let db = KeyValueStore(crate::open_test_db("test_kvp").await); -// assert_eq!(db.read_kvp("key-1").unwrap(), None); + assert_eq!(db.read_kvp("key-1").unwrap(), None); -// db.write_kvp("key-1".to_string(), "one".to_string()) -// .await -// .unwrap(); -// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string())); + db.write_kvp("key-1".to_string(), "one".to_string()) + .await + .unwrap(); + assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string())); -// db.write_kvp("key-1".to_string(), "one-2".to_string()) -// .await -// .unwrap(); -// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string())); + db.write_kvp("key-1".to_string(), "one-2".to_string()) + .await + .unwrap(); + assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string())); -// db.write_kvp("key-2".to_string(), "two".to_string()) -// .await -// .unwrap(); -// assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string())); + db.write_kvp("key-2".to_string(), "two".to_string()) + .await + .unwrap(); + assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string())); -// db.delete_kvp("key-1".to_string()).await.unwrap(); -// assert_eq!(db.read_kvp("key-1").unwrap(), None); -// } -// } + db.delete_kvp("key-1".to_string()).await.unwrap(); + assert_eq!(db.read_kvp("key-1").unwrap(), None); + } +} From 6d562aaa6f111736408a3edb55e7e79bac57dccf Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 Nov 2023 11:02:17 -0400 Subject: [PATCH 116/156] Use naive_format_distance_from_now in Notifications --- .../ui2/src/components/notifications_panel.rs | 38 +++++++++++++++---- crates/ui2/src/static_data.rs | 31 +++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index c53b60f213..697b4de63b 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,3 +1,4 @@ +use crate::utils::naive_format_distance_from_now; use crate::{ h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, @@ -137,6 +138,7 @@ impl Notification { fn new( id: ElementId, message: SharedString, + date_received: NaiveDateTime, slot: ActorOrIcon, click_action: Option>, ) -> Self { @@ -150,9 +152,7 @@ impl Notification { Self { id, - date_received: DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z") - .unwrap() - .naive_local(), + date_received, message, meta: None, slot, @@ -170,12 +170,14 @@ impl Notification { pub fn new_actor_message( id: impl Into, message: impl Into, + date_received: NaiveDateTime, actor: PublicActor, click_action: ClickHandler, ) -> Self { Self::new( id.into(), message.into(), + date_received, ActorOrIcon::Actor(actor), Some(click_action), ) @@ -187,12 +189,14 @@ impl Notification { pub fn new_icon_message( id: impl Into, message: impl Into, + date_received: NaiveDateTime, icon: Icon, click_action: ClickHandler, ) -> Self { Self::new( id.into(), message.into(), + date_received, ActorOrIcon::Icon(icon), Some(click_action), ) @@ -205,10 +209,18 @@ impl Notification { pub fn new_actor_with_actions( id: impl Into, message: impl Into, + date_received: NaiveDateTime, actor: PublicActor, actions: [NotificationAction; 2], ) -> Self { - Self::new(id.into(), message.into(), ActorOrIcon::Actor(actor), None).actions(actions) + Self::new( + id.into(), + message.into(), + date_received, + ActorOrIcon::Actor(actor), + None, + ) + .actions(actions) } /// Creates a new notification with an icon slot @@ -218,10 +230,18 @@ impl Notification { pub fn new_icon_with_actions( id: impl Into, message: impl Into, + date_received: NaiveDateTime, icon: Icon, actions: [NotificationAction; 2], ) -> Self { - Self::new(id.into(), message.into(), ActorOrIcon::Icon(icon), None).actions(actions) + Self::new( + id.into(), + message.into(), + date_received, + ActorOrIcon::Icon(icon), + None, + ) + .actions(actions) } fn on_click(mut self, handler: ClickHandler) -> Self { @@ -303,9 +323,11 @@ impl Notification { h_stack() .gap_1() .child( - Label::new( - self.date_received.format("%m/%d/%Y").to_string(), - ) + Label::new(naive_format_distance_from_now( + self.date_received, + true, + true, + )) .color(LabelColor::Muted), ) .child(self.render_meta_items(cx)), diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 45e4dfa423..3964873055 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use chrono::{DateTime, NaiveDateTime}; use gpui2::{AppContext, ViewContext}; use rand::Rng; use theme2::ActiveTheme; @@ -332,12 +333,18 @@ pub fn static_new_notification_items_2() -> Vec> { Notification::new_icon_message( "notif-1", "You were mentioned in a note.", + DateTime::parse_from_rfc3339("2023-11-02T11:59:57Z") + .unwrap() + .naive_local(), Icon::AtSign, Arc::new(|_, _| {}), ), Notification::new_actor_with_actions( "notif-2", "as-cii sent you a contact request.", + DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z") + .unwrap() + .naive_local(), PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( @@ -355,12 +362,18 @@ pub fn static_new_notification_items_2() -> Vec> { Notification::new_icon_message( "notif-3", "You were mentioned #design.", + DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z") + .unwrap() + .naive_local(), Icon::MessageBubbles, Arc::new(|_, _| {}), ), Notification::new_actor_with_actions( "notif-4", "as-cii sent you a contact request.", + DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z") + .unwrap() + .naive_local(), PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( @@ -378,12 +391,18 @@ pub fn static_new_notification_items_2() -> Vec> { Notification::new_icon_message( "notif-5", "You were mentioned in a note.", + DateTime::parse_from_rfc3339("2023-10-28T12:09:07Z") + .unwrap() + .naive_local(), Icon::AtSign, Arc::new(|_, _| {}), ), Notification::new_actor_with_actions( "notif-6", "as-cii sent you a contact request.", + DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z") + .unwrap() + .naive_local(), PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( @@ -401,12 +420,18 @@ pub fn static_new_notification_items_2() -> Vec> { Notification::new_icon_message( "notif-7", "You were mentioned in a note.", + DateTime::parse_from_rfc3339("2022-10-14T12:09:07Z") + .unwrap() + .naive_local(), Icon::AtSign, Arc::new(|_, _| {}), ), Notification::new_actor_with_actions( "notif-8", "as-cii sent you a contact request.", + DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z") + .unwrap() + .naive_local(), PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( @@ -424,12 +449,18 @@ pub fn static_new_notification_items_2() -> Vec> { Notification::new_icon_message( "notif-9", "You were mentioned in a note.", + DateTime::parse_from_rfc3339("2021-02-02T12:09:07Z") + .unwrap() + .naive_local(), Icon::AtSign, Arc::new(|_, _| {}), ), Notification::new_actor_with_actions( "notif-10", "as-cii sent you a contact request.", + DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z") + .unwrap() + .naive_local(), PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( From d48ec7d58e49ce837055cab693d3d3f5ec86f567 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 Nov 2023 11:02:46 -0400 Subject: [PATCH 117/156] Remove unused import --- crates/ui2/src/components/notifications_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 697b4de63b..74f015ac06 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -361,7 +361,7 @@ impl Notification { } } -use chrono::{DateTime, NaiveDateTime}; +use chrono::NaiveDateTime; use gpui2::{px, Styled}; #[cfg(feature = "stories")] pub use stories::*; From d4db7a14b3f9f59c1b602f3710fb05e2ee54207e Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 Nov 2023 11:04:04 -0400 Subject: [PATCH 118/156] Remove unused import --- crates/ui2/src/static_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 3964873055..68f625c75d 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; -use chrono::{DateTime, NaiveDateTime}; +use chrono::DateTime; use gpui2::{AppContext, ViewContext}; use rand::Rng; use theme2::ActiveTheme; From de80974a1d5bb3beb877aae19d22638206386b2a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 16:11:52 +0100 Subject: [PATCH 119/156] Add `ViewContext::observe_fullscreen` Co-Authored-By: Conrad Irwin --- crates/gpui2/src/window.rs | 44 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0202b7521e..667ef6c486 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -6,8 +6,9 @@ use crate::{ 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, Subscription, TaffyLayoutEngine, Task, - Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS, + SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -64,6 +65,7 @@ type AnyKeyListener = Box< + 'static, >; type AnyFocusListener = Box; +type AnyFullScreenListener = Box bool + 'static>; slotmap::new_key_type! { pub struct FocusId; } @@ -180,10 +182,12 @@ pub struct Window { focus_stack: Vec, focus_parents_by_child: HashMap, pub(crate) focus_listeners: Vec, + fullscreen_listeners: SubscriberSet<(), AnyFullScreenListener>, pub(crate) focus_handles: Arc>>, default_prevented: bool, mouse_position: Point, scale_factor: f32, + fullscreen: bool, pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, pub(crate) last_blur: Option>, @@ -202,6 +206,7 @@ impl Window { let mouse_position = platform_window.mouse_position(); let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); + platform_window.on_resize(Box::new({ let mut cx = cx.to_async(); move |content_size, scale_factor| { @@ -216,6 +221,21 @@ impl Window { .log_err(); } })); + platform_window.on_fullscreen(Box::new({ + let mut cx = cx.to_async(); + move |fullscreen| { + handle + .update(&mut cx, |_, cx| { + cx.window.fullscreen = fullscreen; + cx.window + .fullscreen_listeners + .clone() + .retain(&(), |callback| callback(fullscreen, cx)); + cx.window.dirty = true; + }) + .log_err(); + } + })); platform_window.on_input({ let mut cx = cx.to_async(); @@ -250,10 +270,12 @@ impl Window { focus_stack: Vec::new(), focus_parents_by_child: HashMap::default(), focus_listeners: Vec::new(), + fullscreen_listeners: SubscriberSet::new(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), default_prevented: true, mouse_position, scale_factor, + fullscreen: false, scene_builder: SceneBuilder::new(), dirty: true, last_blur: None, @@ -1710,6 +1732,24 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + pub fn observe_fullscreen( + &mut self, + mut callback: impl FnMut(&mut V, bool, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + self.window.fullscreen_listeners.insert( + (), + Box::new(move |fullscreen, cx| { + view.update(cx, |view, cx| callback(view, fullscreen, cx)) + .is_ok() + }), + ) + } + + // fn observe_window_activation(&mut self) -> Subscription {} + + // fn observe_window_bounds(&mut self) -> Subscription {} + pub fn on_focus_changed( &mut self, listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, From 3a824e468f8852204265f74a649b8fa652bdf4c5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 16:31:27 +0100 Subject: [PATCH 120/156] Subsume `observe_fullscreen` into `observe_window_bounds` Co-Authored-By: Conrad Irwin --- crates/gpui2/src/window.rs | 68 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 667ef6c486..8d3afc9ae0 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -8,7 +8,7 @@ use crate::{ PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowOptions, SUBPIXEL_VARIANTS, + WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -54,6 +54,7 @@ pub enum DispatchPhase { Capture, } +type AnyObserver = Box bool + 'static>; type AnyListener = Box; type AnyKeyListener = Box< dyn Fn( @@ -65,7 +66,6 @@ type AnyKeyListener = Box< + 'static, >; type AnyFocusListener = Box; -type AnyFullScreenListener = Box bool + 'static>; slotmap::new_key_type! { pub struct FocusId; } @@ -182,12 +182,12 @@ pub struct Window { focus_stack: Vec, focus_parents_by_child: HashMap, pub(crate) focus_listeners: Vec, - fullscreen_listeners: SubscriberSet<(), AnyFullScreenListener>, pub(crate) focus_handles: Arc>>, default_prevented: bool, mouse_position: Point, scale_factor: f32, - fullscreen: bool, + bounds: WindowBounds, + bounds_observers: SubscriberSet<(), AnyObserver>, pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, pub(crate) last_blur: Option>, @@ -206,33 +206,21 @@ impl Window { let mouse_position = platform_window.mouse_position(); let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); + let bounds = platform_window.bounds(); platform_window.on_resize(Box::new({ let mut cx = cx.to_async(); - move |content_size, scale_factor| { + move |_, _| { 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; - }) + .update(&mut cx, |_, cx| cx.window_bounds_changed()) .log_err(); } })); - platform_window.on_fullscreen(Box::new({ + platform_window.on_moved(Box::new({ let mut cx = cx.to_async(); - move |fullscreen| { + move || { handle - .update(&mut cx, |_, cx| { - cx.window.fullscreen = fullscreen; - cx.window - .fullscreen_listeners - .clone() - .retain(&(), |callback| callback(fullscreen, cx)); - cx.window.dirty = true; - }) + .update(&mut cx, |_, cx| cx.window_bounds_changed()) .log_err(); } })); @@ -270,12 +258,12 @@ impl Window { focus_stack: Vec::new(), focus_parents_by_child: HashMap::default(), focus_listeners: Vec::new(), - fullscreen_listeners: SubscriberSet::new(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), default_prevented: true, mouse_position, scale_factor, - fullscreen: false, + bounds, + bounds_observers: SubscriberSet::new(), scene_builder: SceneBuilder::new(), dirty: true, last_blur: None, @@ -540,6 +528,23 @@ impl<'a> WindowContext<'a> { bounds } + fn window_bounds_changed(&mut self) { + self.window.scale_factor = self.window.platform_window.scale_factor(); + self.window.content_size = self.window.platform_window.content_size(); + self.window.bounds = self.window.platform_window.bounds(); + self.window.display_id = self.window.platform_window.display().id(); + self.window.dirty = true; + + self.window + .bounds_observers + .clone() + .retain(&(), |callback| callback(self)); + } + + pub fn window_bounds(&self) -> WindowBounds { + self.window.bounds + } + /// 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. @@ -1732,23 +1737,18 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - pub fn observe_fullscreen( + pub fn observe_window_bounds( &mut self, - mut callback: impl FnMut(&mut V, bool, &mut ViewContext) + 'static, + mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, ) -> Subscription { let view = self.view.downgrade(); - self.window.fullscreen_listeners.insert( + self.window.bounds_observers.insert( (), - Box::new(move |fullscreen, cx| { - view.update(cx, |view, cx| callback(view, fullscreen, cx)) - .is_ok() - }), + Box::new(move |cx| view.update(cx, |view, cx| callback(view, cx)).is_ok()), ) } - // fn observe_window_activation(&mut self) -> Subscription {} - - // fn observe_window_bounds(&mut self) -> Subscription {} + fn observe_window_activation(&mut self) -> Subscription {} pub fn on_focus_changed( &mut self, From ec4f0d7bca73cc55c71882ab73ab96ebe6517a9c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 2 Nov 2023 16:37:22 +0100 Subject: [PATCH 121/156] Implement `ViewContext::observe_window_activation` Co-Authored-By: Conrad Irwin --- crates/gpui2/src/window.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 8d3afc9ae0..f1335b3cff 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -188,6 +188,8 @@ pub struct Window { scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, + active: bool, + activation_observers: SubscriberSet<(), AnyObserver>, pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, pub(crate) last_blur: Option>, @@ -224,6 +226,20 @@ impl Window { .log_err(); } })); + platform_window.on_active_status_change(Box::new({ + let mut cx = cx.to_async(); + move |active| { + handle + .update(&mut cx, |_, cx| { + cx.window.active = active; + cx.window + .activation_observers + .clone() + .retain(&(), |callback| callback(cx)); + }) + .log_err(); + } + })); platform_window.on_input({ let mut cx = cx.to_async(); @@ -264,6 +280,8 @@ impl Window { scale_factor, bounds, bounds_observers: SubscriberSet::new(), + active: false, + activation_observers: SubscriberSet::new(), scene_builder: SceneBuilder::new(), dirty: true, last_blur: None, @@ -1748,7 +1766,16 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) } - fn observe_window_activation(&mut self) -> Subscription {} + pub fn observe_window_activation( + &mut self, + mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + self.window.activation_observers.insert( + (), + Box::new(move |cx| view.update(cx, |view, cx| callback(view, cx)).is_ok()), + ) + } pub fn on_focus_changed( &mut self, From ec0cff0e1a251d7bf3ab9b92c8bd49de375518c5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 16:39:40 +0100 Subject: [PATCH 122/156] Add `map` method to `Component`s (#3210) This PR adds a `map` method to the `Component` trait. `map` is a fully-generalized form of `when`, as `when` can be expressed in terms of `map`: ```rs div().map(|this| if condition { then(this) } else { this }) ``` This allows us to take advantage of Rust's pattern matching when building up conditions: ```rs // Before div() .when(self.current_side == PanelSide::Left, |this| this.border_r()) .when(self.current_side == PanelSide::Right, |this| { this.border_l() }) .when(self.current_side == PanelSide::Bottom, |this| { this.border_b().w_full().h(current_size) }) // After div() .map(|this| match self.current_side { PanelSide::Left => this.border_r(), PanelSide::Right => this.border_l(), PanelSide::Bottom => this.border_b().w_full().h(current_size), }) ``` Release Notes: - N/A --- crates/gpui2/src/element.rs | 15 ++++++++++----- crates/ui2/src/components/panel.rs | 16 +++++++--------- crates/ui2/src/elements/input.rs | 15 +++++++-------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 4890b79a9a..a92dbd6ff9 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -198,14 +198,19 @@ impl AnyElement { pub trait Component { fn render(self) -> AnyElement; - fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self + fn map(self, f: impl FnOnce(Self) -> U) -> U + where + Self: Sized, + U: Component, + { + f(self) + } + + fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self where Self: Sized, { - if condition { - self = then(self); - } - self + self.map(|this| if condition { then(this) } else { this }) } } diff --git a/crates/ui2/src/components/panel.rs b/crates/ui2/src/components/panel.rs index c4a9ac5111..5d941eb50e 100644 --- a/crates/ui2/src/components/panel.rs +++ b/crates/ui2/src/components/panel.rs @@ -98,16 +98,14 @@ impl Panel { v_stack() .id(self.id.clone()) .flex_initial() - .when( - self.current_side == PanelSide::Left || self.current_side == PanelSide::Right, - |this| this.h_full().w(current_size), - ) - .when(self.current_side == PanelSide::Left, |this| this.border_r()) - .when(self.current_side == PanelSide::Right, |this| { - this.border_l() + .map(|this| match self.current_side { + PanelSide::Left | PanelSide::Right => this.h_full().w(current_size), + PanelSide::Bottom => this, }) - .when(self.current_side == PanelSide::Bottom, |this| { - this.border_b().w_full().h(current_size) + .map(|this| match self.current_side { + PanelSide::Left => this.border_r(), + PanelSide::Right => this.border_l(), + PanelSide::Bottom => this.border_b().w_full().h(current_size), }) .bg(cx.theme().colors().surface) .border_color(cx.theme().colors().border) diff --git a/crates/ui2/src/elements/input.rs b/crates/ui2/src/elements/input.rs index 3f82512b84..2884470ce2 100644 --- a/crates/ui2/src/elements/input.rs +++ b/crates/ui2/src/elements/input.rs @@ -94,14 +94,13 @@ impl Input { .active(|style| style.bg(input_active_bg)) .flex() .items_center() - .child( - div() - .flex() - .items_center() - .text_sm() - .when(self.value.is_empty(), |this| this.child(placeholder_label)) - .when(!self.value.is_empty(), |this| this.child(label)), - ) + .child(div().flex().items_center().text_sm().map(|this| { + if self.value.is_empty() { + this.child(placeholder_label) + } else { + this.child(label) + } + })) } } From b7efab8a55cf4037ee6aee95f9754fb7fb720e97 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 2 Nov 2023 11:42:48 -0400 Subject: [PATCH 123/156] Uncomment lsp2 tests --- crates/lsp2/src/lsp2.rs | 128 ++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/crates/lsp2/src/lsp2.rs b/crates/lsp2/src/lsp2.rs index ed67a5c9c2..8871c8eaef 100644 --- a/crates/lsp2/src/lsp2.rs +++ b/crates/lsp2/src/lsp2.rs @@ -1107,74 +1107,74 @@ impl FakeLanguageServer { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use gpui::TestAppContext; +#[cfg(test)] +mod tests { + use super::*; + use gpui2::TestAppContext; -// #[ctor::ctor] -// fn init_logger() { -// if std::env::var("RUST_LOG").is_ok() { -// env_logger::init(); -// } -// } + #[ctor::ctor] + fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } + } -// #[gpui::test] -// async fn test_fake(cx: &mut TestAppContext) { -// let (server, mut fake) = -// LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async()); + #[gpui2::test] + async fn test_fake(cx: &mut TestAppContext) { + let (server, mut fake) = + LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async()); -// let (message_tx, message_rx) = channel::unbounded(); -// let (diagnostics_tx, diagnostics_rx) = channel::unbounded(); -// server -// .on_notification::(move |params, _| { -// message_tx.try_send(params).unwrap() -// }) -// .detach(); -// server -// .on_notification::(move |params, _| { -// diagnostics_tx.try_send(params).unwrap() -// }) -// .detach(); + let (message_tx, message_rx) = channel::unbounded(); + let (diagnostics_tx, diagnostics_rx) = channel::unbounded(); + server + .on_notification::(move |params, _| { + message_tx.try_send(params).unwrap() + }) + .detach(); + server + .on_notification::(move |params, _| { + diagnostics_tx.try_send(params).unwrap() + }) + .detach(); -// let server = server.initialize(None).await.unwrap(); -// server -// .notify::(DidOpenTextDocumentParams { -// text_document: TextDocumentItem::new( -// Url::from_str("file://a/b").unwrap(), -// "rust".to_string(), -// 0, -// "".to_string(), -// ), -// }) -// .unwrap(); -// assert_eq!( -// fake.receive_notification::() -// .await -// .text_document -// .uri -// .as_str(), -// "file://a/b" -// ); + let server = server.initialize(None).await.unwrap(); + server + .notify::(DidOpenTextDocumentParams { + text_document: TextDocumentItem::new( + Url::from_str("file://a/b").unwrap(), + "rust".to_string(), + 0, + "".to_string(), + ), + }) + .unwrap(); + assert_eq!( + fake.receive_notification::() + .await + .text_document + .uri + .as_str(), + "file://a/b" + ); -// fake.notify::(ShowMessageParams { -// typ: MessageType::ERROR, -// message: "ok".to_string(), -// }); -// fake.notify::(PublishDiagnosticsParams { -// uri: Url::from_str("file://b/c").unwrap(), -// version: Some(5), -// diagnostics: vec![], -// }); -// assert_eq!(message_rx.recv().await.unwrap().message, "ok"); -// assert_eq!( -// diagnostics_rx.recv().await.unwrap().uri.as_str(), -// "file://b/c" -// ); + fake.notify::(ShowMessageParams { + typ: MessageType::ERROR, + message: "ok".to_string(), + }); + fake.notify::(PublishDiagnosticsParams { + uri: Url::from_str("file://b/c").unwrap(), + version: Some(5), + diagnostics: vec![], + }); + assert_eq!(message_rx.recv().await.unwrap().message, "ok"); + assert_eq!( + diagnostics_rx.recv().await.unwrap().uri.as_str(), + "file://b/c" + ); -// fake.handle_request::(|_, _| async move { Ok(()) }); + fake.handle_request::(|_, _| async move { Ok(()) }); -// drop(server); -// fake.receive_notification::().await; -// } -// } + drop(server); + fake.receive_notification::().await; + } +} From 6f41a77a252eb221a0d11bec16c28a0520c92311 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 Nov 2023 11:53:20 -0400 Subject: [PATCH 124/156] Document format_distance Adds docs for: - distance_in_seconds - distance_string - naive_format_distance - naive_format_distance_from_now --- crates/ui2/src/utils/format_distance.rs | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/ui2/src/utils/format_distance.rs b/crates/ui2/src/utils/format_distance.rs index 8a33619910..133eabc202 100644 --- a/crates/ui2/src/utils/format_distance.rs +++ b/crates/ui2/src/utils/format_distance.rs @@ -1,10 +1,18 @@ use chrono::NaiveDateTime; +/// Calculates the distance in seconds between two NaiveDateTime objects. +/// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative. +/// +/// ## Arguments +/// +/// * `date` - A NaiveDateTime object representing the date of interest +/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { let duration = date.signed_duration_since(base_date); -duration.num_seconds() } +/// Generates a string describing the time distance between two dates in a human-readable way. fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String { let suffix = if distance < 0 { " from now" } else { " ago" }; @@ -74,6 +82,33 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St } } +/// Get the time difference between two dates into a relative human readable string. +/// +/// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. +/// +/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now. +/// +/// # Arguments +/// +/// * `date` - The NaiveDateTime to compare. +/// * `base_date` - The NaiveDateTime to compare against. +/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed +/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future +/// +/// # Example +/// +/// ```rust +/// use chrono::DateTime; +/// use ui2::utils::naive_format_distance; +/// +/// fn time_between_moon_landings() -> String { +/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); +/// let base_date = DateTime::parse_from_rfc3339("1972-12-14T00:00:00Z").unwrap().naive_local(); +/// format!("There was {} between the first and last crewed moon landings.", naive_format_distance(date, base_date, false, false)) +/// } +/// ``` +/// +/// Output: `"There was about 3 years between the first and last crewed moon landings."` pub fn naive_format_distance( date: NaiveDateTime, base_date: NaiveDateTime, @@ -85,6 +120,29 @@ pub fn naive_format_distance( distance_string(distance, include_seconds, add_suffix) } +/// Get the time difference between a date and now as relative human readable string. +/// +/// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. +/// +/// # Arguments +/// +/// * `datetime` - The NaiveDateTime to compare with the current time. +/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed +/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future +/// +/// # Example +/// +/// ```rust +/// use chrono::DateTime; +/// use ui2::utils::naive_format_distance_from_now; +/// +/// fn time_since_first_moon_landing() -> String { +/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); +/// format!("It's been {} since Apollo 11 first landed on the moon.", naive_format_distance_from_now(date, false, false)) +/// } +/// ``` +/// +/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.` pub fn naive_format_distance_from_now( datetime: NaiveDateTime, include_seconds: bool, From 634aba89d29c0cf04d54b17c92e622988b89edbe Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 10:03:03 -0600 Subject: [PATCH 125/156] 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 126/156] 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 127/156] 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 128/156] 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 129/156] 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 130/156] 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 131/156] 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 132/156] 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 133/156] 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 134/156] 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 135/156] 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 136/156] 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 137/156] 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 138/156] 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 139/156] 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 140/156] 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 141/156] 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); From cbd902658c879f057ad842689c2b681bf67ae7af Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 19:18:01 -0600 Subject: [PATCH 142/156] git -> git3 This is needed for the editor. --- Cargo.lock | 34 ++- crates/client2/Cargo.toml | 2 +- crates/fs2/Cargo.toml | 2 +- crates/git3/Cargo.toml | 30 +++ crates/git3/src/diff.rs | 412 ++++++++++++++++++++++++++++++++ crates/git3/src/git.rs | 11 + crates/language2/Cargo.toml | 6 +- crates/multi_buffer2/Cargo.toml | 6 +- crates/project2/Cargo.toml | 4 +- 9 files changed, 490 insertions(+), 17 deletions(-) create mode 100644 crates/git3/Cargo.toml create mode 100644 crates/git3/src/diff.rs create mode 100644 crates/git3/src/git.rs diff --git a/Cargo.lock b/Cargo.lock index db8e88cb1c..8b7ea1c641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3079,7 +3079,7 @@ dependencies = [ "smol", "sum_tree", "tempfile", - "text", + "text2", "time", "util", ] @@ -3371,6 +3371,26 @@ dependencies = [ "url", ] +[[package]] +name = "git3" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "clock", + "collections", + "futures 0.3.28", + "git2", + "lazy_static", + "log", + "parking_lot 0.11.2", + "smol", + "sum_tree", + "text2", + "unindent", + "util", +] + [[package]] name = "glob" version = "0.3.1" @@ -4345,7 +4365,7 @@ dependencies = [ "env_logger 0.9.3", "futures 0.3.28", "fuzzy2", - "git", + "git3", "globset", "gpui2", "indoc", @@ -4366,7 +4386,7 @@ dependencies = [ "smallvec", "smol", "sum_tree", - "text", + "text2", "theme2", "tree-sitter", "tree-sitter-elixir", @@ -5081,7 +5101,7 @@ dependencies = [ "ctor", "env_logger 0.9.3", "futures 0.3.28", - "git", + "git3", "gpui2", "indoc", "itertools 0.10.5", @@ -5104,7 +5124,7 @@ dependencies = [ "smol", "snippet", "sum_tree", - "text", + "text2", "theme2", "tree-sitter", "tree-sitter-html", @@ -6284,8 +6304,8 @@ dependencies = [ "fsevent", "futures 0.3.28", "fuzzy2", - "git", "git2", + "git3", "globset", "gpui2", "ignore", @@ -6313,7 +6333,7 @@ dependencies = [ "sum_tree", "tempdir", "terminal2", - "text", + "text2", "thiserror", "toml 0.5.11", "unindent", diff --git a/crates/client2/Cargo.toml b/crates/client2/Cargo.toml index 45e1f618d2..ace229bc21 100644 --- a/crates/client2/Cargo.toml +++ b/crates/client2/Cargo.toml @@ -17,7 +17,7 @@ db = { package = "db2", path = "../db2" } gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } rpc = { package = "rpc2", path = "../rpc2" } -text = { path = "../text" } +text = { package = "text2", path = "../text2" } settings = { package = "settings2", path = "../settings2" } feature_flags = { package = "feature_flags2", path = "../feature_flags2" } sum_tree = { path = "../sum_tree" } diff --git a/crates/fs2/Cargo.toml b/crates/fs2/Cargo.toml index 636def05ec..ca525afe5f 100644 --- a/crates/fs2/Cargo.toml +++ b/crates/fs2/Cargo.toml @@ -10,7 +10,7 @@ path = "src/fs2.rs" [dependencies] collections = { path = "../collections" } rope = { path = "../rope" } -text = { path = "../text" } +text = { package = "text2", path = "../text2" } util = { path = "../util" } sum_tree = { path = "../sum_tree" } diff --git a/crates/git3/Cargo.toml b/crates/git3/Cargo.toml new file mode 100644 index 0000000000..e88fa6574d --- /dev/null +++ b/crates/git3/Cargo.toml @@ -0,0 +1,30 @@ +[package] +# git2 was already taken. +name = "git3" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/git.rs" + +[dependencies] +anyhow.workspace = true +clock = { path = "../clock" } +lazy_static.workspace = true +sum_tree = { path = "../sum_tree" } +text = { package = "text2", path = "../text2" } +collections = { path = "../collections" } +util = { path = "../util" } +log.workspace = true +smol.workspace = true +parking_lot.workspace = true +async-trait.workspace = true +futures.workspace = true +git2.workspace = true + +[dev-dependencies] +unindent.workspace = true + +[features] +test-support = [] diff --git a/crates/git3/src/diff.rs b/crates/git3/src/diff.rs new file mode 100644 index 0000000000..39383cfc78 --- /dev/null +++ b/crates/git3/src/diff.rs @@ -0,0 +1,412 @@ +use std::{iter, ops::Range}; +use sum_tree::SumTree; +use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point}; + +pub use git2 as libgit; +use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DiffHunkStatus { + Added, + Modified, + Removed, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DiffHunk { + pub buffer_range: Range, + pub diff_base_byte_range: Range, +} + +impl DiffHunk { + pub fn status(&self) -> DiffHunkStatus { + if self.diff_base_byte_range.is_empty() { + DiffHunkStatus::Added + } else if self.buffer_range.is_empty() { + DiffHunkStatus::Removed + } else { + DiffHunkStatus::Modified + } + } +} + +impl sum_tree::Item for DiffHunk { + type Summary = DiffHunkSummary; + + fn summary(&self) -> Self::Summary { + DiffHunkSummary { + buffer_range: self.buffer_range.clone(), + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct DiffHunkSummary { + buffer_range: Range, +} + +impl sum_tree::Summary for DiffHunkSummary { + type Context = text::BufferSnapshot; + + fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { + self.buffer_range.start = self + .buffer_range + .start + .min(&other.buffer_range.start, buffer); + self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer); + } +} + +#[derive(Clone)] +pub struct BufferDiff { + last_buffer_version: Option, + tree: SumTree>, +} + +impl BufferDiff { + pub fn new() -> BufferDiff { + BufferDiff { + last_buffer_version: None, + tree: SumTree::new(), + } + } + + pub fn is_empty(&self) -> bool { + self.tree.is_empty() + } + + pub fn hunks_in_row_range<'a>( + &'a self, + range: Range, + buffer: &'a BufferSnapshot, + ) -> impl 'a + Iterator> { + let start = buffer.anchor_before(Point::new(range.start, 0)); + let end = buffer.anchor_after(Point::new(range.end, 0)); + + self.hunks_intersecting_range(start..end, buffer) + } + + pub fn hunks_intersecting_range<'a>( + &'a self, + range: Range, + buffer: &'a BufferSnapshot, + ) -> impl 'a + Iterator> { + let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| { + let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); + let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); + !before_start && !after_end + }); + + let anchor_iter = std::iter::from_fn(move || { + cursor.next(buffer); + cursor.item() + }) + .flat_map(move |hunk| { + [ + (&hunk.buffer_range.start, hunk.diff_base_byte_range.start), + (&hunk.buffer_range.end, hunk.diff_base_byte_range.end), + ] + .into_iter() + }); + + let mut summaries = buffer.summaries_for_anchors_with_payload::(anchor_iter); + iter::from_fn(move || { + let (start_point, start_base) = summaries.next()?; + let (end_point, end_base) = summaries.next()?; + + let end_row = if end_point.column > 0 { + end_point.row + 1 + } else { + end_point.row + }; + + Some(DiffHunk { + buffer_range: start_point.row..end_row, + diff_base_byte_range: start_base..end_base, + }) + }) + } + + pub fn hunks_intersecting_range_rev<'a>( + &'a self, + range: Range, + buffer: &'a BufferSnapshot, + ) -> impl 'a + Iterator> { + let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| { + let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); + let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); + !before_start && !after_end + }); + + std::iter::from_fn(move || { + cursor.prev(buffer); + + let hunk = cursor.item()?; + let range = hunk.buffer_range.to_point(buffer); + let end_row = if range.end.column > 0 { + range.end.row + 1 + } else { + range.end.row + }; + + Some(DiffHunk { + buffer_range: range.start.row..end_row, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }) + } + + pub fn clear(&mut self, buffer: &text::BufferSnapshot) { + self.last_buffer_version = Some(buffer.version().clone()); + self.tree = SumTree::new(); + } + + pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) { + let mut tree = SumTree::new(); + + let buffer_text = buffer.as_rope().to_string(); + let patch = Self::diff(&diff_base, &buffer_text); + + if let Some(patch) = patch { + let mut divergence = 0; + for hunk_index in 0..patch.num_hunks() { + let hunk = Self::process_patch_hunk(&patch, hunk_index, buffer, &mut divergence); + tree.push(hunk, buffer); + } + } + + self.tree = tree; + self.last_buffer_version = Some(buffer.version().clone()); + } + + #[cfg(test)] + fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator> { + let start = text.anchor_before(Point::new(0, 0)); + let end = text.anchor_after(Point::new(u32::MAX, u32::MAX)); + self.hunks_intersecting_range(start..end, text) + } + + fn diff<'a>(head: &'a str, current: &'a str) -> Option> { + let mut options = GitOptions::default(); + options.context_lines(0); + + let patch = GitPatch::from_buffers( + head.as_bytes(), + None, + current.as_bytes(), + None, + Some(&mut options), + ); + + match patch { + Ok(patch) => Some(patch), + + Err(err) => { + log::error!("`GitPatch::from_buffers` failed: {}", err); + None + } + } + } + + fn process_patch_hunk<'a>( + patch: &GitPatch<'a>, + hunk_index: usize, + buffer: &text::BufferSnapshot, + buffer_row_divergence: &mut i64, + ) -> DiffHunk { + let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap(); + assert!(line_item_count > 0); + + let mut first_deletion_buffer_row: Option = None; + let mut buffer_row_range: Option> = None; + let mut diff_base_byte_range: Option> = None; + + for line_index in 0..line_item_count { + let line = patch.line_in_hunk(hunk_index, line_index).unwrap(); + let kind = line.origin_value(); + let content_offset = line.content_offset() as isize; + let content_len = line.content().len() as isize; + + if kind == GitDiffLineType::Addition { + *buffer_row_divergence += 1; + let row = line.new_lineno().unwrap().saturating_sub(1); + + match &mut buffer_row_range { + Some(buffer_row_range) => buffer_row_range.end = row + 1, + None => buffer_row_range = Some(row..row + 1), + } + } + + if kind == GitDiffLineType::Deletion { + let end = content_offset + content_len; + + match &mut diff_base_byte_range { + Some(head_byte_range) => head_byte_range.end = end as usize, + None => diff_base_byte_range = Some(content_offset as usize..end as usize), + } + + if first_deletion_buffer_row.is_none() { + let old_row = line.old_lineno().unwrap().saturating_sub(1); + let row = old_row as i64 + *buffer_row_divergence; + first_deletion_buffer_row = Some(row as u32); + } + + *buffer_row_divergence -= 1; + } + } + + //unwrap_or deletion without addition + let buffer_row_range = buffer_row_range.unwrap_or_else(|| { + //we cannot have an addition-less hunk without deletion(s) or else there would be no hunk + let row = first_deletion_buffer_row.unwrap(); + row..row + }); + + //unwrap_or addition without deletion + let diff_base_byte_range = diff_base_byte_range.unwrap_or(0..0); + + let start = Point::new(buffer_row_range.start, 0); + let end = Point::new(buffer_row_range.end, 0); + let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end); + DiffHunk { + buffer_range, + diff_base_byte_range, + } + } +} + +/// Range (crossing new lines), old, new +#[cfg(any(test, feature = "test-support"))] +#[track_caller] +pub fn assert_hunks( + diff_hunks: Iter, + buffer: &BufferSnapshot, + diff_base: &str, + expected_hunks: &[(Range, &str, &str)], +) where + Iter: Iterator>, +{ + let actual_hunks = diff_hunks + .map(|hunk| { + ( + hunk.buffer_range.clone(), + &diff_base[hunk.diff_base_byte_range], + buffer + .text_for_range( + Point::new(hunk.buffer_range.start, 0) + ..Point::new(hunk.buffer_range.end, 0), + ) + .collect::(), + ) + }) + .collect::>(); + + let expected_hunks: Vec<_> = expected_hunks + .iter() + .map(|(r, s, h)| (r.clone(), *s, h.to_string())) + .collect(); + + assert_eq!(actual_hunks, expected_hunks); +} + +#[cfg(test)] +mod tests { + use std::assert_eq; + + use super::*; + use text::Buffer; + use unindent::Unindent as _; + + #[test] + fn test_buffer_diff_simple() { + let diff_base = " + one + two + three + " + .unindent(); + + let buffer_text = " + one + HELLO + three + " + .unindent(); + + let mut buffer = Buffer::new(0, 0, buffer_text); + let mut diff = BufferDiff::new(); + smol::block_on(diff.update(&diff_base, &buffer)); + assert_hunks( + diff.hunks(&buffer), + &buffer, + &diff_base, + &[(1..2, "two\n", "HELLO\n")], + ); + + buffer.edit([(0..0, "point five\n")]); + smol::block_on(diff.update(&diff_base, &buffer)); + assert_hunks( + diff.hunks(&buffer), + &buffer, + &diff_base, + &[(0..1, "", "point five\n"), (2..3, "two\n", "HELLO\n")], + ); + + diff.clear(&buffer); + assert_hunks(diff.hunks(&buffer), &buffer, &diff_base, &[]); + } + + #[test] + fn test_buffer_diff_range() { + let diff_base = " + one + two + three + four + five + six + seven + eight + nine + ten + " + .unindent(); + + let buffer_text = " + A + one + B + two + C + three + HELLO + four + five + SIXTEEN + seven + eight + WORLD + nine + + ten + + " + .unindent(); + + let buffer = Buffer::new(0, 0, buffer_text); + let mut diff = BufferDiff::new(); + smol::block_on(diff.update(&diff_base, &buffer)); + assert_eq!(diff.hunks(&buffer).count(), 8); + + assert_hunks( + diff.hunks_in_row_range(7..12, &buffer), + &buffer, + &diff_base, + &[ + (6..7, "", "HELLO\n"), + (9..10, "six\n", "SIXTEEN\n"), + (12..13, "", "WORLD\n"), + ], + ); + } +} diff --git a/crates/git3/src/git.rs b/crates/git3/src/git.rs new file mode 100644 index 0000000000..b1b885eca2 --- /dev/null +++ b/crates/git3/src/git.rs @@ -0,0 +1,11 @@ +use std::ffi::OsStr; + +pub use git2 as libgit; +pub use lazy_static::lazy_static; + +pub mod diff; + +lazy_static! { + pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git"); + pub static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore"); +} diff --git a/crates/language2/Cargo.toml b/crates/language2/Cargo.toml index 4fca16bcb5..0e4d9addfa 100644 --- a/crates/language2/Cargo.toml +++ b/crates/language2/Cargo.toml @@ -25,13 +25,13 @@ test-support = [ clock = { path = "../clock" } collections = { path = "../collections" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } -git = { path = "../git" } +git = { package = "git3", path = "../git3" } 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" } +text = { package = "text2", path = "../text2" } theme = { package = "theme2", path = "../theme2" } util = { path = "../util" } @@ -64,7 +64,7 @@ client = { package = "client2", path = "../client2", features = ["test-support"] collections = { path = "../collections", 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"] } +text = { package = "text2", path = "../text2", features = ["test-support"] } settings = { package = "settings2", path = "../settings2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } ctor.workspace = true diff --git a/crates/multi_buffer2/Cargo.toml b/crates/multi_buffer2/Cargo.toml index a57ef29531..4b69edd5a8 100644 --- a/crates/multi_buffer2/Cargo.toml +++ b/crates/multi_buffer2/Cargo.toml @@ -23,7 +23,7 @@ test-support = [ client = { package = "client2", path = "../client2" } clock = { path = "../clock" } collections = { path = "../collections" } -git = { path = "../git" } +git = { package = "git3", path = "../git3" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } lsp = { package = "lsp2", path = "../lsp2" } @@ -31,7 +31,7 @@ rich_text = { path = "../rich_text" } settings = { package = "settings2", path = "../settings2" } snippet = { path = "../snippet" } sum_tree = { path = "../sum_tree" } -text = { path = "../text" } +text = { package = "text2", path = "../text2" } theme = { package = "theme2", path = "../theme2" } util = { path = "../util" } @@ -60,7 +60,7 @@ tree-sitter-typescript = { workspace = true, optional = true } [dev-dependencies] copilot = { package = "copilot2", path = "../copilot2", features = ["test-support"] } -text = { path = "../text", features = ["test-support"] } +text = { package = "text2", path = "../text2", 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"] } diff --git a/crates/project2/Cargo.toml b/crates/project2/Cargo.toml index 7aae9fb007..892ddb91c7 100644 --- a/crates/project2/Cargo.toml +++ b/crates/project2/Cargo.toml @@ -20,7 +20,7 @@ test-support = [ ] [dependencies] -text = { path = "../text" } +text = { package = "text2", path = "../text2" } copilot = { package = "copilot2", path = "../copilot2" } client = { package = "client2", path = "../client2" } clock = { path = "../clock" } @@ -29,7 +29,7 @@ db = { package = "db2", path = "../db2" } fs = { package = "fs2", path = "../fs2" } fsevent = { path = "../fsevent" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } -git = { path = "../git" } +git = { package = "git3", path = "../git3" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } lsp = { package = "lsp2", path = "../lsp2" } From 6ecb4805f76891344286c18eb81f7aa8e464adf4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 19:19:18 -0600 Subject: [PATCH 143/156] Add rich_text2 --- Cargo.lock | 2 +- crates/multi_buffer2/Cargo.toml | 2 +- crates/rich_text2/src/rich_text.rs | 379 +++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 crates/rich_text2/src/rich_text.rs diff --git a/Cargo.lock b/Cargo.lock index 8b7ea1c641..70586ed6f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,7 +1546,7 @@ dependencies = [ "sum_tree", "sysinfo", "tempfile", - "text", + "text2", "thiserror", "time", "tiny_http", diff --git a/crates/multi_buffer2/Cargo.toml b/crates/multi_buffer2/Cargo.toml index 4b69edd5a8..98b96dfa1d 100644 --- a/crates/multi_buffer2/Cargo.toml +++ b/crates/multi_buffer2/Cargo.toml @@ -27,7 +27,7 @@ git = { package = "git3", path = "../git3" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } lsp = { package = "lsp2", path = "../lsp2" } -rich_text = { path = "../rich_text" } +rich_text = { package = "rich_text2", path = "../rich_text2" } settings = { package = "settings2", path = "../settings2" } snippet = { path = "../snippet" } sum_tree = { path = "../sum_tree" } diff --git a/crates/rich_text2/src/rich_text.rs b/crates/rich_text2/src/rich_text.rs new file mode 100644 index 0000000000..9a8f4a1457 --- /dev/null +++ b/crates/rich_text2/src/rich_text.rs @@ -0,0 +1,379 @@ +use std::{ops::Range, sync::Arc}; + +use anyhow::bail; +use futures::FutureExt; +use gpui::{ + elements::Text, + fonts::{HighlightStyle, Underline, Weight}, + platform::{CursorStyle, MouseButton}, + AnyElement, CursorRegion, Element, MouseRegion, ViewContext, +}; +use language::{HighlightId, Language, LanguageRegistry}; +use theme::{RichTextStyle, SyntaxTheme}; +use util::RangeExt; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Highlight { + Id(HighlightId), + Highlight(HighlightStyle), + Mention, + SelfMention, +} + +impl From for Highlight { + fn from(style: HighlightStyle) -> Self { + Self::Highlight(style) + } +} + +impl From for Highlight { + fn from(style: HighlightId) -> Self { + Self::Id(style) + } +} + +#[derive(Debug, Clone)] +pub struct RichText { + pub text: String, + pub highlights: Vec<(Range, Highlight)>, + pub region_ranges: Vec>, + pub regions: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BackgroundKind { + Code, + /// A mention background for non-self user. + Mention, + SelfMention, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RenderedRegion { + pub background_kind: Option, + pub link_url: Option, +} + +/// Allows one to specify extra links to the rendered markdown, which can be used +/// for e.g. mentions. +pub struct Mention { + pub range: Range, + pub is_self_mention: bool, +} + +impl RichText { + pub fn element( + &self, + syntax: Arc, + style: RichTextStyle, + cx: &mut ViewContext, + ) -> AnyElement { + todo!(); + + // let mut region_id = 0; + // let view_id = cx.view_id(); + + // let regions = self.regions.clone(); + + // enum Markdown {} + // Text::new(self.text.clone(), style.text.clone()) + // .with_highlights( + // self.highlights + // .iter() + // .filter_map(|(range, highlight)| { + // let style = match highlight { + // Highlight::Id(id) => id.style(&syntax)?, + // Highlight::Highlight(style) => style.clone(), + // Highlight::Mention => style.mention_highlight, + // Highlight::SelfMention => style.self_mention_highlight, + // }; + // Some((range.clone(), style)) + // }) + // .collect::>(), + // ) + // .with_custom_runs(self.region_ranges.clone(), move |ix, bounds, cx| { + // region_id += 1; + // let region = regions[ix].clone(); + // if let Some(url) = region.link_url { + // cx.scene().push_cursor_region(CursorRegion { + // bounds, + // style: CursorStyle::PointingHand, + // }); + // cx.scene().push_mouse_region( + // MouseRegion::new::(view_id, region_id, bounds) + // .on_click::(MouseButton::Left, move |_, _, cx| { + // cx.platform().open_url(&url) + // }), + // ); + // } + // if let Some(region_kind) = ®ion.background_kind { + // let background = match region_kind { + // BackgroundKind::Code => style.code_background, + // BackgroundKind::Mention => style.mention_background, + // BackgroundKind::SelfMention => style.self_mention_background, + // }; + // if background.is_some() { + // cx.scene().push_quad(gpui::Quad { + // bounds, + // background, + // border: Default::default(), + // corner_radii: (2.0).into(), + // }); + // } + // } + // }) + // .with_soft_wrap(true) + // .into_any() + } + + pub fn add_mention( + &mut self, + range: Range, + is_current_user: bool, + mention_style: HighlightStyle, + ) -> anyhow::Result<()> { + if range.end > self.text.len() { + bail!( + "Mention in range {range:?} is outside of bounds for a message of length {}", + self.text.len() + ); + } + + if is_current_user { + self.region_ranges.push(range.clone()); + self.regions.push(RenderedRegion { + background_kind: Some(BackgroundKind::Mention), + link_url: None, + }); + } + self.highlights + .push((range, Highlight::Highlight(mention_style))); + Ok(()) + } +} + +pub fn render_markdown_mut( + block: &str, + mut mentions: &[Mention], + language_registry: &Arc, + language: Option<&Arc>, + data: &mut RichText, +) { + use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + let options = Options::all(); + for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() { + let prev_len = data.text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code(&mut data.text, &mut data.highlights, t.as_ref(), language); + } else { + if let Some(mention) = mentions.first() { + if source_range.contains_inclusive(&mention.range) { + mentions = &mentions[1..]; + let range = (prev_len + mention.range.start - source_range.start) + ..(prev_len + mention.range.end - source_range.start); + data.highlights.push(( + range.clone(), + if mention.is_self_mention { + Highlight::SelfMention + } else { + Highlight::Mention + }, + )); + data.region_ranges.push(range); + data.regions.push(RenderedRegion { + background_kind: Some(if mention.is_self_mention { + BackgroundKind::SelfMention + } else { + BackgroundKind::Mention + }), + link_url: None, + }); + } + } + + data.text.push_str(t.as_ref()); + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + data.region_ranges.push(prev_len..data.text.len()); + data.regions.push(RenderedRegion { + link_url: Some(link_url), + background_kind: None, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = data.highlights.last_mut() { + if last_range.end == prev_len + && last_style == &Highlight::Highlight(style) + { + last_range.end = data.text.len(); + new_highlight = false; + } + } + if new_highlight { + data.highlights + .push((prev_len..data.text.len(), Highlight::Highlight(style))); + } + } + } + } + Event::Code(t) => { + data.text.push_str(t.as_ref()); + data.region_ranges.push(prev_len..data.text.len()); + if link_url.is_some() { + data.highlights.push(( + prev_len..data.text.len(), + Highlight::Highlight(HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }), + )); + } + data.regions.push(RenderedRegion { + background_kind: Some(BackgroundKind::Code), + link_url: link_url.clone(), + }); + } + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut data.text, &mut list_stack), + Tag::Heading(_, _, _) => { + new_paragraph(&mut data.text, &mut list_stack); + bold_depth += 1; + } + Tag::CodeBlock(kind) => { + new_paragraph(&mut data.text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.cloned() + } + } + Tag::Emphasis => italic_depth += 1, + Tag::Strong => bold_depth += 1, + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + Tag::List(number) => { + list_stack.push((number, false)); + } + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !data.text.is_empty() && !data.text.ends_with('\n') { + data.text.push('\n'); + } + for _ in 0..len - 1 { + data.text.push_str(" "); + } + if let Some(number) = list_number { + data.text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + data.text.push_str("- "); + } + } + } + _ => {} + }, + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + Event::HardBreak => data.text.push('\n'), + Event::SoftBreak => data.text.push(' '), + _ => {} + } + } +} + +pub fn render_markdown( + block: String, + mentions: &[Mention], + language_registry: &Arc, + language: Option<&Arc>, +) -> RichText { + let mut data = RichText { + text: Default::default(), + highlights: Default::default(), + region_ranges: Default::default(), + regions: Default::default(), + }; + + render_markdown_mut(&block, mentions, language_registry, language, &mut data); + + data.text = data.text.trim().to_string(); + + data +} + +pub fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, Highlight)>, + content: &str, + language: &Arc, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + highlights.push(( + prev_len + range.start..prev_len + range.end, + Highlight::Id(highlight_id), + )); + } +} + +pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } +} From 0b3932f38cd9ad53df7513b30b9a7fde5cd07fbc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 2 Nov 2023 19:30:33 -0600 Subject: [PATCH 144/156] Convert rich_text2 --- Cargo.lock | 22 ++++++++++++++++++++-- crates/rich_text2/Cargo.toml | 29 +++++++++++++++++++++++++++++ crates/rich_text2/src/rich_text.rs | 22 ++++++++-------------- crates/theme2/Cargo.toml | 4 ++-- 4 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 crates/rich_text2/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 70586ed6f4..64bfe4edf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5115,7 +5115,7 @@ dependencies = [ "project2", "pulldown-cmark", "rand 0.8.5", - "rich_text", + "rich_text2", "schemars", "serde", "serde_derive", @@ -6947,6 +6947,24 @@ dependencies = [ "util", ] +[[package]] +name = "rich_text2" +version = "0.1.0" +dependencies = [ + "anyhow", + "collections", + "futures 0.3.28", + "gpui2", + "language2", + "lazy_static", + "pulldown-cmark", + "smallvec", + "smol", + "sum_tree", + "theme2", + "util", +] + [[package]] name = "ring" version = "0.16.20" @@ -8876,7 +8894,7 @@ name = "theme2" version = "0.1.0" dependencies = [ "anyhow", - "fs", + "fs2", "gpui2", "indexmap 1.9.3", "parking_lot 0.11.2", diff --git a/crates/rich_text2/Cargo.toml b/crates/rich_text2/Cargo.toml new file mode 100644 index 0000000000..4eee1e107b --- /dev/null +++ b/crates/rich_text2/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rich_text2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/rich_text.rs" +doctest = false + +[features] +test-support = [ + "gpui/test-support", + "util/test-support", +] + +[dependencies] +collections = { path = "../collections" } +gpui = { package = "gpui2", path = "../gpui2" } +sum_tree = { path = "../sum_tree" } +theme = { package = "theme2", path = "../theme2" } +language = { package = "language2", path = "../language2" } +util = { path = "../util" } +anyhow.workspace = true +futures.workspace = true +lazy_static.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } +smallvec.workspace = true +smol.workspace = true diff --git a/crates/rich_text2/src/rich_text.rs b/crates/rich_text2/src/rich_text.rs index 9a8f4a1457..48b530b7c5 100644 --- a/crates/rich_text2/src/rich_text.rs +++ b/crates/rich_text2/src/rich_text.rs @@ -2,14 +2,8 @@ use std::{ops::Range, sync::Arc}; use anyhow::bail; use futures::FutureExt; -use gpui::{ - elements::Text, - fonts::{HighlightStyle, Underline, Weight}, - platform::{CursorStyle, MouseButton}, - AnyElement, CursorRegion, Element, MouseRegion, ViewContext, -}; +use gpui::{AnyElement, FontStyle, FontWeight, HighlightStyle, UnderlineStyle}; use language::{HighlightId, Language, LanguageRegistry}; -use theme::{RichTextStyle, SyntaxTheme}; use util::RangeExt; #[derive(Debug, Clone, PartialEq, Eq)] @@ -64,9 +58,9 @@ pub struct Mention { impl RichText { pub fn element( &self, - syntax: Arc, - style: RichTextStyle, - cx: &mut ViewContext, + // syntax: Arc, + // style: RichTextStyle, + // cx: &mut ViewContext, ) -> AnyElement { todo!(); @@ -203,10 +197,10 @@ pub fn render_markdown_mut( data.text.push_str(t.as_ref()); let mut style = HighlightStyle::default(); if bold_depth > 0 { - style.weight = Some(Weight::BOLD); + style.font_weight = Some(FontWeight::BOLD); } if italic_depth > 0 { - style.italic = Some(true); + style.font_style = Some(FontStyle::Italic); } if let Some(link_url) = link_url.clone() { data.region_ranges.push(prev_len..data.text.len()); @@ -214,7 +208,7 @@ pub fn render_markdown_mut( link_url: Some(link_url), background_kind: None, }); - style.underline = Some(Underline { + style.underline = Some(UnderlineStyle { thickness: 1.0.into(), ..Default::default() }); @@ -244,7 +238,7 @@ pub fn render_markdown_mut( data.highlights.push(( prev_len..data.text.len(), Highlight::Highlight(HighlightStyle { - underline: Some(Underline { + underline: Some(UnderlineStyle { thickness: 1.0.into(), ..Default::default() }), diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index a051468b00..5a8448372c 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -17,7 +17,7 @@ doctest = false [dependencies] anyhow.workspace = true -fs = { path = "../fs" } +fs = { package = "fs2", path = "../fs2" } gpui = { package = "gpui2", path = "../gpui2" } indexmap = "1.6.2" parking_lot.workspace = true @@ -32,5 +32,5 @@ util = { path = "../util" } [dev-dependencies] gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } +fs = { package = "fs2", path = "../fs2", features = ["test-support"] } settings = { package = "settings2", path = "../settings2", features = ["test-support"] } From d673efebd2fccfc3500f66ebf0c6887b85ca6b47 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 1 Nov 2023 11:53:00 +0200 Subject: [PATCH 145/156] Add prettier workspace resolution test --- Cargo.lock | 2 +- crates/prettier/src/prettier.rs | 396 +++++++++++++++++- crates/project/src/project_tests.rs | 4 +- crates/project/src/search.rs | 26 +- crates/search/Cargo.toml | 1 - crates/search/src/project_search.rs | 4 +- crates/semantic_index/src/db.rs | 4 +- crates/semantic_index/src/semantic_index.rs | 3 +- .../src/semantic_index_tests.rs | 4 +- crates/util/Cargo.toml | 1 + crates/util/src/paths.rs | 26 ++ 11 files changed, 426 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64bfe4edf8..17d19258e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7580,7 +7580,6 @@ dependencies = [ "collections", "editor", "futures 0.3.28", - "globset", "gpui", "language", "log", @@ -9887,6 +9886,7 @@ dependencies = [ "dirs 3.0.2", "futures 0.3.28", "git2", + "globset", "isahc", "lazy_static", "log", diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7517b4ee43..58de2fc913 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use collections::HashMap; +use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -11,7 +11,7 @@ use language::{Buffer, Diff}; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; -use util::paths::DEFAULT_PRETTIER_DIR; +use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR}; pub enum Prettier { Real(RealPrettier), @@ -63,14 +63,77 @@ impl Prettier { ".editorconfig", ]; + pub async fn locate_prettier_installation( + fs: &dyn Fs, + installed_prettiers: &HashSet, + locate_from: &Path, + ) -> anyhow::Result> { + let mut path_to_check = locate_from + .components() + .take_while(|component| !is_node_modules(component)) + .collect::(); + let mut project_path_with_prettier_dependency = None; + loop { + if installed_prettiers.contains(&path_to_check) { + return Ok(Some(path_to_check)); + } else if let Some(package_json_contents) = + read_package_json(fs, &path_to_check).await? + { + if has_prettier_in_package_json(&package_json_contents) { + if has_prettier_in_node_modules(fs, &path_to_check).await? { + return Ok(Some(path_to_check)); + } else if project_path_with_prettier_dependency.is_none() { + project_path_with_prettier_dependency = Some(path_to_check.clone()); + } + } else { + match package_json_contents.get("workspaces") { + Some(serde_json::Value::Array(workspaces)) => { + match &project_path_with_prettier_dependency { + Some(project_path_with_prettier_dependency) => { + let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix"); + if workspaces.iter().filter_map(|value| { + if let serde_json::Value::String(s) = value { + Some(s.clone()) + } else { + log::warn!("Skipping non-string 'workspaces' value: {value:?}"); + None + } + }).any(|workspace_definition| { + if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() { + path_matcher.is_match(subproject_path) + } else { + workspace_definition == subproject_path.to_string_lossy() + } + }) { + return Ok(Some(path_to_check)); + } else { + log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}"); + } + } + None => { + log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json"); + } + } + }, + Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."), + None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"), + } + } + } + + if !path_to_check.pop() { + match project_path_with_prettier_dependency { + Some(closest_prettier_discovered) => anyhow::bail!("No prettier found in ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}"), + None => return Ok(None), + } + } + } + } + pub async fn locate( starting_path: Option, fs: Arc, ) -> anyhow::Result { - fn is_node_modules(path_component: &std::path::Component<'_>) -> bool { - path_component.as_os_str().to_string_lossy() == "node_modules" - } - let paths_to_check = match starting_path.as_ref() { Some(starting_path) => { let worktree_root = starting_path @@ -106,7 +169,7 @@ impl Prettier { None => Vec::new(), }; - match find_closest_prettier_dir(paths_to_check, fs.as_ref()) + match find_closest_prettier_dir(fs.as_ref(), paths_to_check) .await .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { @@ -350,9 +413,62 @@ impl Prettier { } } -async fn find_closest_prettier_dir( - paths_to_check: Vec, +async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result { + let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); + if let Some(node_modules_location_metadata) = fs + .metadata(&possible_node_modules_location) + .await + .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? + { + return Ok(node_modules_location_metadata.is_dir); + } + Ok(false) +} + +async fn read_package_json( fs: &dyn Fs, + path: &Path, +) -> anyhow::Result>> { + let possible_package_json = path.join("package.json"); + if let Some(package_json_metadata) = fs + .metadata(&possible_package_json) + .await + .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? + { + if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { + let package_json_contents = fs + .load(&possible_package_json) + .await + .with_context(|| format!("reading {possible_package_json:?} file contents"))?; + return serde_json::from_str::>( + &package_json_contents, + ) + .map(Some) + .with_context(|| format!("parsing {possible_package_json:?} file contents")); + } + } + Ok(None) +} + +fn has_prettier_in_package_json( + package_json_contents: &HashMap, +) -> bool { + if let Some(serde_json::Value::Object(o)) = package_json_contents.get("dependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return true; + } + } + if let Some(serde_json::Value::Object(o)) = package_json_contents.get("devDependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return true; + } + } + false +} + +async fn find_closest_prettier_dir( + fs: &dyn Fs, + paths_to_check: Vec, ) -> anyhow::Result> { for path in paths_to_check { let possible_package_json = path.join("package.json"); @@ -436,3 +552,265 @@ impl lsp::request::Request for ClearCache { type Result = (); const METHOD: &'static str = "prettier/clear_cache"; } + +#[cfg(test)] +mod tests { + use fs::FakeFs; + use serde_json::json; + + use super::*; + + #[gpui::test] + async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + ".config": { + "zed": { + "settings.json": r#"{ "formatter": "auto" }"#, + }, + }, + "work": { + "project": { + "src": { + "index.js": "// index.js file contents", + }, + "node_modules": { + "expect": { + "build": { + "print.js": "// print.js file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.5.1" + } + }"#, + }, + "prettier": { + "index.js": "// Dummy prettier package file", + }, + }, + "package.json": r#"{}"# + }, + } + }), + ) + .await; + + assert!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/.config/zed/settings.json"), + ) + .await + .unwrap() + .is_none(), + "Should successfully find no prettier for path hierarchy without it" + ); + assert!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/project/src/index.js") + ) + .await + .unwrap() + .is_none(), + "Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it" + ); + assert!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/project/node_modules/expect/build/print.js") + ) + .await + .unwrap() + .is_none(), + "Even though it has package.json with prettier in it and no prettier on node_modules along the path, nothing should fail since declared inside node_modules" + ); + } + + #[gpui::test] + async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + "web_blog": { + "node_modules": { + "prettier": { + "index.js": "// Dummy prettier package file", + }, + "expect": { + "build": { + "print.js": "// print.js file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.5.1" + } + }"#, + }, + }, + "pages": { + "[slug].tsx": "// [slug].tsx file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.3.0" + }, + "prettier": { + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 + } + }"# + } + }), + ) + .await; + + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/web_blog/pages/[slug].tsx") + ) + .await + .unwrap(), + Some(PathBuf::from("/root/web_blog")), + "Should find a preinstalled prettier in the project root" + ); + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/web_blog/node_modules/expect/build/print.js") + ) + .await + .unwrap(), + Some(PathBuf::from("/root/web_blog")), + "Should find a preinstalled prettier in the project root even for node_modules files" + ); + } + + #[gpui::test] + async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + "work": { + "web_blog": { + "pages": { + "[slug].tsx": "// [slug].tsx file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.3.0" + }, + "prettier": { + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 + } + }"# + } + } + }), + ) + .await; + + let path = "/root/work/web_blog/node_modules/pages/[slug].tsx"; + match Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new(path) + ) + .await { + Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), + Err(e) => { + let message = e.to_string(); + assert!(message.contains(path), "Error message should mention which start file was used for location"); + assert!(message.contains("/root/work/web_blog"), "Error message should mention potential candidates without prettier node_modules contents"); + }, + }; + + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::from_iter( + [PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter() + ), + Path::new("/root/work/web_blog/node_modules/pages/[slug].tsx") + ) + .await + .unwrap(), + Some(PathBuf::from("/root/work")), + "Should return first cached value found without path checks" + ); + } + + #[gpui::test] + async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + "work": { + "full-stack-foundations": { + "exercises": { + "03.loading": { + "01.problem.loader": { + "app": { + "routes": { + "users+": { + "$username_+": { + "notes.tsx": "// notes.tsx file contents", + }, + }, + }, + }, + "node_modules": {}, + "package.json": r#"{ + "devDependencies": { + "prettier": "^3.0.3" + } + }"# + }, + }, + }, + "package.json": r#"{ + "workspaces": ["exercises/*/*", "examples/*"] + }"#, + "node_modules": { + "prettier": { + "index.js": "// Dummy prettier package file", + }, + }, + }, + } + }), + ) + .await; + + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"), + ).await.unwrap(), + Some(PathBuf::from("/root/work/full-stack-foundations")), + "Should ascend to the multi-workspace root and find the prettier there", + ); + } +} + +fn is_node_modules(path_component: &std::path::Component<'_>) -> bool { + path_component.as_os_str().to_string_lossy() == "node_modules" +} diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index d5ff1e08d7..32dc542c20 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,4 +1,4 @@ -use crate::{search::PathMatcher, worktree::WorktreeModelHandle, Event, *}; +use crate::{worktree::WorktreeModelHandle, Event, *}; use fs::{FakeFs, RealFs}; use futures::{future, StreamExt}; use gpui::{executor::Deterministic, test::subscribe, AppContext}; @@ -13,7 +13,7 @@ use pretty_assertions::assert_eq; use serde_json::json; use std::{cell::RefCell, os::unix, rc::Rc, task::Poll}; use unindent::Unindent as _; -use util::{assert_set_eq, test::temp_tree}; +use util::{assert_set_eq, test::temp_tree, paths::PathMatcher}; #[cfg(test)] #[ctor::ctor] diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index 46dd30c8a0..f626f15d12 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -13,6 +13,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use util::paths::PathMatcher; #[derive(Clone, Debug)] pub struct SearchInputs { @@ -52,31 +53,6 @@ pub enum SearchQuery { }, } -#[derive(Clone, Debug)] -pub struct PathMatcher { - maybe_path: PathBuf, - glob: GlobMatcher, -} - -impl std::fmt::Display for PathMatcher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.maybe_path.to_string_lossy().fmt(f) - } -} - -impl PathMatcher { - pub fn new(maybe_glob: &str) -> Result { - Ok(PathMatcher { - glob: Glob::new(&maybe_glob)?.compile_matcher(), - maybe_path: PathBuf::from(maybe_glob), - }) - } - - pub fn is_match>(&self, other: P) -> bool { - other.as_ref().starts_with(&self.maybe_path) || self.glob.is_match(other) - } -} - impl SearchQuery { pub fn text( query: impl ToString, diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 64421f5431..4ebd31a2bc 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -29,7 +29,6 @@ serde.workspace = true serde_derive.workspace = true smallvec.workspace = true smol.workspace = true -globset.workspace = true serde_json.workspace = true [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 55e3f6babd..f6e17bbee5 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -22,7 +22,7 @@ use gpui::{ }; use menu::Confirm; use project::{ - search::{PathMatcher, SearchInputs, SearchQuery}, + search::{SearchInputs, SearchQuery}, Entry, Project, }; use semantic_index::{SemanticIndex, SemanticIndexStatus}; @@ -37,7 +37,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use util::ResultExt as _; +use util::{paths::PathMatcher, ResultExt as _}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, searchable::{Direction, SearchableItem, SearchableItemHandle}, diff --git a/crates/semantic_index/src/db.rs b/crates/semantic_index/src/db.rs index 63527cea1c..5b416f7a64 100644 --- a/crates/semantic_index/src/db.rs +++ b/crates/semantic_index/src/db.rs @@ -9,7 +9,7 @@ use futures::channel::oneshot; use gpui::executor; use ndarray::{Array1, Array2}; use ordered_float::OrderedFloat; -use project::{search::PathMatcher, Fs}; +use project::Fs; use rpc::proto::Timestamp; use rusqlite::params; use rusqlite::types::Value; @@ -21,7 +21,7 @@ use std::{ sync::Arc, time::SystemTime, }; -use util::TryFutureExt; +use util::{paths::PathMatcher, TryFutureExt}; pub fn argsort(data: &[T]) -> Vec { let mut indices = (0..data.len()).collect::>(); diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 818faa0444..7d1eacd7fa 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -21,7 +21,7 @@ use ordered_float::OrderedFloat; use parking_lot::Mutex; use parsing::{CodeContextRetriever, Span, SpanDigest, PARSEABLE_ENTIRE_FILE_TYPES}; use postage::watch; -use project::{search::PathMatcher, Fs, PathChange, Project, ProjectEntryId, Worktree, WorktreeId}; +use project::{Fs, PathChange, Project, ProjectEntryId, Worktree, WorktreeId}; use smol::channel; use std::{ cmp::Reverse, @@ -33,6 +33,7 @@ use std::{ sync::{Arc, Weak}, time::{Duration, Instant, SystemTime}, }; +use util::paths::PathMatcher; use util::{channel::RELEASE_CHANNEL_NAME, http::HttpClient, paths::EMBEDDINGS_DIR, ResultExt}; use workspace::WorkspaceCreated; diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 7a91d1e100..044ded2682 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -10,13 +10,13 @@ use gpui::{executor::Deterministic, Task, TestAppContext}; use language::{Language, LanguageConfig, LanguageRegistry, ToOffset}; use parking_lot::Mutex; use pretty_assertions::assert_eq; -use project::{project_settings::ProjectSettings, search::PathMatcher, FakeFs, Fs, Project}; +use project::{project_settings::ProjectSettings, FakeFs, Fs, Project}; use rand::{rngs::StdRng, Rng}; use serde_json::json; use settings::SettingsStore; use std::{path::Path, sync::Arc, time::SystemTime}; use unindent::Unindent; -use util::RandomCharIter; +use util::{paths::PathMatcher, RandomCharIter}; #[ctor::ctor] fn init_logger() { diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 6ab76b0850..cfbd7551f9 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -14,6 +14,7 @@ test-support = ["tempdir", "git2"] [dependencies] anyhow.workspace = true backtrace = "0.3" +globset.workspace = true log.workspace = true lazy_static.workspace = true futures.workspace = true diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 96d77236a9..d54e0b1cd6 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use globset::{Glob, GlobMatcher}; use serde::{Deserialize, Serialize}; lazy_static::lazy_static! { @@ -189,6 +190,31 @@ impl

PathLikeWithPosition

{ } } +#[derive(Clone, Debug)] +pub struct PathMatcher { + maybe_path: PathBuf, + glob: GlobMatcher, +} + +impl std::fmt::Display for PathMatcher { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.maybe_path.to_string_lossy().fmt(f) + } +} + +impl PathMatcher { + pub fn new(maybe_glob: &str) -> Result { + Ok(PathMatcher { + glob: Glob::new(&maybe_glob)?.compile_matcher(), + maybe_path: PathBuf::from(maybe_glob), + }) + } + + pub fn is_match>(&self, other: P) -> bool { + other.as_ref().starts_with(&self.maybe_path) || self.glob.is_match(other) + } +} + #[cfg(test)] mod tests { use super::*; From 6bbb79a9f55beba80c3e6e776411cf3f3fc63a54 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 2 Nov 2023 21:21:41 +0200 Subject: [PATCH 146/156] Rework prettier installation and start --- crates/prettier/src/prettier.rs | 134 +------ crates/project/src/project.rs | 622 ++++++++++++++++++-------------- crates/project/src/search.rs | 3 +- 3 files changed, 366 insertions(+), 393 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 58de2fc913..3e846db66e 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,4 +1,3 @@ -use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -20,7 +19,6 @@ pub enum Prettier { } pub struct RealPrettier { - worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, @@ -28,17 +26,10 @@ pub struct RealPrettier { #[cfg(any(test, feature = "test-support"))] pub struct TestPrettier { - worktree_id: Option, prettier_dir: PathBuf, default: bool, } -#[derive(Debug)] -pub struct LocateStart { - pub worktree_root_path: Arc, - pub starting_path: Arc, -} - pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); const PRETTIER_PACKAGE_NAME: &str = "prettier"; @@ -130,75 +121,21 @@ impl Prettier { } } - pub async fn locate( - starting_path: Option, - fs: Arc, - ) -> anyhow::Result { - let paths_to_check = match starting_path.as_ref() { - Some(starting_path) => { - let worktree_root = starting_path - .worktree_root_path - .components() - .into_iter() - .take_while(|path_component| !is_node_modules(path_component)) - .collect::(); - if worktree_root != starting_path.worktree_root_path.as_ref() { - vec![worktree_root] - } else { - if starting_path.starting_path.as_ref() == Path::new("") { - worktree_root - .parent() - .map(|path| vec![path.to_path_buf()]) - .unwrap_or_default() - } else { - let file_to_format = starting_path.starting_path.as_ref(); - let mut paths_to_check = VecDeque::new(); - let mut current_path = worktree_root; - for path_component in file_to_format.components().into_iter() { - let new_path = current_path.join(path_component); - let old_path = std::mem::replace(&mut current_path, new_path); - paths_to_check.push_front(old_path); - if is_node_modules(&path_component) { - break; - } - } - Vec::from(paths_to_check) - } - } - } - None => Vec::new(), - }; - - match find_closest_prettier_dir(fs.as_ref(), paths_to_check) - .await - .with_context(|| format!("finding prettier starting with {starting_path:?}"))? - { - Some(prettier_dir) => Ok(prettier_dir), - None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()), - } - } - #[cfg(any(test, feature = "test-support"))] pub async fn start( - worktree_id: Option, _: LanguageServerId, prettier_dir: PathBuf, _: Arc, _: AsyncAppContext, ) -> anyhow::Result { - Ok( - #[cfg(any(test, feature = "test-support"))] - Self::Test(TestPrettier { - worktree_id, - default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), - prettier_dir, - }), - ) + Ok(Self::Test(TestPrettier { + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + })) } #[cfg(not(any(test, feature = "test-support")))] pub async fn start( - worktree_id: Option, server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, @@ -206,7 +143,7 @@ impl Prettier { ) -> anyhow::Result { use lsp::LanguageServerBinary; - let backgroud = cx.background(); + let background = cx.background(); anyhow::ensure!( prettier_dir.is_dir(), "Prettier dir {prettier_dir:?} is not a directory" @@ -217,7 +154,7 @@ impl Prettier { "no prettier server package found at {prettier_server:?}" ); - let node_path = backgroud + let node_path = background .spawn(async move { node.binary_path().await }) .await?; let server = LanguageServer::new( @@ -232,12 +169,11 @@ impl Prettier { cx, ) .context("prettier server creation")?; - let server = backgroud + let server = background .spawn(server.initialize(None)) .await .context("prettier server initialization")?; Ok(Self::Real(RealPrettier { - worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), prettier_dir, @@ -403,14 +339,6 @@ impl Prettier { Self::Test(test_prettier) => &test_prettier.prettier_dir, } } - - pub fn worktree_id(&self) -> Option { - match self { - Self::Real(local) => local.worktree_id, - #[cfg(any(test, feature = "test-support"))] - Self::Test(test_prettier) => test_prettier.worktree_id, - } - } } async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result { @@ -466,54 +394,6 @@ fn has_prettier_in_package_json( false } -async fn find_closest_prettier_dir( - fs: &dyn Fs, - paths_to_check: Vec, -) -> anyhow::Result> { - for path in paths_to_check { - let possible_package_json = path.join("package.json"); - if let Some(package_json_metadata) = fs - .metadata(&possible_package_json) - .await - .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? - { - if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { - let package_json_contents = fs - .load(&possible_package_json) - .await - .with_context(|| format!("reading {possible_package_json:?} file contents"))?; - if let Ok(json_contents) = serde_json::from_str::>( - &package_json_contents, - ) { - if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") { - if o.contains_key(PRETTIER_PACKAGE_NAME) { - return Ok(Some(path)); - } - } - if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies") - { - if o.contains_key(PRETTIER_PACKAGE_NAME) { - return Ok(Some(path)); - } - } - } - } - } - - let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); - if let Some(node_modules_location_metadata) = fs - .metadata(&possible_node_modules_location) - .await - .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? - { - if node_modules_location_metadata.is_dir { - return Ok(Some(path)); - } - } - } - Ok(None) -} - enum Format {} #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b38bcd1db2..a5c26bbb41 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -54,7 +54,7 @@ use lsp_command::*; use node_runtime::NodeRuntime; use parking_lot::Mutex; use postage::watch; -use prettier::{LocateStart, Prettier}; +use prettier::Prettier; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -82,8 +82,11 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, + http::HttpClient, + merge_json_value_into, + paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, + post_inc, ResultExt, TryFutureExt as _, }; pub use fs::*; @@ -162,17 +165,14 @@ pub struct Project { copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, node: Option>, - #[cfg(not(any(test, feature = "test-support")))] default_prettier: Option, - prettier_instances: HashMap< - (Option, PathBuf), - Shared, Arc>>>, - >, + prettiers_per_worktree: HashMap>>, + prettier_instances: HashMap, Arc>>>>, } -#[cfg(not(any(test, feature = "test-support")))] struct DefaultPrettier { - installation_process: Option>>, + instance: Option, Arc>>>>, + #[cfg(not(any(test, feature = "test-support")))] installed_plugins: HashSet<&'static str>, } @@ -685,8 +685,8 @@ impl Project { copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), node: Some(node), - #[cfg(not(any(test, feature = "test-support")))] default_prettier: None, + prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), } }) @@ -786,8 +786,8 @@ impl Project { copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), node: None, - #[cfg(not(any(test, feature = "test-support")))] default_prettier: None, + prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -2681,20 +2681,8 @@ impl Project { let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - let task_buffer = buffer.clone(); - let prettier_installation_task = - self.install_default_formatters(worktree, &new_language, &settings, cx); - cx.spawn(|project, mut cx| async move { - prettier_installation_task.await?; - let _ = project - .update(&mut cx, |project, cx| { - project.prettier_instance_for_buffer(&task_buffer, cx) - }) - .await; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - + self.install_default_formatters(worktree, &new_language, &settings, cx) + .detach_and_log_err(cx); if let Some(file) = buffer_file { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { @@ -4029,7 +4017,7 @@ impl Project { } pub fn format( - &self, + &mut self, buffers: HashSet>, push_to_history: bool, trigger: FormatTrigger, @@ -4049,10 +4037,10 @@ impl Project { }) .collect::>(); - cx.spawn(|this, mut cx| async move { + cx.spawn(|project, mut cx| async move { // Do not allow multiple concurrent formatting requests for the // same buffer. - this.update(&mut cx, |this, cx| { + project.update(&mut cx, |this, cx| { buffers_with_paths_and_servers.retain(|(buffer, _, _)| { this.buffers_being_formatted .insert(buffer.read(cx).remote_id()) @@ -4060,7 +4048,7 @@ impl Project { }); let _cleanup = defer({ - let this = this.clone(); + let this = project.clone(); let mut cx = cx.clone(); let buffers = &buffers_with_paths_and_servers; move || { @@ -4128,7 +4116,7 @@ impl Project { { format_operation = Some(FormatOperation::Lsp( Self::format_via_lsp( - &this, + &project, &buffer, buffer_abs_path, &language_server, @@ -4163,16 +4151,24 @@ impl Project { } } (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier_task) = this + if let Some((prettier_path, prettier_task)) = project .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) }).await { match prettier_task.await { Ok(prettier) => { - let buffer_path = buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) - }); + let buffer_file = buffer.update(&mut cx, |buffer, _| buffer.file().cloned()); + let buffer_path= { + File::from_dyn(buffer_file.as_ref()).map(|file| { + cx.update(|cx| { + let worktree_id = file.worktree_id(cx); + let file_abs_path = file.abs_path(cx); + project.update(cx, |project, _| project.prettiers_per_worktree.entry(worktree_id).or_default().insert(prettier_path)); + file_abs_path + }) + }) + }; format_operation = Some(FormatOperation::Prettier( prettier .format(buffer, buffer_path, &cx) @@ -4180,16 +4176,29 @@ impl Project { .context("formatting via prettier")?, )); } - Err(e) => anyhow::bail!( - "Failed to create prettier instance for buffer during autoformatting: {e:#}" - ), + Err(e) => { + project.update(&mut cx, |project, _| { + match &prettier_path { + Some(prettier_path) => { + project.prettier_instances.remove(prettier_path); + }, + None => { + if let Some(default_prettier) = project.default_prettier.as_mut() { + default_prettier.instance = None; + } + }, + } + }); + anyhow::bail!( + "Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}" + )}, } } else if let Some((language_server, buffer_abs_path)) = language_server.as_ref().zip(buffer_abs_path.as_ref()) { format_operation = Some(FormatOperation::Lsp( Self::format_via_lsp( - &this, + &project, &buffer, buffer_abs_path, &language_server, @@ -4202,16 +4211,24 @@ impl Project { } } (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier_task) = this + if let Some((prettier_path, prettier_task)) = project .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) }).await { match prettier_task.await { Ok(prettier) => { - let buffer_path = buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) - }); + let buffer_file = buffer.update(&mut cx, |buffer, _| buffer.file().cloned()); + let buffer_path= { + File::from_dyn(buffer_file.as_ref()).map(|file| { + cx.update(|cx| { + let worktree_id = file.worktree_id(cx); + let file_abs_path = file.abs_path(cx); + project.update(cx, |project, _| project.prettiers_per_worktree.entry(worktree_id).or_default().insert(prettier_path)); + file_abs_path + }) + }) + }; format_operation = Some(FormatOperation::Prettier( prettier .format(buffer, buffer_path, &cx) @@ -4219,9 +4236,22 @@ impl Project { .context("formatting via prettier")?, )); } - Err(e) => anyhow::bail!( - "Failed to create prettier instance for buffer during formatting: {e:#}" - ), + Err(e) => { + project.update(&mut cx, |project, _| { + match &prettier_path { + Some(prettier_path) => { + project.prettier_instances.remove(prettier_path); + }, + None => { + if let Some(default_prettier) = project.default_prettier.as_mut() { + default_prettier.instance = None; + } + }, + } + }); + anyhow::bail!( + "Failed to create prettier instance from {prettier_path:?} for buffer during formatting: {e:#}" + )}, } } } @@ -6431,15 +6461,25 @@ impl Project { "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); let prettiers_to_reload = self - .prettier_instances + .prettiers_per_worktree + .get(¤t_worktree_id) .iter() - .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { - Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) - } else { - None - } + .flat_map(|prettier_paths| prettier_paths.iter()) + .flatten() + .filter_map(|prettier_path| { + Some(( + current_worktree_id, + Some(prettier_path.clone()), + self.prettier_instances.get(prettier_path)?.clone(), + )) }) + .chain(self.default_prettier.iter().filter_map(|default_prettier| { + Some(( + current_worktree_id, + None, + default_prettier.instance.clone()?, + )) + })) .collect::>(); cx.background() @@ -6450,9 +6490,15 @@ impl Project { .clear_cache() .await .with_context(|| { - format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" - ) + match prettier_path { + Some(prettier_path) => format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" + ), + None => format!( + "clearing default prettier cache for worktree {worktree_id:?} on prettier settings update" + ), + } + }) .map_err(Arc::new) } @@ -8364,7 +8410,12 @@ impl Project { &mut self, buffer: &ModelHandle, cx: &mut ModelContext, - ) -> Task, Arc>>>>> { + ) -> Task< + Option<( + Option, + Shared, Arc>>>, + )>, + > { let buffer = buffer.read(cx); let buffer_file = buffer.file(); let Some(buffer_language) = buffer.language() else { @@ -8374,136 +8425,136 @@ impl Project { return Task::ready(None); } - let buffer_file = File::from_dyn(buffer_file); - let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); - let worktree_path = buffer_file - .as_ref() - .and_then(|file| Some(file.worktree.read(cx).abs_path())); - let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - if self.is_local() || worktree_id.is_none() || worktree_path.is_none() { + if self.is_local() { let Some(node) = self.node.as_ref().map(Arc::clone) else { return Task::ready(None); }; - cx.spawn(|this, mut cx| async move { - let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - let prettier_dir = match cx - .background() - .spawn(Prettier::locate( - worktree_path.zip(buffer_path).map( - |(worktree_root_path, starting_path)| LocateStart { - worktree_root_path, - starting_path, - }, - ), - fs, - )) - .await - { - Ok(path) => path, - Err(e) => { - return Some( - Task::ready(Err(Arc::new(e.context( - "determining prettier path for worktree {worktree_path:?}", - )))) - .shared(), - ); - } - }; - - if let Some(existing_prettier) = this.update(&mut cx, |project, _| { - project - .prettier_instances - .get(&(worktree_id, prettier_dir.clone())) - .cloned() - }) { - return Some(existing_prettier); - } - - log::info!("Found prettier in {prettier_dir:?}, starting."); - let task_prettier_dir = prettier_dir.clone(); - let weak_project = this.downgrade(); - let new_server_id = - this.update(&mut cx, |this, _| this.languages.next_language_server_id()); - let new_prettier_task = cx - .spawn(|mut cx| async move { - let prettier = Prettier::start( - worktree_id.map(|id| id.to_usize()), - new_server_id, - task_prettier_dir, - node, - cx.clone(), - ) - .await - .context("prettier start") - .map_err(Arc::new)?; - log::info!("Started prettier in {:?}", prettier.prettier_dir()); - - if let Some((project, prettier_server)) = - weak_project.upgrade(&mut cx).zip(prettier.server()) + match File::from_dyn(buffer_file).map(|file| file.abs_path(cx)) { + Some(buffer_path) => { + let fs = Arc::clone(&self.fs); + let installed_prettiers = self.prettier_instances.keys().cloned().collect(); + return cx.spawn(|project, mut cx| async move { + match cx + .background() + .spawn(async move { + Prettier::locate_prettier_installation( + fs.as_ref(), + &installed_prettiers, + &buffer_path, + ) + .await + }) + .await { - project.update(&mut cx, |project, cx| { - let name = if prettier.is_default() { - LanguageServerName(Arc::from("prettier (default)")) - } else { - let prettier_dir = prettier.prettier_dir(); - let worktree_path = prettier - .worktree_id() - .map(WorktreeId::from_usize) - .and_then(|id| project.worktree_for_id(id, cx)) - .map(|worktree| worktree.read(cx).abs_path()); - match worktree_path { - Some(worktree_path) => { - if worktree_path.as_ref() == prettier_dir { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - ))) - } else { - let dir_to_display = match prettier_dir - .strip_prefix(&worktree_path) - .ok() - { - Some(relative_path) => relative_path, - None => prettier_dir, - }; - LanguageServerName(Arc::from(format!( - "prettier ({})", - dir_to_display.display(), - ))) - } - } - None => LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir.display(), - ))), - } - }; + Ok(None) => { + let new_task = project.update(&mut cx, |project, cx| { + let new_task = spawn_default_prettier(node, cx); + project + .default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + #[cfg(not(any(test, feature = "test-support")))] + installed_plugins: HashSet::default(), + }) + .instance = Some(new_task.clone()); + new_task + }); + return Some((None, new_task)); + } + Err(e) => { + return Some(( + None, + Task::ready(Err(Arc::new(e.context( + "determining prettier path for worktree {worktree_path:?}", + )))) + .shared(), + )); + } + Ok(Some(prettier_dir)) => { + if let Some(existing_prettier) = + project.update(&mut cx, |project, _| { + project.prettier_instances.get(&prettier_dir).cloned() + }) + { + return Some((Some(prettier_dir), existing_prettier)); + } - project - .supplementary_language_servers - .insert(new_server_id, (name, Arc::clone(prettier_server))); - cx.emit(Event::LanguageServerAdded(new_server_id)); - }); + log::info!("Found prettier in {prettier_dir:?}, starting."); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = project.downgrade(); + let new_server_id = project.update(&mut cx, |this, _| { + this.languages.next_language_server_id() + }); + let new_prettier_task = cx + .spawn(|mut cx| async move { + let prettier = Prettier::start( + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; + log::info!( + "Started prettier in {:?}", + prettier.prettier_dir() + ); + + if let Some((project, prettier_server)) = + weak_project.upgrade(&mut cx).zip(prettier.server()) + { + project.update(&mut cx, |project, cx| { + let name = if prettier.is_default() { + LanguageServerName(Arc::from( + "prettier (default)", + )) + } else { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier.prettier_dir().display(), + ))) + }; + + project.supplementary_language_servers.insert( + new_server_id, + (name, Arc::clone(prettier_server)), + ); + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + Ok(Arc::new(prettier)).map_err(Arc::new) + }) + .shared(); + project.update(&mut cx, |project, _| { + project + .prettier_instances + .insert(prettier_dir.clone(), new_prettier_task.clone()); + }); + Some((Some(prettier_dir), new_prettier_task)) + } } - Ok(Arc::new(prettier)).map_err(Arc::new) - }) - .shared(); - this.update(&mut cx, |project, _| { - project - .prettier_instances - .insert((worktree_id, prettier_dir), new_prettier_task.clone()); - }); - Some(new_prettier_task) - }) + }); + } + None => { + let new_task = spawn_default_prettier(node, cx); + self.default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + #[cfg(not(any(test, feature = "test-support")))] + installed_plugins: HashSet::default(), + }) + .instance = Some(new_task.clone()); + return Task::ready(Some((None, new_task))); + } + } } else if self.remote_id().is_some() { return Task::ready(None); } else { - Task::ready(Some( + Task::ready(Some(( + None, Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), - )) + ))) } } @@ -8537,7 +8588,7 @@ impl Project { let mut prettier_plugins = None; if new_language.prettier_parser_name().is_some() { prettier_plugins - .get_or_insert_with(|| HashSet::default()) + .get_or_insert_with(|| HashSet::<&'static str>::default()) .extend( new_language .lsp_adapters() @@ -8549,27 +8600,25 @@ impl Project { return Task::ready(Ok(())); }; + let fs = Arc::clone(&self.fs); + let locate_prettier_installation = match worktree.and_then(|worktree_id| { + self.worktree_for_id(worktree_id, cx) + .map(|worktree| worktree.read(cx).abs_path()) + }) { + Some(locate_from) => { + let installed_prettiers = self.prettier_instances.keys().cloned().collect(); + cx.background().spawn(async move { + Prettier::locate_prettier_installation( + fs.as_ref(), + &installed_prettiers, + locate_from.as_ref(), + ) + .await + }) + } + None => Task::ready(Ok(None)), + }; let mut plugins_to_install = prettier_plugins; - let (mut install_success_tx, mut install_success_rx) = - futures::channel::mpsc::channel::>(1); - let new_installation_process = cx - .spawn(|this, mut cx| async move { - if let Some(installed_plugins) = install_success_rx.next().await { - this.update(&mut cx, |this, _| { - let default_prettier = - this.default_prettier - .get_or_insert_with(|| DefaultPrettier { - installation_process: None, - installed_plugins: HashSet::default(), - }); - if !installed_plugins.is_empty() { - log::info!("Installed new prettier plugins: {installed_plugins:?}"); - default_prettier.installed_plugins.extend(installed_plugins); - } - }) - } - }) - .shared(); let previous_installation_process = if let Some(default_prettier) = &mut self.default_prettier { plugins_to_install @@ -8577,83 +8626,128 @@ impl Project { if plugins_to_install.is_empty() { return Task::ready(Ok(())); } - std::mem::replace( - &mut default_prettier.installation_process, - Some(new_installation_process.clone()), - ) + default_prettier.instance.clone() } else { None }; - - let default_prettier_dir = util::paths::DEFAULT_PRETTIER_DIR.as_path(); - let already_running_prettier = self - .prettier_instances - .get(&(worktree, default_prettier_dir.to_path_buf())) - .cloned(); let fs = Arc::clone(&self.fs); cx.spawn(|this, mut cx| async move { - if let Some(previous_installation_process) = previous_installation_process { - previous_installation_process.await; - } - let mut everything_was_installed = false; - this.update(&mut cx, |this, _| { - match &mut this.default_prettier { - Some(default_prettier) => { - plugins_to_install - .retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); - everything_was_installed = plugins_to_install.is_empty(); - }, - None => this.default_prettier = Some(DefaultPrettier { installation_process: Some(new_installation_process), installed_plugins: HashSet::default() }), - } - }); - if everything_was_installed { - return Ok(()); - } - - cx.background() - .spawn(async move { - 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(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 - .iter() - .chain(Some(&"prettier")) - .map(|package_name| async { - let returned_package_name = package_name.to_string(); - let latest_version = node.npm_package_latest_version(package_name) - .await - .with_context(|| { - format!("fetching latest npm version for package {returned_package_name}") - })?; - anyhow::Ok((returned_package_name, latest_version)) - }), - ) - .await - .context("fetching latest npm versions")?; - - log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); - let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { - (package.as_str(), version.as_str()) - }).collect::>(); - node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; - let installed_packages = !plugins_to_install.is_empty(); - install_success_tx.try_send(plugins_to_install).ok(); - - if !installed_packages { - if let Some(prettier) = already_running_prettier { - prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?; + match locate_prettier_installation + .await + .context("locate prettier installation")? + { + Some(_non_default_prettier) => return Ok(()), + None => { + let mut needs_restart = match previous_installation_process { + Some(previous_installation_process) => { + previous_installation_process.await.is_err() } + None => true, + }; + this.update(&mut cx, |this, _| { + if let Some(default_prettier) = &mut this.default_prettier { + plugins_to_install.retain(|plugin| { + !default_prettier.installed_plugins.contains(plugin) + }); + needs_restart |= !plugins_to_install.is_empty(); + } + }); + if needs_restart { + let installed_plugins = plugins_to_install.clone(); + cx.background() + .spawn(async move { + install_default_prettier(plugins_to_install, node, fs).await + }) + .await + .context("prettier & plugins install")?; + this.update(&mut cx, |this, _| { + let default_prettier = + this.default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installed_plugins: HashSet::default(), + }); + default_prettier.instance = None; + default_prettier.installed_plugins.extend(installed_plugins); + }); } - - anyhow::Ok(()) - }).await + } + } + Ok(()) }) } } +fn spawn_default_prettier( + node: Arc, + cx: &mut ModelContext<'_, Project>, +) -> Shared, Arc>>> { + cx.spawn(|project, mut cx| async move { + let new_server_id = project.update(&mut cx, |project, _| { + project.languages.next_language_server_id() + }); + Prettier::start( + new_server_id, + DEFAULT_PRETTIER_DIR.clone(), + node, + cx.clone(), + ) + .await + .context("default prettier spawn") + .map(Arc::new) + .map_err(Arc::new) + }) + .shared() +} + +#[cfg(not(any(test, feature = "test-support")))] +async fn install_default_prettier( + plugins_to_install: HashSet<&'static str>, + node: Arc, + fs: Arc, +) -> anyhow::Result<()> { + 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(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.iter().chain(Some(&"prettier")).map( + |package_name| async { + let returned_package_name = package_name.to_string(); + let latest_version = node + .npm_package_latest_version(package_name) + .await + .with_context(|| { + format!("fetching latest npm version for package {returned_package_name}") + })?; + anyhow::Ok((returned_package_name, latest_version)) + }, + )) + .await + .context("fetching latest npm versions")?; + + log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); + let borrowed_packages = packages_to_versions + .iter() + .map(|(package, version)| (package.as_str(), version.as_str())) + .collect::>(); + node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages) + .await + .context("fetching formatter packages")?; + anyhow::Ok(()) +} + fn subscribe_for_copilot_events( copilot: &ModelHandle, cx: &mut ModelContext<'_, Project>, diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index f626f15d12..7e360e22ee 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -1,7 +1,6 @@ use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; use anyhow::{Context, Result}; use client::proto; -use globset::{Glob, GlobMatcher}; use itertools::Itertools; use language::{char_kind, BufferSnapshot}; use regex::{Regex, RegexBuilder}; @@ -10,7 +9,7 @@ use std::{ borrow::Cow, io::{BufRead, BufReader, Read}, ops::Range, - path::{Path, PathBuf}, + path::Path, sync::Arc, }; use util::paths::PathMatcher; From ff144def6343bb2776a120ee80d9bdbd8d7ebf40 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 2 Nov 2023 21:42:33 +0200 Subject: [PATCH 147/156] Fix the bugs --- crates/prettier/src/prettier.rs | 11 ++++- crates/project/src/project.rs | 72 ++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 3e846db66e..685a3ae7a5 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -63,6 +63,15 @@ impl Prettier { .components() .take_while(|component| !is_node_modules(component)) .collect::(); + let path_to_check_metadata = fs + .metadata(&path_to_check) + .await + .with_context(|| format!("failed to get metadata for initial path {path_to_check:?}"))? + .with_context(|| format!("empty metadata for initial path {path_to_check:?}"))?; + if !path_to_check_metadata.is_dir { + path_to_check.pop(); + } + let mut project_path_with_prettier_dependency = None; loop { if installed_prettiers.contains(&path_to_check) { @@ -361,7 +370,7 @@ async fn read_package_json( if let Some(package_json_metadata) = fs .metadata(&possible_package_json) .await - .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? + .with_context(|| format!("fetching metadata for package json {possible_package_json:?}"))? { if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { let package_json_contents = fs diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a5c26bbb41..b861291338 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8464,9 +8464,9 @@ impl Project { Err(e) => { return Some(( None, - Task::ready(Err(Arc::new(e.context( - "determining prettier path for worktree {worktree_path:?}", - )))) + Task::ready(Err(Arc::new( + e.context("determining prettier path"), + ))) .shared(), )); } @@ -8481,7 +8481,7 @@ impl Project { log::info!("Found prettier in {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); - let weak_project = project.downgrade(); + let task_project = project.clone(); let new_server_id = project.update(&mut cx, |this, _| { this.languages.next_language_server_id() }); @@ -8496,33 +8496,12 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; - log::info!( - "Started prettier in {:?}", - prettier.prettier_dir() + register_new_prettier( + &task_project, + &prettier, + new_server_id, + &mut cx, ); - - if let Some((project, prettier_server)) = - weak_project.upgrade(&mut cx).zip(prettier.server()) - { - project.update(&mut cx, |project, cx| { - let name = if prettier.is_default() { - LanguageServerName(Arc::from( - "prettier (default)", - )) - } else { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier.prettier_dir().display(), - ))) - }; - - project.supplementary_language_servers.insert( - new_server_id, - (name, Arc::clone(prettier_server)), - ); - cx.emit(Event::LanguageServerAdded(new_server_id)); - }); - } Ok(Arc::new(prettier)).map_err(Arc::new) }) .shared(); @@ -8678,6 +8657,31 @@ impl Project { } } +fn register_new_prettier( + project: &ModelHandle, + prettier: &Prettier, + new_server_id: LanguageServerId, + cx: &mut AsyncAppContext, +) { + log::info!("Started prettier in {:?}", prettier.prettier_dir()); + if let Some(prettier_server) = prettier.server() { + project.update(cx, |project, cx| { + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier.prettier_dir().display(), + ))) + }; + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier_server))); + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } +} + fn spawn_default_prettier( node: Arc, cx: &mut ModelContext<'_, Project>, @@ -8686,7 +8690,7 @@ fn spawn_default_prettier( let new_server_id = project.update(&mut cx, |project, _| { project.languages.next_language_server_id() }); - Prettier::start( + let new_prettier = Prettier::start( new_server_id, DEFAULT_PRETTIER_DIR.clone(), node, @@ -8695,7 +8699,11 @@ fn spawn_default_prettier( .await .context("default prettier spawn") .map(Arc::new) - .map_err(Arc::new) + .map_err(Arc::new)?; + + register_new_prettier(&project, &new_prettier, new_server_id, &mut cx); + + Ok(new_prettier) }) .shared() } From b75d8a60a84c737b822eef49d95a2dae19389a3e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 2 Nov 2023 21:50:47 +0200 Subject: [PATCH 148/156] Simplify --- crates/project/src/project.rs | 81 ++++++++++++----------------------- 1 file changed, 27 insertions(+), 54 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b861291338..215c8d15ee 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8448,7 +8448,8 @@ impl Project { { Ok(None) => { let new_task = project.update(&mut cx, |project, cx| { - let new_task = spawn_default_prettier(node, cx); + let new_task = + start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), cx); project .default_prettier .get_or_insert_with(|| DefaultPrettier { @@ -8480,35 +8481,13 @@ impl Project { } log::info!("Found prettier in {prettier_dir:?}, starting."); - let task_prettier_dir = prettier_dir.clone(); - let task_project = project.clone(); - let new_server_id = project.update(&mut cx, |this, _| { - this.languages.next_language_server_id() - }); - let new_prettier_task = cx - .spawn(|mut cx| async move { - let prettier = Prettier::start( - new_server_id, - task_prettier_dir, - node, - cx.clone(), - ) - .await - .context("prettier start") - .map_err(Arc::new)?; - register_new_prettier( - &task_project, - &prettier, - new_server_id, - &mut cx, - ); - Ok(Arc::new(prettier)).map_err(Arc::new) - }) - .shared(); - project.update(&mut cx, |project, _| { + let new_prettier_task = project.update(&mut cx, |project, cx| { + let new_prettier_task = + start_prettier(node, prettier_dir.clone(), cx); project .prettier_instances .insert(prettier_dir.clone(), new_prettier_task.clone()); + new_prettier_task }); Some((Some(prettier_dir), new_prettier_task)) } @@ -8516,7 +8495,7 @@ impl Project { }); } None => { - let new_task = spawn_default_prettier(node, cx); + let new_task = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), cx); self.default_prettier .get_or_insert_with(|| DefaultPrettier { instance: None, @@ -8657,6 +8636,26 @@ impl Project { } } +fn start_prettier( + node: Arc, + prettier_dir: PathBuf, + cx: &mut ModelContext<'_, Project>, +) -> Shared, Arc>>> { + cx.spawn(|project, mut cx| async move { + let new_server_id = project.update(&mut cx, |project, _| { + project.languages.next_language_server_id() + }); + let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone()) + .await + .context("default prettier spawn") + .map(Arc::new) + .map_err(Arc::new)?; + register_new_prettier(&project, &new_prettier, new_server_id, &mut cx); + Ok(new_prettier) + }) + .shared() +} + fn register_new_prettier( project: &ModelHandle, prettier: &Prettier, @@ -8682,32 +8681,6 @@ fn register_new_prettier( } } -fn spawn_default_prettier( - node: Arc, - cx: &mut ModelContext<'_, Project>, -) -> Shared, Arc>>> { - cx.spawn(|project, mut cx| async move { - let new_server_id = project.update(&mut cx, |project, _| { - project.languages.next_language_server_id() - }); - let new_prettier = Prettier::start( - new_server_id, - DEFAULT_PRETTIER_DIR.clone(), - node, - cx.clone(), - ) - .await - .context("default prettier spawn") - .map(Arc::new) - .map_err(Arc::new)?; - - register_new_prettier(&project, &new_prettier, new_server_id, &mut cx); - - Ok(new_prettier) - }) - .shared() -} - #[cfg(not(any(test, feature = "test-support")))] async fn install_default_prettier( plugins_to_install: HashSet<&'static str>, From 369b5140fb56b20ac972f18f6bbb062c6f6fc73a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 2 Nov 2023 22:12:19 +0200 Subject: [PATCH 149/156] Restore LSP names for prettier servers --- crates/project/src/project.rs | 119 +++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 45 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 215c8d15ee..dc009b7468 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4158,17 +4158,7 @@ impl Project { match prettier_task.await { Ok(prettier) => { - let buffer_file = buffer.update(&mut cx, |buffer, _| buffer.file().cloned()); - let buffer_path= { - File::from_dyn(buffer_file.as_ref()).map(|file| { - cx.update(|cx| { - let worktree_id = file.worktree_id(cx); - let file_abs_path = file.abs_path(cx); - project.update(cx, |project, _| project.prettiers_per_worktree.entry(worktree_id).or_default().insert(prettier_path)); - file_abs_path - }) - }) - }; + let buffer_path = buffer.update(&mut cx, |buffer, cx| File::from_dyn(buffer.file()).map(|f| f.abs_path(cx))); format_operation = Some(FormatOperation::Prettier( prettier .format(buffer, buffer_path, &cx) @@ -4218,17 +4208,7 @@ impl Project { match prettier_task.await { Ok(prettier) => { - let buffer_file = buffer.update(&mut cx, |buffer, _| buffer.file().cloned()); - let buffer_path= { - File::from_dyn(buffer_file.as_ref()).map(|file| { - cx.update(|cx| { - let worktree_id = file.worktree_id(cx); - let file_abs_path = file.abs_path(cx); - project.update(cx, |project, _| project.prettiers_per_worktree.entry(worktree_id).or_default().insert(prettier_path)); - file_abs_path - }) - }) - }; + let buffer_path = buffer.update(&mut cx, |buffer, cx| File::from_dyn(buffer.file()).map(|f| f.abs_path(cx))); format_operation = Some(FormatOperation::Prettier( prettier .format(buffer, buffer_path, &cx) @@ -8429,8 +8409,9 @@ impl Project { let Some(node) = self.node.as_ref().map(Arc::clone) else { return Task::ready(None); }; - match File::from_dyn(buffer_file).map(|file| file.abs_path(cx)) { - Some(buffer_path) => { + match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) + { + Some((worktree_id, buffer_path)) => { let fs = Arc::clone(&self.fs); let installed_prettiers = self.prettier_instances.keys().cloned().collect(); return cx.spawn(|project, mut cx| async move { @@ -8447,9 +8428,20 @@ impl Project { .await { Ok(None) => { + project.update(&mut cx, |project, _| { + project + .prettiers_per_worktree + .entry(worktree_id) + .or_default() + .insert(None) + }); let new_task = project.update(&mut cx, |project, cx| { - let new_task = - start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), cx); + let new_task = start_prettier( + node, + DEFAULT_PRETTIER_DIR.clone(), + Some(worktree_id), + cx, + ); project .default_prettier .get_or_insert_with(|| DefaultPrettier { @@ -8462,16 +8454,14 @@ impl Project { }); return Some((None, new_task)); } - Err(e) => { - return Some(( - None, - Task::ready(Err(Arc::new( - e.context("determining prettier path"), - ))) - .shared(), - )); - } Ok(Some(prettier_dir)) => { + project.update(&mut cx, |project, _| { + project + .prettiers_per_worktree + .entry(worktree_id) + .or_default() + .insert(Some(prettier_dir.clone())) + }); if let Some(existing_prettier) = project.update(&mut cx, |project, _| { project.prettier_instances.get(&prettier_dir).cloned() @@ -8482,8 +8472,12 @@ impl Project { log::info!("Found prettier in {prettier_dir:?}, starting."); let new_prettier_task = project.update(&mut cx, |project, cx| { - let new_prettier_task = - start_prettier(node, prettier_dir.clone(), cx); + let new_prettier_task = start_prettier( + node, + prettier_dir.clone(), + Some(worktree_id), + cx, + ); project .prettier_instances .insert(prettier_dir.clone(), new_prettier_task.clone()); @@ -8491,11 +8485,20 @@ impl Project { }); Some((Some(prettier_dir), new_prettier_task)) } + Err(e) => { + return Some(( + None, + Task::ready(Err(Arc::new( + e.context("determining prettier path"), + ))) + .shared(), + )); + } } }); } None => { - let new_task = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), cx); + let new_task = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), None, cx); self.default_prettier .get_or_insert_with(|| DefaultPrettier { instance: None, @@ -8639,6 +8642,7 @@ impl Project { fn start_prettier( node: Arc, prettier_dir: PathBuf, + worktree_id: Option, cx: &mut ModelContext<'_, Project>, ) -> Shared, Arc>>> { cx.spawn(|project, mut cx| async move { @@ -8650,7 +8654,7 @@ fn start_prettier( .context("default prettier spawn") .map(Arc::new) .map_err(Arc::new)?; - register_new_prettier(&project, &new_prettier, new_server_id, &mut cx); + register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx); Ok(new_prettier) }) .shared() @@ -8659,19 +8663,44 @@ fn start_prettier( fn register_new_prettier( project: &ModelHandle, prettier: &Prettier, + worktree_id: Option, new_server_id: LanguageServerId, cx: &mut AsyncAppContext, ) { - log::info!("Started prettier in {:?}", prettier.prettier_dir()); + let prettier_dir = prettier.prettier_dir(); + let is_default = prettier.is_default(); + if is_default { + log::info!("Started default prettier in {prettier_dir:?}"); + } else { + log::info!("Started prettier in {prettier_dir:?}"); + } if let Some(prettier_server) = prettier.server() { project.update(cx, |project, cx| { - let name = if prettier.is_default() { + let name = if is_default { LanguageServerName(Arc::from("prettier (default)")) } else { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier.prettier_dir().display(), - ))) + let worktree_path = worktree_id + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path())); + let name = match worktree_path { + Some(worktree_path) => { + if prettier_dir == worktree_path.as_ref() { + let name = prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + format!("prettier ({name})") + } else { + let dir_to_display = prettier_dir + .strip_prefix(worktree_path.as_ref()) + .ok() + .unwrap_or(prettier_dir); + format!("prettier ({})", dir_to_display.display()) + } + } + None => format!("prettier ({})", prettier_dir.display()), + }; + LanguageServerName(Arc::from(name)) }; project .supplementary_language_servers From cf95f9b08279709fafc88a187c17dfc1f7fefd36 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 2 Nov 2023 22:49:32 +0200 Subject: [PATCH 150/156] Make it more clear that missing prettier is to blame --- crates/prettier/src/prettier.rs | 68 ++++++++++++++++++++++++++++++++- crates/project/src/project.rs | 24 +++++++++--- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 685a3ae7a5..857bec3939 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -75,12 +75,14 @@ impl Prettier { let mut project_path_with_prettier_dependency = None; loop { if installed_prettiers.contains(&path_to_check) { + log::debug!("Found prettier path {path_to_check:?} in installed prettiers"); return Ok(Some(path_to_check)); } else if let Some(package_json_contents) = read_package_json(fs, &path_to_check).await? { if has_prettier_in_package_json(&package_json_contents) { if has_prettier_in_node_modules(fs, &path_to_check).await? { + log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules"); return Ok(Some(path_to_check)); } else if project_path_with_prettier_dependency.is_none() { project_path_with_prettier_dependency = Some(path_to_check.clone()); @@ -105,6 +107,8 @@ impl Prettier { workspace_definition == subproject_path.to_string_lossy() } }) { + anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules"); + log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}"); return Ok(Some(path_to_check)); } else { log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}"); @@ -123,8 +127,13 @@ impl Prettier { if !path_to_check.pop() { match project_path_with_prettier_dependency { - Some(closest_prettier_discovered) => anyhow::bail!("No prettier found in ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}"), - None => return Ok(None), + Some(closest_prettier_discovered) => { + anyhow::bail!("No prettier found in node_modules for ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}") + } + None => { + log::debug!("Found no prettier in ancestors of {locate_from:?}"); + return Ok(None); + } } } } @@ -698,6 +707,61 @@ mod tests { "Should ascend to the multi-workspace root and find the prettier there", ); } + + #[gpui::test] + async fn test_prettier_lookup_in_npm_workspaces_for_not_installed( + cx: &mut gpui::TestAppContext, + ) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + "work": { + "full-stack-foundations": { + "exercises": { + "03.loading": { + "01.problem.loader": { + "app": { + "routes": { + "users+": { + "$username_+": { + "notes.tsx": "// notes.tsx file contents", + }, + }, + }, + }, + "node_modules": {}, + "package.json": r#"{ + "devDependencies": { + "prettier": "^3.0.3" + } + }"# + }, + }, + }, + "package.json": r#"{ + "workspaces": ["exercises/*/*", "examples/*"] + }"#, + }, + } + }), + ) + .await; + + match Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx") + ) + .await { + Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), + Err(e) => { + let message = e.to_string(); + assert!(message.contains("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader"), "Error message should mention which project had prettier defined"); + assert!(message.contains("/root/work/full-stack-foundations"), "Error message should mention potential candidates without prettier node_modules contents"); + }, + }; + } } fn is_node_modules(path_component: &std::path::Component<'_>) -> bool { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index dc009b7468..147981c944 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4179,9 +4179,15 @@ impl Project { }, } }); - anyhow::bail!( - "Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}" - )}, + match &prettier_path { + Some(prettier_path) => { + log::error!("Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}"); + }, + None => { + log::error!("Failed to create default prettier instance for buffer during autoformatting: {e:#}"); + }, + } + } } } else if let Some((language_server, buffer_abs_path)) = language_server.as_ref().zip(buffer_abs_path.as_ref()) @@ -4229,9 +4235,15 @@ impl Project { }, } }); - anyhow::bail!( - "Failed to create prettier instance from {prettier_path:?} for buffer during formatting: {e:#}" - )}, + match &prettier_path { + Some(prettier_path) => { + log::error!("Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}"); + }, + None => { + log::error!("Failed to create default prettier instance for buffer during autoformatting: {e:#}"); + }, + } + } } } } From 244c6939681cd347f9eb35bae3c14ca8f04bf014 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 2 Nov 2023 23:01:05 +0200 Subject: [PATCH 151/156] Reuse already running default prettiers --- crates/project/src/project.rs | 93 ++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 147981c944..7623f84c6d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8440,31 +8440,43 @@ impl Project { .await { Ok(None) => { - project.update(&mut cx, |project, _| { - project - .prettiers_per_worktree - .entry(worktree_id) - .or_default() - .insert(None) - }); - let new_task = project.update(&mut cx, |project, cx| { - let new_task = start_prettier( - node, - DEFAULT_PRETTIER_DIR.clone(), - Some(worktree_id), - cx, - ); - project - .default_prettier - .get_or_insert_with(|| DefaultPrettier { - instance: None, - #[cfg(not(any(test, feature = "test-support")))] - installed_plugins: HashSet::default(), - }) - .instance = Some(new_task.clone()); - new_task - }); - return Some((None, new_task)); + let started_default_prettier = + project.update(&mut cx, |project, _| { + project + .prettiers_per_worktree + .entry(worktree_id) + .or_default() + .insert(None); + project.default_prettier.as_ref().and_then( + |default_prettier| default_prettier.instance.clone(), + ) + }); + match started_default_prettier { + Some(old_task) => return Some((None, old_task)), + None => { + let new_task = project.update(&mut cx, |project, cx| { + let new_task = start_prettier( + node, + DEFAULT_PRETTIER_DIR.clone(), + Some(worktree_id), + cx, + ); + project + .default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + #[cfg(not(any( + test, + feature = "test-support" + )))] + installed_plugins: HashSet::default(), + }) + .instance = Some(new_task.clone()); + new_task + }); + return Some((None, new_task)); + } + } } Ok(Some(prettier_dir)) => { project.update(&mut cx, |project, _| { @@ -8479,6 +8491,9 @@ impl Project { project.prettier_instances.get(&prettier_dir).cloned() }) { + log::debug!( + "Found already started prettier in {prettier_dir:?}" + ); return Some((Some(prettier_dir), existing_prettier)); } @@ -8510,15 +8525,25 @@ impl Project { }); } None => { - let new_task = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), None, cx); - self.default_prettier - .get_or_insert_with(|| DefaultPrettier { - instance: None, - #[cfg(not(any(test, feature = "test-support")))] - installed_plugins: HashSet::default(), - }) - .instance = Some(new_task.clone()); - return Task::ready(Some((None, new_task))); + let started_default_prettier = self + .default_prettier + .as_ref() + .and_then(|default_prettier| default_prettier.instance.clone()); + match started_default_prettier { + Some(old_task) => return Task::ready(Some((None, old_task))), + None => { + let new_task = + start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), None, cx); + self.default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + #[cfg(not(any(test, feature = "test-support")))] + installed_plugins: HashSet::default(), + }) + .instance = Some(new_task.clone()); + return Task::ready(Some((None, new_task))); + } + } } } } else if self.remote_id().is_some() { From 24dd1c581290f3f7df572e79e11ad2d597cc2dd1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 3 Nov 2023 00:38:18 +0200 Subject: [PATCH 152/156] Properly order default prettier installations and startups --- crates/project/src/project.rs | 205 +++++++++++++++++----------- crates/project/src/project_tests.rs | 2 +- 2 files changed, 123 insertions(+), 84 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7623f84c6d..919b563cfb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -172,6 +172,7 @@ pub struct Project { struct DefaultPrettier { instance: Option, Arc>>>>, + installation_process: Option>>>>, #[cfg(not(any(test, feature = "test-support")))] installed_plugins: HashSet<&'static str>, } @@ -924,8 +925,7 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.install_default_formatters(worktree, &language, &settings, cx) - .detach_and_log_err(cx); + self.install_default_formatters(worktree, &language, &settings, cx); } // Start all the newly-enabled language servers. @@ -2681,8 +2681,7 @@ impl Project { let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - self.install_default_formatters(worktree, &new_language, &settings, cx) - .detach_and_log_err(cx); + self.install_default_formatters(worktree, &new_language, &settings, cx); if let Some(file) = buffer_file { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { @@ -8454,27 +8453,12 @@ impl Project { match started_default_prettier { Some(old_task) => return Some((None, old_task)), None => { - let new_task = project.update(&mut cx, |project, cx| { - let new_task = start_prettier( - node, - DEFAULT_PRETTIER_DIR.clone(), - Some(worktree_id), - cx, - ); - project - .default_prettier - .get_or_insert_with(|| DefaultPrettier { - instance: None, - #[cfg(not(any( - test, - feature = "test-support" - )))] - installed_plugins: HashSet::default(), - }) - .instance = Some(new_task.clone()); - new_task - }); - return Some((None, new_task)); + let new_default_prettier = project + .update(&mut cx, |_, cx| { + start_default_prettier(node, Some(worktree_id), cx) + }) + .await; + return Some((None, new_default_prettier)); } } } @@ -8532,16 +8516,8 @@ impl Project { match started_default_prettier { Some(old_task) => return Task::ready(Some((None, old_task))), None => { - let new_task = - start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), None, cx); - self.default_prettier - .get_or_insert_with(|| DefaultPrettier { - instance: None, - #[cfg(not(any(test, feature = "test-support")))] - installed_plugins: HashSet::default(), - }) - .instance = Some(new_task.clone()); - return Task::ready(Some((None, new_task))); + let new_task = start_default_prettier(node, None, cx); + return cx.spawn(|_, _| async move { Some((None, new_task.await)) }); } } } @@ -8563,8 +8539,7 @@ impl Project { _new_language: &Language, _language_settings: &LanguageSettings, _cx: &mut ModelContext, - ) -> Task> { - return Task::ready(Ok(())); + ) { } #[cfg(not(any(test, feature = "test-support")))] @@ -8574,13 +8549,13 @@ impl Project { new_language: &Language, language_settings: &LanguageSettings, cx: &mut ModelContext, - ) -> Task> { + ) { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => {} - Formatter::LanguageServer | Formatter::External { .. } => return Task::ready(Ok(())), + Formatter::LanguageServer | Formatter::External { .. } => return, }; let Some(node) = self.node.as_ref().cloned() else { - return Task::ready(Ok(())); + return; }; let mut prettier_plugins = None; @@ -8595,7 +8570,7 @@ impl Project { ) } let Some(prettier_plugins) = prettier_plugins else { - return Task::ready(Ok(())); + return; }; let fs = Arc::clone(&self.fs); @@ -8622,60 +8597,124 @@ impl Project { plugins_to_install .retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); if plugins_to_install.is_empty() { - return Task::ready(Ok(())); + return; } - default_prettier.instance.clone() + default_prettier.installation_process.clone() } else { None }; let fs = Arc::clone(&self.fs); - cx.spawn(|this, mut cx| async move { - match locate_prettier_installation - .await - .context("locate prettier installation")? - { - Some(_non_default_prettier) => return Ok(()), - None => { - let mut needs_restart = match previous_installation_process { - Some(previous_installation_process) => { - previous_installation_process.await.is_err() - } - None => true, - }; - this.update(&mut cx, |this, _| { - if let Some(default_prettier) = &mut this.default_prettier { - plugins_to_install.retain(|plugin| { - !default_prettier.installed_plugins.contains(plugin) - }); - needs_restart |= !plugins_to_install.is_empty(); - } - }); - if needs_restart { - let installed_plugins = plugins_to_install.clone(); - cx.background() - .spawn(async move { - install_default_prettier(plugins_to_install, node, fs).await - }) - .await - .context("prettier & plugins install")?; + let default_prettier = self + .default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installation_process: None, + installed_plugins: HashSet::default(), + }); + default_prettier.installation_process = Some( + cx.spawn(|this, mut cx| async move { + match locate_prettier_installation + .await + .context("locate prettier installation") + .map_err(Arc::new)? + { + Some(_non_default_prettier) => return Ok(()), + None => { + let mut needs_install = match previous_installation_process { + Some(previous_installation_process) => { + previous_installation_process.await.is_err() + } + None => true, + }; this.update(&mut cx, |this, _| { - let default_prettier = - this.default_prettier - .get_or_insert_with(|| DefaultPrettier { - instance: None, - installed_plugins: HashSet::default(), - }); - default_prettier.instance = None; - default_prettier.installed_plugins.extend(installed_plugins); + if let Some(default_prettier) = &mut this.default_prettier { + plugins_to_install.retain(|plugin| { + !default_prettier.installed_plugins.contains(plugin) + }); + needs_install |= !plugins_to_install.is_empty(); + } }); + if needs_install { + let installed_plugins = plugins_to_install.clone(); + cx.background() + .spawn(async move { + install_default_prettier(plugins_to_install, node, fs).await + }) + .await + .context("prettier & plugins install") + .map_err(Arc::new)?; + this.update(&mut cx, |this, _| { + let default_prettier = + this.default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installation_process: Some( + Task::ready(Ok(())).shared(), + ), + installed_plugins: HashSet::default(), + }); + default_prettier.instance = None; + default_prettier.installed_plugins.extend(installed_plugins); + }); + } } } - } - Ok(()) - }) + Ok(()) + }) + .shared(), + ); } } +fn start_default_prettier( + node: Arc, + worktree_id: Option, + cx: &mut ModelContext<'_, Project>, +) -> Task, Arc>>>> { + cx.spawn(|project, mut cx| async move { + loop { + let default_prettier_installing = project.update(&mut cx, |project, _| { + project + .default_prettier + .as_ref() + .and_then(|default_prettier| default_prettier.installation_process.clone()) + }); + match default_prettier_installing { + Some(installation_task) => { + if installation_task.await.is_ok() { + break; + } + } + None => break, + } + } + + project.update(&mut cx, |project, cx| { + match project + .default_prettier + .as_mut() + .and_then(|default_prettier| default_prettier.instance.as_mut()) + { + Some(default_prettier) => default_prettier.clone(), + None => { + let new_default_prettier = + start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx); + project + .default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installation_process: None, + #[cfg(not(any(test, feature = "test-support")))] + installed_plugins: HashSet::default(), + }) + .instance = Some(new_default_prettier.clone()); + new_default_prettier + } + } + }) + }) +} + fn start_prettier( node: Arc, prettier_dir: PathBuf, diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 32dc542c20..90d32643d5 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -13,7 +13,7 @@ use pretty_assertions::assert_eq; use serde_json::json; use std::{cell::RefCell, os::unix, rc::Rc, task::Poll}; use unindent::Unindent as _; -use util::{assert_set_eq, test::temp_tree, paths::PathMatcher}; +use util::{assert_set_eq, paths::PathMatcher, test::temp_tree}; #[cfg(test)] #[ctor::ctor] From 09346fb9f1eb4391390dbbf726bccc4109deb1bc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 3 Nov 2023 11:02:38 +0200 Subject: [PATCH 153/156] Port changes to zed2 --- crates/prettier/src/prettier.rs | 6 +- crates/prettier/src/prettier_server.js | 98 +-- crates/prettier2/src/prettier2.rs | 555 +++++++++++++---- crates/prettier2/src/prettier_server.js | 99 +-- crates/project/src/project.rs | 8 +- crates/project2/src/project2.rs | 761 +++++++++++++++--------- crates/project2/src/project_tests.rs | 4 +- crates/project2/src/search.rs | 29 +- 8 files changed, 1059 insertions(+), 501 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 857bec3939..06c1b66977 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -61,7 +61,7 @@ impl Prettier { ) -> anyhow::Result> { let mut path_to_check = locate_from .components() - .take_while(|component| !is_node_modules(component)) + .take_while(|component| component.as_os_str().to_string_lossy() != "node_modules") .collect::(); let path_to_check_metadata = fs .metadata(&path_to_check) @@ -763,7 +763,3 @@ mod tests { }; } } - -fn is_node_modules(path_component: &std::path::Component<'_>) -> bool { - path_component.as_os_str().to_string_lossy() == "node_modules" -} diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 9967aec50f..191431da0b 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -1,11 +1,13 @@ -const { Buffer } = require('buffer'); +const { Buffer } = require("buffer"); const fs = require("fs"); const path = require("path"); -const { once } = require('events'); +const { once } = require("events"); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`); + process.stderr.write( + `Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`, + ); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { @@ -19,7 +21,7 @@ fs.stat(prettierContainerPath, (err, stats) => { process.exit(1); } }); -const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +const prettierPath = path.join(prettierContainerPath, "node_modules/prettier"); class Prettier { constructor(path, prettier, config) { @@ -34,7 +36,7 @@ class Prettier { let config; try { prettier = await loadPrettier(prettierPath); - config = await prettier.resolveConfig(prettierPath) || {}; + config = (await prettier.resolveConfig(prettierPath)) || {}; } catch (e) { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); @@ -42,7 +44,7 @@ class Prettier { process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`); process.stdin.resume(); handleBuffer(new Prettier(prettierPath, prettier, config)); -})() +})(); async function handleBuffer(prettier) { for await (const messageText of readStdin()) { @@ -54,25 +56,29 @@ async function handleBuffer(prettier) { continue; } // allow concurrent request handling by not `await`ing the message handling promise (async function) - handleMessage(message, prettier).catch(e => { + handleMessage(message, prettier).catch((e) => { const errorMessage = message; if ((errorMessage.params || {}).text !== undefined) { errorMessage.params.text = "..snip.."; } - sendResponse({ id: message.id, ...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`) }); }); + sendResponse({ + id: message.id, + ...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`), + }); + }); } } const headerSeparator = "\r\n"; -const contentLengthHeaderName = 'Content-Length'; +const contentLengthHeaderName = "Content-Length"; async function* readStdin() { let buffer = Buffer.alloc(0); let streamEnded = false; - process.stdin.on('end', () => { + process.stdin.on("end", () => { streamEnded = true; }); - process.stdin.on('data', (data) => { + process.stdin.on("data", (data) => { buffer = Buffer.concat([buffer, data]); }); @@ -80,7 +86,7 @@ async function* readStdin() { sendResponse(makeError(errorMessage)); buffer = Buffer.alloc(0); messageLength = null; - await once(process.stdin, 'readable'); + await once(process.stdin, "readable"); streamEnded = false; } @@ -91,20 +97,25 @@ async function* readStdin() { if (messageLength === null) { while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) { if (streamEnded) { - await handleStreamEnded('Unexpected end of stream: headers not found'); + await handleStreamEnded("Unexpected end of stream: headers not found"); continue main_loop; } else if (buffer.length > contentLengthHeaderName.length * 10) { - await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`); + await handleStreamEnded( + `Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`, + ); continue main_loop; } - await once(process.stdin, 'readable'); + await once(process.stdin, "readable"); } - const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); - const contentLengthHeader = headers.split(headerSeparator) - .map(header => header.split(':')) - .filter(header => header[2] === undefined) - .filter(header => (header[1] || '').length > 0) - .find(header => (header[0] || '').trim() === contentLengthHeaderName); + const headers = buffer + .subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)) + .toString("ascii"); + const contentLengthHeader = headers + .split(headerSeparator) + .map((header) => header.split(":")) + .filter((header) => header[2] === undefined) + .filter((header) => (header[1] || "").length > 0) + .find((header) => (header[0] || "").trim() === contentLengthHeaderName); const contentLength = (contentLengthHeader || [])[1]; if (contentLength === undefined) { await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); @@ -114,13 +125,14 @@ async function* readStdin() { messageLength = parseInt(contentLength, 10); } - while (buffer.length < (headersLength + messageLength)) { + while (buffer.length < headersLength + messageLength) { if (streamEnded) { await handleStreamEnded( - `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`); + `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`, + ); continue main_loop; } - await once(process.stdin, 'readable'); + await once(process.stdin, "readable"); } const messageEnd = headersLength + messageLength; @@ -128,12 +140,12 @@ async function* readStdin() { buffer = buffer.subarray(messageEnd); headersLength = null; messageLength = null; - yield message.toString('utf8'); + yield message.toString("utf8"); } } catch (e) { sendResponse(makeError(`Error reading stdin: ${e}`)); } finally { - process.stdin.off('data', () => { }); + process.stdin.off("data", () => {}); } } @@ -146,7 +158,7 @@ async function handleMessage(message, prettier) { throw new Error(`Message id is undefined: ${JSON.stringify(message)}`); } - if (method === 'prettier/format') { + if (method === "prettier/format") { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`); } @@ -156,7 +168,7 @@ async function handleMessage(message, prettier) { let resolvedConfig = {}; if (params.options.filepath !== undefined) { - resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {}; + resolvedConfig = (await prettier.prettier.resolveConfig(params.options.filepath)) || {}; } const options = { @@ -164,21 +176,25 @@ async function handleMessage(message, prettier) { ...resolvedConfig, parser: params.options.parser, plugins: params.options.plugins, - path: params.options.filepath + path: params.options.filepath, }; - process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`); + process.stderr.write( + `Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${ + params.options.filepath || "" + }' with options: ${JSON.stringify(options)}\n`, + ); const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); - } else if (method === 'prettier/clear_cache') { + } else if (method === "prettier/clear_cache") { prettier.prettier.clearConfigCache(); - prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {}; + prettier.config = (await prettier.prettier.resolveConfig(prettier.path)) || {}; sendResponse({ id, result: null }); - } else if (method === 'initialize') { + } else if (method === "initialize") { sendResponse({ - id: id || 0, + id, result: { - "capabilities": {} - } + capabilities: {}, + }, }); } else { throw new Error(`Unknown method: ${method}`); @@ -188,18 +204,20 @@ async function handleMessage(message, prettier) { function makeError(message) { return { error: { - "code": -32600, // invalid request code + code: -32600, // invalid request code message, - } + }, }; } function sendResponse(response) { const responsePayloadString = JSON.stringify({ jsonrpc: "2.0", - ...response + ...response, }); - const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + const headers = `${contentLengthHeaderName}: ${Buffer.byteLength( + responsePayloadString, + )}${headerSeparator}${headerSeparator}`; process.stdout.write(headers + responsePayloadString); } diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index d9b6f9eab7..44151774ae 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/crates/prettier2/src/prettier2.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use collections::HashMap; +use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, Model}; use language::{language_settings::language_settings, Buffer, Diff}; @@ -7,11 +7,10 @@ use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use std::{ - collections::VecDeque, path::{Path, PathBuf}, sync::Arc, }; -use util::paths::DEFAULT_PRETTIER_DIR; +use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR}; pub enum Prettier { Real(RealPrettier), @@ -20,7 +19,6 @@ pub enum Prettier { } pub struct RealPrettier { - worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, @@ -28,17 +26,10 @@ pub struct RealPrettier { #[cfg(any(test, feature = "test-support"))] pub struct TestPrettier { - worktree_id: Option, prettier_dir: PathBuf, default: bool, } -#[derive(Debug)] -pub struct LocateStart { - pub worktree_root_path: Arc, - pub starting_path: Arc, -} - pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); const PRETTIER_PACKAGE_NAME: &str = "prettier"; @@ -63,79 +54,106 @@ impl Prettier { ".editorconfig", ]; - pub async fn locate( - starting_path: Option, - fs: Arc, - ) -> anyhow::Result { - fn is_node_modules(path_component: &std::path::Component<'_>) -> bool { - path_component.as_os_str().to_string_lossy() == "node_modules" + pub async fn locate_prettier_installation( + fs: &dyn Fs, + installed_prettiers: &HashSet, + locate_from: &Path, + ) -> anyhow::Result> { + let mut path_to_check = locate_from + .components() + .take_while(|component| component.as_os_str().to_string_lossy() != "node_modules") + .collect::(); + let path_to_check_metadata = fs + .metadata(&path_to_check) + .await + .with_context(|| format!("failed to get metadata for initial path {path_to_check:?}"))? + .with_context(|| format!("empty metadata for initial path {path_to_check:?}"))?; + if !path_to_check_metadata.is_dir { + path_to_check.pop(); } - let paths_to_check = match starting_path.as_ref() { - Some(starting_path) => { - let worktree_root = starting_path - .worktree_root_path - .components() - .into_iter() - .take_while(|path_component| !is_node_modules(path_component)) - .collect::(); - if worktree_root != starting_path.worktree_root_path.as_ref() { - vec![worktree_root] + let mut project_path_with_prettier_dependency = None; + loop { + if installed_prettiers.contains(&path_to_check) { + log::debug!("Found prettier path {path_to_check:?} in installed prettiers"); + return Ok(Some(path_to_check)); + } else if let Some(package_json_contents) = + read_package_json(fs, &path_to_check).await? + { + if has_prettier_in_package_json(&package_json_contents) { + if has_prettier_in_node_modules(fs, &path_to_check).await? { + log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules"); + return Ok(Some(path_to_check)); + } else if project_path_with_prettier_dependency.is_none() { + project_path_with_prettier_dependency = Some(path_to_check.clone()); + } } else { - if starting_path.starting_path.as_ref() == Path::new("") { - worktree_root - .parent() - .map(|path| vec![path.to_path_buf()]) - .unwrap_or_default() - } else { - let file_to_format = starting_path.starting_path.as_ref(); - let mut paths_to_check = VecDeque::new(); - let mut current_path = worktree_root; - for path_component in file_to_format.components().into_iter() { - let new_path = current_path.join(path_component); - let old_path = std::mem::replace(&mut current_path, new_path); - paths_to_check.push_front(old_path); - if is_node_modules(&path_component) { - break; - } + match package_json_contents.get("workspaces") { + Some(serde_json::Value::Array(workspaces)) => { + match &project_path_with_prettier_dependency { + Some(project_path_with_prettier_dependency) => { + let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix"); + if workspaces.iter().filter_map(|value| { + if let serde_json::Value::String(s) = value { + Some(s.clone()) + } else { + log::warn!("Skipping non-string 'workspaces' value: {value:?}"); + None + } + }).any(|workspace_definition| { + if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() { + path_matcher.is_match(subproject_path) + } else { + workspace_definition == subproject_path.to_string_lossy() + } + }) { + anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules"); + log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}"); + return Ok(Some(path_to_check)); + } else { + log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}"); + } + } + None => { + log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json"); + } + } + }, + Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."), + None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"), } - Vec::from(paths_to_check) + } + } + + if !path_to_check.pop() { + match project_path_with_prettier_dependency { + Some(closest_prettier_discovered) => { + anyhow::bail!("No prettier found in node_modules for ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}") + } + None => { + log::debug!("Found no prettier in ancestors of {locate_from:?}"); + return Ok(None); } } } - None => Vec::new(), - }; - - match find_closest_prettier_dir(paths_to_check, fs.as_ref()) - .await - .with_context(|| format!("finding prettier starting with {starting_path:?}"))? - { - Some(prettier_dir) => Ok(prettier_dir), - None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()), } } #[cfg(any(test, feature = "test-support"))] pub async fn start( - worktree_id: Option, _: LanguageServerId, prettier_dir: PathBuf, _: Arc, _: AsyncAppContext, ) -> anyhow::Result { - Ok( - #[cfg(any(test, feature = "test-support"))] - Self::Test(TestPrettier { - worktree_id, - default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), - prettier_dir, - }), - ) + Ok(Self::Test(TestPrettier { + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + })) } #[cfg(not(any(test, feature = "test-support")))] pub async fn start( - worktree_id: Option, server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, @@ -174,7 +192,6 @@ impl Prettier { .await .context("prettier server initialization")?; Ok(Self::Real(RealPrettier { - worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), prettier_dir, @@ -370,64 +387,61 @@ impl Prettier { Self::Test(test_prettier) => &test_prettier.prettier_dir, } } - - pub fn worktree_id(&self) -> Option { - match self { - Self::Real(local) => local.worktree_id, - #[cfg(any(test, feature = "test-support"))] - Self::Test(test_prettier) => test_prettier.worktree_id, - } - } } -async fn find_closest_prettier_dir( - paths_to_check: Vec, - fs: &dyn Fs, -) -> anyhow::Result> { - for path in paths_to_check { - let possible_package_json = path.join("package.json"); - if let Some(package_json_metadata) = fs - .metadata(&possible_package_json) - .await - .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? - { - if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { - let package_json_contents = fs - .load(&possible_package_json) - .await - .with_context(|| format!("reading {possible_package_json:?} file contents"))?; - if let Ok(json_contents) = serde_json::from_str::>( - &package_json_contents, - ) { - if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") { - if o.contains_key(PRETTIER_PACKAGE_NAME) { - return Ok(Some(path)); - } - } - if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies") - { - if o.contains_key(PRETTIER_PACKAGE_NAME) { - return Ok(Some(path)); - } - } - } - } - } +async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result { + let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); + if let Some(node_modules_location_metadata) = fs + .metadata(&possible_node_modules_location) + .await + .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? + { + return Ok(node_modules_location_metadata.is_dir); + } + Ok(false) +} - let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); - if let Some(node_modules_location_metadata) = fs - .metadata(&possible_node_modules_location) - .await - .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? - { - if node_modules_location_metadata.is_dir { - return Ok(Some(path)); - } +async fn read_package_json( + fs: &dyn Fs, + path: &Path, +) -> anyhow::Result>> { + let possible_package_json = path.join("package.json"); + if let Some(package_json_metadata) = fs + .metadata(&possible_package_json) + .await + .with_context(|| format!("fetching metadata for package json {possible_package_json:?}"))? + { + if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { + let package_json_contents = fs + .load(&possible_package_json) + .await + .with_context(|| format!("reading {possible_package_json:?} file contents"))?; + return serde_json::from_str::>( + &package_json_contents, + ) + .map(Some) + .with_context(|| format!("parsing {possible_package_json:?} file contents")); } } Ok(None) } +fn has_prettier_in_package_json( + package_json_contents: &HashMap, +) -> bool { + if let Some(serde_json::Value::Object(o)) = package_json_contents.get("dependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return true; + } + } + if let Some(serde_json::Value::Object(o)) = package_json_contents.get("devDependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return true; + } + } + false +} + enum Format {} #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -466,3 +480,316 @@ impl lsp::request::Request for ClearCache { type Result = (); const METHOD: &'static str = "prettier/clear_cache"; } + +#[cfg(test)] +mod tests { + use fs::FakeFs; + use serde_json::json; + + use super::*; + + #[gpui::test] + async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + ".config": { + "zed": { + "settings.json": r#"{ "formatter": "auto" }"#, + }, + }, + "work": { + "project": { + "src": { + "index.js": "// index.js file contents", + }, + "node_modules": { + "expect": { + "build": { + "print.js": "// print.js file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.5.1" + } + }"#, + }, + "prettier": { + "index.js": "// Dummy prettier package file", + }, + }, + "package.json": r#"{}"# + }, + } + }), + ) + .await; + + assert!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/.config/zed/settings.json"), + ) + .await + .unwrap() + .is_none(), + "Should successfully find no prettier for path hierarchy without it" + ); + assert!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/project/src/index.js") + ) + .await + .unwrap() + .is_none(), + "Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it" + ); + assert!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/project/node_modules/expect/build/print.js") + ) + .await + .unwrap() + .is_none(), + "Even though it has package.json with prettier in it and no prettier on node_modules along the path, nothing should fail since declared inside node_modules" + ); + } + + #[gpui::test] + async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "web_blog": { + "node_modules": { + "prettier": { + "index.js": "// Dummy prettier package file", + }, + "expect": { + "build": { + "print.js": "// print.js file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.5.1" + } + }"#, + }, + }, + "pages": { + "[slug].tsx": "// [slug].tsx file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.3.0" + }, + "prettier": { + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 + } + }"# + } + }), + ) + .await; + + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/web_blog/pages/[slug].tsx") + ) + .await + .unwrap(), + Some(PathBuf::from("/root/web_blog")), + "Should find a preinstalled prettier in the project root" + ); + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/web_blog/node_modules/expect/build/print.js") + ) + .await + .unwrap(), + Some(PathBuf::from("/root/web_blog")), + "Should find a preinstalled prettier in the project root even for node_modules files" + ); + } + + #[gpui::test] + async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "work": { + "web_blog": { + "pages": { + "[slug].tsx": "// [slug].tsx file contents", + }, + "package.json": r#"{ + "devDependencies": { + "prettier": "2.3.0" + }, + "prettier": { + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 + } + }"# + } + } + }), + ) + .await; + + let path = "/root/work/web_blog/node_modules/pages/[slug].tsx"; + match Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new(path) + ) + .await { + Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), + Err(e) => { + let message = e.to_string(); + assert!(message.contains(path), "Error message should mention which start file was used for location"); + assert!(message.contains("/root/work/web_blog"), "Error message should mention potential candidates without prettier node_modules contents"); + }, + }; + + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::from_iter( + [PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter() + ), + Path::new("/root/work/web_blog/node_modules/pages/[slug].tsx") + ) + .await + .unwrap(), + Some(PathBuf::from("/root/work")), + "Should return first cached value found without path checks" + ); + } + + #[gpui::test] + async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "work": { + "full-stack-foundations": { + "exercises": { + "03.loading": { + "01.problem.loader": { + "app": { + "routes": { + "users+": { + "$username_+": { + "notes.tsx": "// notes.tsx file contents", + }, + }, + }, + }, + "node_modules": {}, + "package.json": r#"{ + "devDependencies": { + "prettier": "^3.0.3" + } + }"# + }, + }, + }, + "package.json": r#"{ + "workspaces": ["exercises/*/*", "examples/*"] + }"#, + "node_modules": { + "prettier": { + "index.js": "// Dummy prettier package file", + }, + }, + }, + } + }), + ) + .await; + + assert_eq!( + Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"), + ).await.unwrap(), + Some(PathBuf::from("/root/work/full-stack-foundations")), + "Should ascend to the multi-workspace root and find the prettier there", + ); + } + + #[gpui::test] + async fn test_prettier_lookup_in_npm_workspaces_for_not_installed( + cx: &mut gpui::TestAppContext, + ) { + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "work": { + "full-stack-foundations": { + "exercises": { + "03.loading": { + "01.problem.loader": { + "app": { + "routes": { + "users+": { + "$username_+": { + "notes.tsx": "// notes.tsx file contents", + }, + }, + }, + }, + "node_modules": {}, + "package.json": r#"{ + "devDependencies": { + "prettier": "^3.0.3" + } + }"# + }, + }, + }, + "package.json": r#"{ + "workspaces": ["exercises/*/*", "examples/*"] + }"#, + }, + } + }), + ) + .await; + + match Prettier::locate_prettier_installation( + fs.as_ref(), + &HashSet::default(), + Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx") + ) + .await { + Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), + Err(e) => { + let message = e.to_string(); + assert!(message.contains("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader"), "Error message should mention which project had prettier defined"); + assert!(message.contains("/root/work/full-stack-foundations"), "Error message should mention potential candidates without prettier node_modules contents"); + }, + }; + } +} diff --git a/crates/prettier2/src/prettier_server.js b/crates/prettier2/src/prettier_server.js index a56c220f20..191431da0b 100644 --- a/crates/prettier2/src/prettier_server.js +++ b/crates/prettier2/src/prettier_server.js @@ -1,11 +1,13 @@ -const { Buffer } = require('buffer'); +const { Buffer } = require("buffer"); const fs = require("fs"); const path = require("path"); -const { once } = require('events'); +const { once } = require("events"); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`); + process.stderr.write( + `Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`, + ); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { @@ -19,7 +21,7 @@ fs.stat(prettierContainerPath, (err, stats) => { process.exit(1); } }); -const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +const prettierPath = path.join(prettierContainerPath, "node_modules/prettier"); class Prettier { constructor(path, prettier, config) { @@ -34,7 +36,7 @@ class Prettier { let config; try { prettier = await loadPrettier(prettierPath); - config = await prettier.resolveConfig(prettierPath) || {}; + config = (await prettier.resolveConfig(prettierPath)) || {}; } catch (e) { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); @@ -42,7 +44,7 @@ class Prettier { process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`); process.stdin.resume(); handleBuffer(new Prettier(prettierPath, prettier, config)); -})() +})(); async function handleBuffer(prettier) { for await (const messageText of readStdin()) { @@ -54,22 +56,29 @@ async function handleBuffer(prettier) { continue; } // allow concurrent request handling by not `await`ing the message handling promise (async function) - handleMessage(message, prettier).catch(e => { - sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); + handleMessage(message, prettier).catch((e) => { + const errorMessage = message; + if ((errorMessage.params || {}).text !== undefined) { + errorMessage.params.text = "..snip.."; + } + sendResponse({ + id: message.id, + ...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`), + }); }); } } const headerSeparator = "\r\n"; -const contentLengthHeaderName = 'Content-Length'; +const contentLengthHeaderName = "Content-Length"; async function* readStdin() { let buffer = Buffer.alloc(0); let streamEnded = false; - process.stdin.on('end', () => { + process.stdin.on("end", () => { streamEnded = true; }); - process.stdin.on('data', (data) => { + process.stdin.on("data", (data) => { buffer = Buffer.concat([buffer, data]); }); @@ -77,7 +86,7 @@ async function* readStdin() { sendResponse(makeError(errorMessage)); buffer = Buffer.alloc(0); messageLength = null; - await once(process.stdin, 'readable'); + await once(process.stdin, "readable"); streamEnded = false; } @@ -88,20 +97,25 @@ async function* readStdin() { if (messageLength === null) { while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) { if (streamEnded) { - await handleStreamEnded('Unexpected end of stream: headers not found'); + await handleStreamEnded("Unexpected end of stream: headers not found"); continue main_loop; } else if (buffer.length > contentLengthHeaderName.length * 10) { - await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`); + await handleStreamEnded( + `Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`, + ); continue main_loop; } - await once(process.stdin, 'readable'); + await once(process.stdin, "readable"); } - const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); - const contentLengthHeader = headers.split(headerSeparator) - .map(header => header.split(':')) - .filter(header => header[2] === undefined) - .filter(header => (header[1] || '').length > 0) - .find(header => (header[0] || '').trim() === contentLengthHeaderName); + const headers = buffer + .subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)) + .toString("ascii"); + const contentLengthHeader = headers + .split(headerSeparator) + .map((header) => header.split(":")) + .filter((header) => header[2] === undefined) + .filter((header) => (header[1] || "").length > 0) + .find((header) => (header[0] || "").trim() === contentLengthHeaderName); const contentLength = (contentLengthHeader || [])[1]; if (contentLength === undefined) { await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); @@ -111,13 +125,14 @@ async function* readStdin() { messageLength = parseInt(contentLength, 10); } - while (buffer.length < (headersLength + messageLength)) { + while (buffer.length < headersLength + messageLength) { if (streamEnded) { await handleStreamEnded( - `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`); + `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`, + ); continue main_loop; } - await once(process.stdin, 'readable'); + await once(process.stdin, "readable"); } const messageEnd = headersLength + messageLength; @@ -125,12 +140,12 @@ async function* readStdin() { buffer = buffer.subarray(messageEnd); headersLength = null; messageLength = null; - yield message.toString('utf8'); + yield message.toString("utf8"); } } catch (e) { sendResponse(makeError(`Error reading stdin: ${e}`)); } finally { - process.stdin.off('data', () => { }); + process.stdin.off("data", () => {}); } } @@ -143,7 +158,7 @@ async function handleMessage(message, prettier) { throw new Error(`Message id is undefined: ${JSON.stringify(message)}`); } - if (method === 'prettier/format') { + if (method === "prettier/format") { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`); } @@ -153,7 +168,7 @@ async function handleMessage(message, prettier) { let resolvedConfig = {}; if (params.options.filepath !== undefined) { - resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {}; + resolvedConfig = (await prettier.prettier.resolveConfig(params.options.filepath)) || {}; } const options = { @@ -161,21 +176,25 @@ async function handleMessage(message, prettier) { ...resolvedConfig, parser: params.options.parser, plugins: params.options.plugins, - path: params.options.filepath + path: params.options.filepath, }; - process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`); + process.stderr.write( + `Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${ + params.options.filepath || "" + }' with options: ${JSON.stringify(options)}\n`, + ); const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); - } else if (method === 'prettier/clear_cache') { + } else if (method === "prettier/clear_cache") { prettier.prettier.clearConfigCache(); - prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {}; + prettier.config = (await prettier.prettier.resolveConfig(prettier.path)) || {}; sendResponse({ id, result: null }); - } else if (method === 'initialize') { + } else if (method === "initialize") { sendResponse({ id, result: { - "capabilities": {} - } + capabilities: {}, + }, }); } else { throw new Error(`Unknown method: ${method}`); @@ -185,18 +204,20 @@ async function handleMessage(message, prettier) { function makeError(message) { return { error: { - "code": -32600, // invalid request code + code: -32600, // invalid request code message, - } + }, }; } function sendResponse(response) { const responsePayloadString = JSON.stringify({ jsonrpc: "2.0", - ...response + ...response, }); - const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + const headers = `${contentLengthHeaderName}: ${Buffer.byteLength( + responsePayloadString, + )}${headerSeparator}${headerSeparator}`; process.stdout.write(headers + responsePayloadString); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 919b563cfb..f7a050e7e0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4157,7 +4157,9 @@ impl Project { match prettier_task.await { Ok(prettier) => { - let buffer_path = buffer.update(&mut cx, |buffer, cx| File::from_dyn(buffer.file()).map(|f| f.abs_path(cx))); + let buffer_path = buffer.update(&mut cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier .format(buffer, buffer_path, &cx) @@ -4213,7 +4215,9 @@ impl Project { match prettier_task.await { Ok(prettier) => { - let buffer_path = buffer.update(&mut cx, |buffer, cx| File::from_dyn(buffer.file()).map(|f| f.abs_path(cx))); + let buffer_path = buffer.update(&mut cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier .format(buffer, buffer_path, &cx) diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 65d1aba820..5d7c976e77 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -54,7 +54,7 @@ use lsp_command::*; use node_runtime::NodeRuntime; use parking_lot::Mutex; use postage::watch; -use prettier::{LocateStart, Prettier}; +use prettier::Prettier; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -82,8 +82,11 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, + http::HttpClient, + merge_json_value_into, + paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, + post_inc, ResultExt, TryFutureExt as _, }; pub use fs::*; @@ -162,17 +165,15 @@ pub struct Project { copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, node: Option>, - #[cfg(not(any(test, feature = "test-support")))] default_prettier: Option, - prettier_instances: HashMap< - (Option, PathBuf), - Shared, Arc>>>, - >, + prettiers_per_worktree: HashMap>>, + prettier_instances: HashMap, Arc>>>>, } -#[cfg(not(any(test, feature = "test-support")))] struct DefaultPrettier { - installation_process: Option>>, + instance: Option, Arc>>>>, + installation_process: Option>>>>, + #[cfg(not(any(test, feature = "test-support")))] installed_plugins: HashSet<&'static str>, } @@ -686,8 +687,8 @@ impl Project { copilot_log_subscription: None, current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(), node: Some(node), - #[cfg(not(any(test, feature = "test-support")))] default_prettier: None, + prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), } }) @@ -789,8 +790,8 @@ impl Project { copilot_log_subscription: None, current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(), node: None, - #[cfg(not(any(test, feature = "test-support")))] default_prettier: None, + prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -963,8 +964,7 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.install_default_formatters(worktree, &language, &settings, cx) - .detach_and_log_err(cx); + self.install_default_formatters(worktree, &language, &settings, cx); } // Start all the newly-enabled language servers. @@ -2720,20 +2720,7 @@ impl Project { let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - let task_buffer = buffer.clone(); - let prettier_installation_task = - self.install_default_formatters(worktree, &new_language, &settings, cx); - cx.spawn(move |project, mut cx| async move { - prettier_installation_task.await?; - let _ = project - .update(&mut cx, |project, cx| { - project.prettier_instance_for_buffer(&task_buffer, cx) - })? - .await; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - + self.install_default_formatters(worktree, &new_language, &settings, cx); if let Some(file) = buffer_file { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { @@ -4096,7 +4083,7 @@ impl Project { } pub fn format( - &self, + &mut self, buffers: HashSet>, push_to_history: bool, trigger: FormatTrigger, @@ -4116,10 +4103,10 @@ impl Project { }) .collect::>(); - cx.spawn(move |this, mut cx| async move { + cx.spawn(move |project, mut cx| async move { // Do not allow multiple concurrent formatting requests for the // same buffer. - this.update(&mut cx, |this, cx| { + project.update(&mut cx, |this, cx| { buffers_with_paths_and_servers.retain(|(buffer, _, _)| { this.buffers_being_formatted .insert(buffer.read(cx).remote_id()) @@ -4127,7 +4114,7 @@ impl Project { })?; let _cleanup = defer({ - let this = this.clone(); + let this = project.clone(); let mut cx = cx.clone(); let buffers = &buffers_with_paths_and_servers; move || { @@ -4195,7 +4182,7 @@ impl Project { { format_operation = Some(FormatOperation::Lsp( Self::format_via_lsp( - &this, + &project, &buffer, buffer_abs_path, &language_server, @@ -4230,7 +4217,7 @@ impl Project { } } (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier_task) = this + if let Some((prettier_path, prettier_task)) = project .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) })?.await { @@ -4247,16 +4234,35 @@ impl Project { .context("formatting via prettier")?, )); } - Err(e) => anyhow::bail!( - "Failed to create prettier instance for buffer during autoformatting: {e:#}" - ), + Err(e) => { + project.update(&mut cx, |project, _| { + match &prettier_path { + Some(prettier_path) => { + project.prettier_instances.remove(prettier_path); + }, + None => { + if let Some(default_prettier) = project.default_prettier.as_mut() { + default_prettier.instance = None; + } + }, + } + })?; + match &prettier_path { + Some(prettier_path) => { + log::error!("Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}"); + }, + None => { + log::error!("Failed to create default prettier instance for buffer during autoformatting: {e:#}"); + }, + } + } } } else if let Some((language_server, buffer_abs_path)) = language_server.as_ref().zip(buffer_abs_path.as_ref()) { format_operation = Some(FormatOperation::Lsp( Self::format_via_lsp( - &this, + &project, &buffer, buffer_abs_path, &language_server, @@ -4269,7 +4275,7 @@ impl Project { } } (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier_task) = this + if let Some((prettier_path, prettier_task)) = project .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) })?.await { @@ -4286,9 +4292,28 @@ impl Project { .context("formatting via prettier")?, )); } - Err(e) => anyhow::bail!( - "Failed to create prettier instance for buffer during formatting: {e:#}" - ), + Err(e) => { + project.update(&mut cx, |project, _| { + match &prettier_path { + Some(prettier_path) => { + project.prettier_instances.remove(prettier_path); + }, + None => { + if let Some(default_prettier) = project.default_prettier.as_mut() { + default_prettier.instance = None; + } + }, + } + })?; + match &prettier_path { + Some(prettier_path) => { + log::error!("Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}"); + }, + None => { + log::error!("Failed to create default prettier instance for buffer during autoformatting: {e:#}"); + }, + } + } } } } @@ -6506,15 +6531,25 @@ impl Project { "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); let prettiers_to_reload = self - .prettier_instances + .prettiers_per_worktree + .get(¤t_worktree_id) .iter() - .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { - Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) - } else { - None - } + .flat_map(|prettier_paths| prettier_paths.iter()) + .flatten() + .filter_map(|prettier_path| { + Some(( + current_worktree_id, + Some(prettier_path.clone()), + self.prettier_instances.get(prettier_path)?.clone(), + )) }) + .chain(self.default_prettier.iter().filter_map(|default_prettier| { + Some(( + current_worktree_id, + None, + default_prettier.instance.clone()?, + )) + })) .collect::>(); cx.background_executor() @@ -6525,9 +6560,14 @@ impl Project { .clear_cache() .await .with_context(|| { - format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" - ) + match prettier_path { + Some(prettier_path) => format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" + ), + None => format!( + "clearing default prettier cache for worktree {worktree_id:?} on prettier settings update" + ), + } }) .map_err(Arc::new) } @@ -8411,7 +8451,12 @@ impl Project { &mut self, buffer: &Model, cx: &mut ModelContext, - ) -> Task, Arc>>>>> { + ) -> Task< + Option<( + Option, + Shared, Arc>>>, + )>, + > { let buffer = buffer.read(cx); let buffer_file = buffer.file(); let Some(buffer_language) = buffer.language() else { @@ -8421,142 +8466,142 @@ impl Project { return Task::ready(None); } - let buffer_file = File::from_dyn(buffer_file); - let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); - let worktree_path = buffer_file - .as_ref() - .and_then(|file| Some(file.worktree.read(cx).abs_path())); - let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - if self.is_local() || worktree_id.is_none() || worktree_path.is_none() { + if self.is_local() { let Some(node) = self.node.as_ref().map(Arc::clone) else { return Task::ready(None); }; - let fs = self.fs.clone(); - cx.spawn(move |this, mut cx| async move { - let prettier_dir = match cx - .background_executor() - .spawn(Prettier::locate( - worktree_path.zip(buffer_path).map( - |(worktree_root_path, starting_path)| LocateStart { - worktree_root_path, - starting_path, - }, - ), - fs, - )) - .await - { - Ok(path) => path, - Err(e) => { - return Some( - Task::ready(Err(Arc::new(e.context( - "determining prettier path for worktree {worktree_path:?}", - )))) - .shared(), - ); - } - }; - - if let Some(existing_prettier) = this - .update(&mut cx, |project, _| { - project - .prettier_instances - .get(&(worktree_id, prettier_dir.clone())) - .cloned() - }) - .ok() - .flatten() - { - return Some(existing_prettier); - } - - log::info!("Found prettier in {prettier_dir:?}, starting."); - let task_prettier_dir = prettier_dir.clone(); - let new_prettier_task = cx - .spawn({ - let this = this.clone(); - move |mut cx| async move { - let new_server_id = this.update(&mut cx, |this, _| { - this.languages.next_language_server_id() - })?; - let prettier = Prettier::start( - worktree_id.map(|id| id.to_usize()), - new_server_id, - task_prettier_dir, - node, - cx.clone(), - ) + match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) + { + Some((worktree_id, buffer_path)) => { + let fs = Arc::clone(&self.fs); + let installed_prettiers = self.prettier_instances.keys().cloned().collect(); + return cx.spawn(|project, mut cx| async move { + match cx + .background_executor() + .spawn(async move { + Prettier::locate_prettier_installation( + fs.as_ref(), + &installed_prettiers, + &buffer_path, + ) + .await + }) .await - .context("prettier start") - .map_err(Arc::new)?; - log::info!("Started prettier in {:?}", prettier.prettier_dir()); - - if let Some(prettier_server) = prettier.server() { - this.update(&mut cx, |project, cx| { - let name = if prettier.is_default() { - LanguageServerName(Arc::from("prettier (default)")) - } else { - let prettier_dir = prettier.prettier_dir(); - let worktree_path = prettier - .worktree_id() - .map(WorktreeId::from_usize) - .and_then(|id| project.worktree_for_id(id, cx)) - .map(|worktree| worktree.read(cx).abs_path()); - match worktree_path { - Some(worktree_path) => { - if worktree_path.as_ref() == prettier_dir { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - ))) - } else { - let dir_to_display = match prettier_dir - .strip_prefix(&worktree_path) - .ok() - { - Some(relative_path) => relative_path, - None => prettier_dir, - }; - LanguageServerName(Arc::from(format!( - "prettier ({})", - dir_to_display.display(), - ))) - } - } - None => LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir.display(), - ))), - } - }; - + { + Ok(None) => { + match project.update(&mut cx, |project, _| { project - .supplementary_language_servers - .insert(new_server_id, (name, Arc::clone(prettier_server))); - cx.emit(Event::LanguageServerAdded(new_server_id)); - })?; + .prettiers_per_worktree + .entry(worktree_id) + .or_default() + .insert(None); + project.default_prettier.as_ref().and_then( + |default_prettier| default_prettier.instance.clone(), + ) + }) { + Ok(Some(old_task)) => Some((None, old_task)), + Ok(None) => { + match project.update(&mut cx, |_, cx| { + start_default_prettier(node, Some(worktree_id), cx) + }) { + Ok(new_default_prettier) => { + return Some((None, new_default_prettier.await)) + } + Err(e) => { + Some(( + None, + Task::ready(Err(Arc::new(e.context("project is gone during default prettier startup")))) + .shared(), + )) + } + } + } + Err(e) => Some((None, Task::ready(Err(Arc::new(e.context("project is gone during default prettier checks")))) + .shared())), + } + } + Ok(Some(prettier_dir)) => { + match project.update(&mut cx, |project, _| { + project + .prettiers_per_worktree + .entry(worktree_id) + .or_default() + .insert(Some(prettier_dir.clone())); + project.prettier_instances.get(&prettier_dir).cloned() + }) { + Ok(Some(existing_prettier)) => { + log::debug!( + "Found already started prettier in {prettier_dir:?}" + ); + return Some((Some(prettier_dir), existing_prettier)); + } + Err(e) => { + return Some(( + Some(prettier_dir), + Task::ready(Err(Arc::new(e.context("project is gone during custom prettier checks")))) + .shared(), + )) + } + _ => {}, + } + + log::info!("Found prettier in {prettier_dir:?}, starting."); + let new_prettier_task = + match project.update(&mut cx, |project, cx| { + let new_prettier_task = start_prettier( + node, + prettier_dir.clone(), + Some(worktree_id), + cx, + ); + project.prettier_instances.insert( + prettier_dir.clone(), + new_prettier_task.clone(), + ); + new_prettier_task + }) { + Ok(task) => task, + Err(e) => return Some(( + Some(prettier_dir), + Task::ready(Err(Arc::new(e.context("project is gone during custom prettier startup")))) + .shared() + )), + }; + Some((Some(prettier_dir), new_prettier_task)) + } + Err(e) => { + return Some(( + None, + Task::ready(Err(Arc::new( + e.context("determining prettier path"), + ))) + .shared(), + )); } - Ok(Arc::new(prettier)).map_err(Arc::new) } - }) - .shared(); - this.update(&mut cx, |project, _| { - project - .prettier_instances - .insert((worktree_id, prettier_dir), new_prettier_task.clone()); - }) - .ok(); - Some(new_prettier_task) - }) + }); + } + None => { + let started_default_prettier = self + .default_prettier + .as_ref() + .and_then(|default_prettier| default_prettier.instance.clone()); + match started_default_prettier { + Some(old_task) => return Task::ready(Some((None, old_task))), + None => { + let new_task = start_default_prettier(node, None, cx); + return cx.spawn(|_, _| async move { Some((None, new_task.await)) }); + } + } + } + } } else if self.remote_id().is_some() { return Task::ready(None); } else { - Task::ready(Some( + Task::ready(Some(( + None, Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), - )) + ))) } } @@ -8567,8 +8612,7 @@ impl Project { _: &Language, _: &LanguageSettings, _: &mut ModelContext, - ) -> Task> { - Task::ready(Ok(())) + ) { } #[cfg(not(any(test, feature = "test-support")))] @@ -8578,19 +8622,19 @@ impl Project { new_language: &Language, language_settings: &LanguageSettings, cx: &mut ModelContext, - ) -> Task> { + ) { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => {} - Formatter::LanguageServer | Formatter::External { .. } => return Task::ready(Ok(())), + Formatter::LanguageServer | Formatter::External { .. } => return, }; let Some(node) = self.node.as_ref().cloned() else { - return Task::ready(Ok(())); + return; }; let mut prettier_plugins = None; if new_language.prettier_parser_name().is_some() { prettier_plugins - .get_or_insert_with(|| HashSet::default()) + .get_or_insert_with(|| HashSet::<&'static str>::default()) .extend( new_language .lsp_adapters() @@ -8599,114 +8643,287 @@ impl Project { ) } let Some(prettier_plugins) = prettier_plugins else { - return Task::ready(Ok(())); + return; }; + let fs = Arc::clone(&self.fs); + let locate_prettier_installation = match worktree.and_then(|worktree_id| { + self.worktree_for_id(worktree_id, cx) + .map(|worktree| worktree.read(cx).abs_path()) + }) { + Some(locate_from) => { + let installed_prettiers = self.prettier_instances.keys().cloned().collect(); + cx.background_executor().spawn(async move { + Prettier::locate_prettier_installation( + fs.as_ref(), + &installed_prettiers, + locate_from.as_ref(), + ) + .await + }) + } + None => Task::ready(Ok(None)), + }; let mut plugins_to_install = prettier_plugins; - let (mut install_success_tx, mut install_success_rx) = - futures::channel::mpsc::channel::>(1); - let new_installation_process = cx - .spawn(|this, mut cx| async move { - if let Some(installed_plugins) = install_success_rx.next().await { - this.update(&mut cx, |this, _| { - let default_prettier = - this.default_prettier - .get_or_insert_with(|| DefaultPrettier { - installation_process: None, - installed_plugins: HashSet::default(), - }); - if !installed_plugins.is_empty() { - log::info!("Installed new prettier plugins: {installed_plugins:?}"); - default_prettier.installed_plugins.extend(installed_plugins); - } - }) - .ok(); - } - }) - .shared(); let previous_installation_process = if let Some(default_prettier) = &mut self.default_prettier { plugins_to_install .retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); if plugins_to_install.is_empty() { - return Task::ready(Ok(())); + return; } - std::mem::replace( - &mut default_prettier.installation_process, - Some(new_installation_process.clone()), - ) + default_prettier.installation_process.clone() } else { None }; - let default_prettier_dir = util::paths::DEFAULT_PRETTIER_DIR.as_path(); - let already_running_prettier = self - .prettier_instances - .get(&(worktree, default_prettier_dir.to_path_buf())) - .cloned(); let fs = Arc::clone(&self.fs); - cx.spawn(move |this, mut cx| async move { - if let Some(previous_installation_process) = previous_installation_process { - previous_installation_process.await; - } - let mut everything_was_installed = false; - this.update(&mut cx, |this, _| { - match &mut this.default_prettier { - Some(default_prettier) => { - plugins_to_install - .retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); - everything_was_installed = plugins_to_install.is_empty(); - }, - None => this.default_prettier = Some(DefaultPrettier { installation_process: Some(new_installation_process), installed_plugins: HashSet::default() }), - } - })?; - if everything_was_installed { - return Ok(()); - } - - cx.spawn(move |_| async move { - 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(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 - .iter() - .chain(Some(&"prettier")) - .map(|package_name| async { - let returned_package_name = package_name.to_string(); - let latest_version = node.npm_package_latest_version(package_name) + let default_prettier = self + .default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installation_process: None, + installed_plugins: HashSet::default(), + }); + default_prettier.installation_process = Some( + cx.spawn(|this, mut cx| async move { + match locate_prettier_installation + .await + .context("locate prettier installation") + .map_err(Arc::new)? + { + Some(_non_default_prettier) => return Ok(()), + None => { + let mut needs_install = match previous_installation_process { + Some(previous_installation_process) => { + previous_installation_process.await.is_err() + } + None => true, + }; + this.update(&mut cx, |this, _| { + if let Some(default_prettier) = &mut this.default_prettier { + plugins_to_install.retain(|plugin| { + !default_prettier.installed_plugins.contains(plugin) + }); + needs_install |= !plugins_to_install.is_empty(); + } + })?; + if needs_install { + let installed_plugins = plugins_to_install.clone(); + cx.background_executor() + .spawn(async move { + install_default_prettier(plugins_to_install, node, fs).await + }) .await - .with_context(|| { - format!("fetching latest npm version for package {returned_package_name}") - })?; - anyhow::Ok((returned_package_name, latest_version)) - }), - ) - .await - .context("fetching latest npm versions")?; - - log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); - let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { - (package.as_str(), version.as_str()) - }).collect::>(); - node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; - let installed_packages = !plugins_to_install.is_empty(); - install_success_tx.try_send(plugins_to_install).ok(); - - if !installed_packages { - if let Some(prettier) = already_running_prettier { - prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?; + .context("prettier & plugins install") + .map_err(Arc::new)?; + this.update(&mut cx, |this, _| { + let default_prettier = + this.default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installation_process: Some( + Task::ready(Ok(())).shared(), + ), + installed_plugins: HashSet::default(), + }); + default_prettier.instance = None; + default_prettier.installed_plugins.extend(installed_plugins); + })?; + } } } - - anyhow::Ok(()) - }).await - }) + Ok(()) + }) + .shared(), + ); } } +fn start_default_prettier( + node: Arc, + worktree_id: Option, + cx: &mut ModelContext<'_, Project>, +) -> Task, Arc>>>> { + cx.spawn(|project, mut cx| async move { + loop { + let default_prettier_installing = match project.update(&mut cx, |project, _| { + project + .default_prettier + .as_ref() + .and_then(|default_prettier| default_prettier.installation_process.clone()) + }) { + Ok(installation) => installation, + Err(e) => { + return Task::ready(Err(Arc::new( + e.context("project is gone during default prettier installation"), + ))) + .shared() + } + }; + match default_prettier_installing { + Some(installation_task) => { + if installation_task.await.is_ok() { + break; + } + } + None => break, + } + } + + match project.update(&mut cx, |project, cx| { + match project + .default_prettier + .as_mut() + .and_then(|default_prettier| default_prettier.instance.as_mut()) + { + Some(default_prettier) => default_prettier.clone(), + None => { + let new_default_prettier = + start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx); + project + .default_prettier + .get_or_insert_with(|| DefaultPrettier { + instance: None, + installation_process: None, + #[cfg(not(any(test, feature = "test-support")))] + installed_plugins: HashSet::default(), + }) + .instance = Some(new_default_prettier.clone()); + new_default_prettier + } + } + }) { + Ok(task) => task, + Err(e) => Task::ready(Err(Arc::new( + e.context("project is gone during default prettier startup"), + ))) + .shared(), + } + }) +} + +fn start_prettier( + node: Arc, + prettier_dir: PathBuf, + worktree_id: Option, + cx: &mut ModelContext<'_, Project>, +) -> Shared, Arc>>> { + cx.spawn(|project, mut cx| async move { + let new_server_id = project.update(&mut cx, |project, _| { + project.languages.next_language_server_id() + })?; + let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone()) + .await + .context("default prettier spawn") + .map(Arc::new) + .map_err(Arc::new)?; + register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx); + Ok(new_prettier) + }) + .shared() +} + +fn register_new_prettier( + project: &WeakModel, + prettier: &Prettier, + worktree_id: Option, + new_server_id: LanguageServerId, + cx: &mut AsyncAppContext, +) { + let prettier_dir = prettier.prettier_dir(); + let is_default = prettier.is_default(); + if is_default { + log::info!("Started default prettier in {prettier_dir:?}"); + } else { + log::info!("Started prettier in {prettier_dir:?}"); + } + if let Some(prettier_server) = prettier.server() { + project + .update(cx, |project, cx| { + let name = if is_default { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let worktree_path = worktree_id + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path())); + let name = match worktree_path { + Some(worktree_path) => { + if prettier_dir == worktree_path.as_ref() { + let name = prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + format!("prettier ({name})") + } else { + let dir_to_display = prettier_dir + .strip_prefix(worktree_path.as_ref()) + .ok() + .unwrap_or(prettier_dir); + format!("prettier ({})", dir_to_display.display()) + } + } + None => format!("prettier ({})", prettier_dir.display()), + }; + LanguageServerName(Arc::from(name)) + }; + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier_server))); + cx.emit(Event::LanguageServerAdded(new_server_id)); + }) + .ok(); + } +} + +#[cfg(not(any(test, feature = "test-support")))] +async fn install_default_prettier( + plugins_to_install: HashSet<&'static str>, + node: Arc, + fs: Arc, +) -> anyhow::Result<()> { + 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(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.iter().chain(Some(&"prettier")).map( + |package_name| async { + let returned_package_name = package_name.to_string(); + let latest_version = node + .npm_package_latest_version(package_name) + .await + .with_context(|| { + format!("fetching latest npm version for package {returned_package_name}") + })?; + anyhow::Ok((returned_package_name, latest_version)) + }, + )) + .await + .context("fetching latest npm versions")?; + + log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); + let borrowed_packages = packages_to_versions + .iter() + .map(|(package, version)| (package.as_str(), version.as_str())) + .collect::>(); + node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages) + .await + .context("fetching formatter packages")?; + anyhow::Ok(()) +} + fn subscribe_for_copilot_events( copilot: &Model, cx: &mut ModelContext<'_, Project>, diff --git a/crates/project2/src/project_tests.rs b/crates/project2/src/project_tests.rs index 490b3a0788..19485b2306 100644 --- a/crates/project2/src/project_tests.rs +++ b/crates/project2/src/project_tests.rs @@ -1,4 +1,4 @@ -use crate::{search::PathMatcher, Event, *}; +use crate::{Event, *}; use fs::FakeFs; use futures::{future, StreamExt}; use gpui::AppContext; @@ -13,7 +13,7 @@ use pretty_assertions::assert_eq; use serde_json::json; use std::{os, task::Poll}; use unindent::Unindent as _; -use util::{assert_set_eq, test::temp_tree}; +use util::{assert_set_eq, paths::PathMatcher, test::temp_tree}; #[gpui::test] async fn test_block_via_channel(cx: &mut gpui::TestAppContext) { diff --git a/crates/project2/src/search.rs b/crates/project2/src/search.rs index 46dd30c8a0..7e360e22ee 100644 --- a/crates/project2/src/search.rs +++ b/crates/project2/src/search.rs @@ -1,7 +1,6 @@ use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; use anyhow::{Context, Result}; use client::proto; -use globset::{Glob, GlobMatcher}; use itertools::Itertools; use language::{char_kind, BufferSnapshot}; use regex::{Regex, RegexBuilder}; @@ -10,9 +9,10 @@ use std::{ borrow::Cow, io::{BufRead, BufReader, Read}, ops::Range, - path::{Path, PathBuf}, + path::Path, sync::Arc, }; +use util::paths::PathMatcher; #[derive(Clone, Debug)] pub struct SearchInputs { @@ -52,31 +52,6 @@ pub enum SearchQuery { }, } -#[derive(Clone, Debug)] -pub struct PathMatcher { - maybe_path: PathBuf, - glob: GlobMatcher, -} - -impl std::fmt::Display for PathMatcher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.maybe_path.to_string_lossy().fmt(f) - } -} - -impl PathMatcher { - pub fn new(maybe_glob: &str) -> Result { - Ok(PathMatcher { - glob: Glob::new(&maybe_glob)?.compile_matcher(), - maybe_path: PathBuf::from(maybe_glob), - }) - } - - pub fn is_match>(&self, other: P) -> bool { - other.as_ref().starts_with(&self.maybe_path) || self.glob.is_match(other) - } -} - impl SearchQuery { pub fn text( query: impl ToString, From eb8a0e71487bfdf05072713c6e441a4ca8bca628 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 3 Nov 2023 12:38:09 +0200 Subject: [PATCH 154/156] Uncomment persistence tests --- crates/language2/src/syntax_map.rs | 2 - .../src/semantic_index_tests.rs | 8 +- crates/workspace2/Cargo.toml | 6 +- crates/workspace2/src/dock.rs | 6 +- crates/workspace2/src/item.rs | 12 +- crates/workspace2/src/notifications.rs | 6 +- crates/workspace2/src/pane.rs | 4 +- .../src/pane/dragged_item_receiver.rs | 2 +- crates/workspace2/src/pane_group.rs | 4 +- crates/workspace2/src/persistence.rs | 730 +++++++++--------- crates/workspace2/src/persistence/model.rs | 24 +- crates/workspace2/src/searchable.rs | 2 +- crates/workspace2/src/status_bar.rs | 2 +- crates/workspace2/src/toolbar.rs | 2 +- crates/workspace2/src/workspace2.rs | 4 +- crates/workspace2/src/workspace_settings.rs | 2 +- crates/zed2/src/zed2.rs | 63 +- 17 files changed, 437 insertions(+), 442 deletions(-) diff --git a/crates/language2/src/syntax_map.rs b/crates/language2/src/syntax_map.rs index 4abb9afe7e..18f2e9b264 100644 --- a/crates/language2/src/syntax_map.rs +++ b/crates/language2/src/syntax_map.rs @@ -234,7 +234,6 @@ impl SyntaxMap { self.snapshot.interpolate(text); } - #[allow(dead_code)] // todo!() #[cfg(test)] pub fn reparse(&mut self, language: Arc, text: &BufferSnapshot) { self.snapshot @@ -786,7 +785,6 @@ impl SyntaxSnapshot { ) } - #[allow(dead_code)] // todo!() #[cfg(test)] pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec { self.layers_for_range(0..buffer.len(), buffer).collect() diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 044ded2682..2145d1f9e0 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -289,12 +289,12 @@ async fn test_code_context_retrieval_rust() { impl E { // This is also a preceding comment pub fn function_1() -> Option<()> { - todo!(); + unimplemented!(); } // This is a preceding comment fn function_2() -> Result<()> { - todo!(); + unimplemented!(); } } @@ -344,7 +344,7 @@ async fn test_code_context_retrieval_rust() { " // This is also a preceding comment pub fn function_1() -> Option<()> { - todo!(); + unimplemented!(); }" .unindent(), text.find("pub fn function_1").unwrap(), @@ -353,7 +353,7 @@ async fn test_code_context_retrieval_rust() { " // This is a preceding comment fn function_2() -> Result<()> { - todo!(); + unimplemented!(); }" .unindent(), text.find("fn function_2").unwrap(), diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index 5072f2b8f9..f3f10d2015 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -14,7 +14,7 @@ test-support = [ "client2/test-support", "project2/test-support", "settings2/test-support", - "gpui2/test-support", + "gpui/test-support", "fs2/test-support" ] @@ -25,7 +25,7 @@ client2 = { path = "../client2" } collections = { path = "../collections" } # context_menu = { path = "../context_menu" } fs2 = { path = "../fs2" } -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } install_cli2 = { path = "../install_cli2" } language2 = { path = "../language2" } #menu = { path = "../menu" } @@ -56,7 +56,7 @@ uuid.workspace = true [dev-dependencies] call2 = { path = "../call2", features = ["test-support"] } client2 = { path = "../client2", features = ["test-support"] } -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } project2 = { path = "../project2", features = ["test-support"] } settings2 = { path = "../settings2", features = ["test-support"] } fs2 = { path = "../fs2", features = ["test-support"] } diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 9da9123a2f..9ade6278bb 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,5 +1,5 @@ use crate::{status_bar::StatusItemView, Axis, Workspace}; -use gpui2::{ +use gpui::{ div, Action, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, ParentElement, Render, Subscription, View, ViewContext, WeakView, WindowContext, }; @@ -629,7 +629,7 @@ impl StatusItemView for PanelButtons { #[cfg(any(test, feature = "test-support"))] pub mod test { use super::*; - use gpui2::{div, Div, ViewContext, WindowContext}; + use gpui::{div, Div, ViewContext, WindowContext}; #[derive(Debug)] pub enum TestPanelEvent { @@ -678,7 +678,7 @@ pub mod test { "TestPanel" } - fn position(&self, _: &gpui2::WindowContext) -> super::DockPosition { + fn position(&self, _: &gpui::WindowContext) -> super::DockPosition { self.position } diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index c2d5c25781..15b387cbed 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -11,7 +11,7 @@ use client2::{ proto::{self, PeerId}, Client, }; -use gpui2::{ +use gpui::{ AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, }; @@ -212,7 +212,7 @@ pub trait ItemHandle: 'static + Send { &self, cx: &mut WindowContext, handler: Box, - ) -> gpui2::Subscription; + ) -> gpui::Subscription; 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; @@ -256,7 +256,7 @@ pub trait ItemHandle: 'static + Send { &mut self, cx: &mut AppContext, callback: Box, - ) -> gpui2::Subscription; + ) -> gpui::Subscription; fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; fn breadcrumbs(&self, theme: &ThemeVariant, cx: &AppContext) -> Option>; @@ -286,7 +286,7 @@ impl ItemHandle for View { &self, cx: &mut WindowContext, handler: Box, - ) -> gpui2::Subscription { + ) -> gpui::Subscription { cx.subscribe(self, move |_, event, cx| { for item_event in T::to_item_events(event) { handler(item_event, cx) @@ -573,7 +573,7 @@ impl ItemHandle for View { &mut self, cx: &mut AppContext, callback: Box, - ) -> gpui2::Subscription { + ) -> gpui::Subscription { cx.observe_release(self, move |_, cx| callback(cx)) } @@ -747,7 +747,7 @@ impl FollowableItemHandle for View { // pub mod test { // use super::{Item, ItemEvent}; // use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; -// use gpui2::{ +// use gpui::{ // elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View, // ViewContext, View, WeakViewHandle, // }; diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 9922bcdd26..0e0b291926 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -1,6 +1,6 @@ use crate::{Toast, Workspace}; use collections::HashMap; -use gpui2::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext}; +use gpui::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext}; use std::{any::TypeId, ops::DerefMut}; pub fn init(cx: &mut AppContext) { @@ -160,7 +160,7 @@ impl Workspace { pub mod simple_message_notification { use super::Notification; - use gpui2::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext}; + use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext}; use serde::Deserialize; use std::{borrow::Cow, sync::Arc}; @@ -265,7 +265,7 @@ pub mod simple_message_notification { // "MessageNotification" // } - // fn render(&mut self, cx: &mut gpui2::ViewContext) -> gpui::AnyElement { + // fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { // let theme = theme2::current(cx).clone(); // let theme = &theme.simple_message_notification; diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index b30ec0b7f8..7c27b8158b 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -8,7 +8,7 @@ use crate::{ }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; -use gpui2::{ +use gpui::{ AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, Model, PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; @@ -2907,6 +2907,6 @@ impl Render for DraggedTab { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - div().w_8().h_4().bg(gpui2::red()) + div().w_8().h_4().bg(gpui::red()) } } diff --git a/crates/workspace2/src/pane/dragged_item_receiver.rs b/crates/workspace2/src/pane/dragged_item_receiver.rs index 292529e787..d8e967dd75 100644 --- a/crates/workspace2/src/pane/dragged_item_receiver.rs +++ b/crates/workspace2/src/pane/dragged_item_receiver.rs @@ -1,6 +1,6 @@ use super::DraggedItem; use crate::{Pane, SplitDirection, Workspace}; -use gpui2::{ +use gpui::{ color::Color, elements::{Canvas, MouseEventHandler, ParentElement, Stack}, geometry::{rect::RectF, vector::Vector2F}, diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index e521c51bda..441aef21f5 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -6,9 +6,7 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui2::{ - point, size, AnyElement, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext, -}; +use gpui::{point, size, AnyElement, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext}; use parking_lot::Mutex; use project2::Project; use serde::Deserialize; diff --git a/crates/workspace2/src/persistence.rs b/crates/workspace2/src/persistence.rs index 435518271d..9790495087 100644 --- a/crates/workspace2/src/persistence.rs +++ b/crates/workspace2/src/persistence.rs @@ -6,7 +6,7 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui2::WindowBounds; +use gpui::WindowBounds; use util::{unzip_option, ResultExt}; use uuid::Uuid; @@ -549,425 +549,425 @@ impl WorkspaceDb { } } -// todo!() -// #[cfg(test)] -// mod tests { -// use super::*; -// use db::open_test_db; +#[cfg(test)] +mod tests { + use super::*; + use db2::open_test_db; + use gpui; -// #[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: 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( + Axis::Horizontal, + vec![ + group( + 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( + Axis::Horizontal, + vec![ + group( + 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( + Axis::Horizontal, + vec![ + group( + 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( + 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 de4518f68e..2b8ec94bd4 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::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; +use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; use project2::Project; use std::{ path::{Path, PathBuf}, @@ -55,7 +55,7 @@ impl Column for WorkspaceLocation { } } -#[derive(PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, @@ -127,7 +127,7 @@ impl Bind for DockData { } } -#[derive(PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub enum SerializedPaneGroup { Group { axis: Axis, @@ -286,15 +286,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/searchable.rs b/crates/workspace2/src/searchable.rs index 3935423635..2b870c2944 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::{AnyView, AppContext, Subscription, Task, View, ViewContext, WindowContext}; +use gpui::{AnyView, AppContext, Subscription, Task, View, ViewContext, WindowContext}; use project2::search::SearchQuery; use crate::{ diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index c2f78d9ad6..ca4ebcdb13 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -1,7 +1,7 @@ use std::any::TypeId; use crate::{ItemHandle, Pane}; -use gpui2::{ +use gpui::{ div, AnyView, Component, Div, ParentElement, Render, Styled, Subscription, View, ViewContext, WindowContext, }; diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index c3d1e520c7..80503ad7bb 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -1,5 +1,5 @@ use crate::ItemHandle; -use gpui2::{ +use gpui::{ AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext, }; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3d9b86a051..a754daaffa 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -30,7 +30,7 @@ use futures::{ future::try_join_all, Future, FutureExt, StreamExt, }; -use gpui2::{ +use gpui::{ div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription, @@ -460,7 +460,7 @@ struct Follower { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { - use gpui2::Context; + use gpui::Context; use node_runtime::FakeNodeRuntime; use settings2::SettingsStore; diff --git a/crates/workspace2/src/workspace_settings.rs b/crates/workspace2/src/workspace_settings.rs index 4b93b705a3..c4d1bb41cd 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], - _: &mut gpui2::AppContext, + _: &mut gpui::AppContext, ) -> anyhow::Result { Self::load_via_json_merge(default_value, user_values) } diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 4f28536085..713345b2ee 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -69,11 +69,10 @@ pub async fn handle_cli_connection( 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() + workspace2::last_opened_workspace_paths() + .await + .map(|location| location.paths().to_vec()) + .unwrap_or_default() } else { paths .into_iter() @@ -260,33 +259,33 @@ pub fn initialize_workspace( 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); - // }) + pane.toolbar().update(cx, |toolbar, cx| { + // todo!() + // 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); + }) }); } } From f1fc07de946acdd067be53b0eed11443c69c5717 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 3 Nov 2023 12:55:06 +0200 Subject: [PATCH 155/156] Move journal2 to workspace2 --- Cargo.lock | 2 +- crates/journal2/Cargo.toml | 2 +- crates/journal2/src/journal2.rs | 63 +++++++++++----------- crates/zed2/src/main.rs | 89 ++++++++++++++++---------------- crates/zed2/src/only_instance.rs | 7 ++- 5 files changed, 82 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17d19258e4..ea80c92376 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4232,7 +4232,7 @@ dependencies = [ "settings2", "shellexpand", "util", - "workspace", + "workspace2", ] [[package]] diff --git a/crates/journal2/Cargo.toml b/crates/journal2/Cargo.toml index f43d90fc85..72da3deb69 100644 --- a/crates/journal2/Cargo.toml +++ b/crates/journal2/Cargo.toml @@ -12,7 +12,7 @@ doctest = false editor = { path = "../editor" } gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } -workspace = { path = "../workspace" } +workspace2 = { path = "../workspace2" } settings2 = { path = "../settings2" } anyhow.workspace = true diff --git a/crates/journal2/src/journal2.rs b/crates/journal2/src/journal2.rs index fa6e05cca7..20d520e36e 100644 --- a/crates/journal2/src/journal2.rs +++ b/crates/journal2/src/journal2.rs @@ -9,7 +9,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use workspace::AppState; +use workspace2::AppState; // use zed::AppState; // todo!(); @@ -59,7 +59,7 @@ pub fn init(_: Arc, cx: &mut AppContext) { // cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx)); } -pub fn new_journal_entry(_: Arc, cx: &mut AppContext) { +pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { let settings = JournalSettings::get_global(cx); let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) { Some(journal_dir) => journal_dir, @@ -77,7 +77,7 @@ pub fn new_journal_entry(_: Arc, cx: &mut AppContext) { let now = now.time(); let _entry_heading = heading_entry(now, &settings.hour_format); - let _create_entry = cx.background_executor().spawn(async move { + let create_entry = cx.background_executor().spawn(async move { std::fs::create_dir_all(month_dir)?; OpenOptions::new() .create(true) @@ -86,37 +86,38 @@ pub fn new_journal_entry(_: Arc, cx: &mut AppContext) { Ok::<_, std::io::Error>((journal_dir, entry_path)) }); - // todo!("workspace") - // cx.spawn(|cx| async move { - // let (journal_dir, entry_path) = create_entry.await?; - // let (workspace, _) = - // cx.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?; + cx.spawn(|mut cx| async move { + let (journal_dir, entry_path) = create_entry.await?; + let (workspace, _) = cx + .update(|cx| workspace2::open_paths(&[journal_dir], &app_state, None, cx))? + .await?; - // let opened = workspace - // .update(&mut cx, |workspace, cx| { - // workspace.open_paths(vec![entry_path], true, cx) - // })? - // .await; + let _opened = workspace + .update(&mut cx, |workspace, cx| { + workspace.open_paths(vec![entry_path], true, cx) + })? + .await; - // if let Some(Some(Ok(item))) = opened.first() { - // if let Some(editor) = item.downcast::().map(|editor| editor.downgrade()) { - // editor.update(&mut cx, |editor, cx| { - // let len = editor.buffer().read(cx).len(cx); - // editor.change_selections(Some(Autoscroll::center()), cx, |s| { - // s.select_ranges([len..len]) - // }); - // if len > 0 { - // editor.insert("\n\n", cx); - // } - // editor.insert(&entry_heading, cx); - // editor.insert("\n\n", cx); - // })?; - // } - // } + // todo!("editor") + // if let Some(Some(Ok(item))) = opened.first() { + // if let Some(editor) = item.downcast::().map(|editor| editor.downgrade()) { + // editor.update(&mut cx, |editor, cx| { + // let len = editor.buffer().read(cx).len(cx); + // editor.change_selections(Some(Autoscroll::center()), cx, |s| { + // s.select_ranges([len..len]) + // }); + // if len > 0 { + // editor.insert("\n\n", cx); + // } + // editor.insert(&entry_heading, cx); + // editor.insert("\n\n", cx); + // })?; + // } + // } - // anyhow::Ok(()) - // }) - // .detach_and_log_err(cx); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } fn journal_dir(path: &str) -> Option { diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index f8b77fe9df..6522d97fdc 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -12,6 +12,7 @@ use cli::{ CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; use client::UserStore; +use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use fs::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; @@ -42,11 +43,13 @@ use std::{ thread, time::{SystemTime, UNIX_EPOCH}, }; +use text::Point; use util::{ async_maybe, channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, http::{self, HttpClient}, - paths, ResultExt, + paths::{self, PathLikeWithPosition}, + ResultExt, }; use uuid::Uuid; use workspace2::{AppState, WorkspaceStore}; @@ -228,10 +231,8 @@ fn main() { let mut _triggered_authentication = false; match open_rx.try_next() { - Ok(Some(OpenRequest::Paths { paths: _ })) => { - // todo!("workspace") - // cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - // .detach(); + Ok(Some(OpenRequest::Paths { paths })) => { + workspace2::open_paths(&paths, &app_state, None, cx).detach(); } Ok(Some(OpenRequest::CliConnection { connection })) => { let app_state = app_state.clone(); @@ -263,10 +264,10 @@ fn main() { async move { while let Some(request) = open_rx.next().await { match request { - OpenRequest::Paths { paths: _ } => { - // todo!("workspace") - // cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - // .detach(); + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) + .ok() + .map(|t| t.detach()); } OpenRequest::CliConnection { connection } => { let app_state = app_state.clone(); @@ -781,45 +782,45 @@ async fn handle_cli_connection( ) { 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(); - // todo!("workspace") - // let paths = if paths.is_empty() { - // 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() { + workspace2::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() + }; + // todo!("editor") // let mut errored = false; // match cx - // .update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + // .update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) // .await // { // Ok((workspace, items)) => { diff --git a/crates/zed2/src/only_instance.rs b/crates/zed2/src/only_instance.rs index b252f72ce5..a8c4b30816 100644 --- a/crates/zed2/src/only_instance.rs +++ b/crates/zed2/src/only_instance.rs @@ -37,10 +37,9 @@ pub enum IsOnlyInstance { } pub fn ensure_only_instance() -> IsOnlyInstance { - // todo!("zed_stateless") - // if *db::ZED_STATELESS { - // return IsOnlyInstance::Yes; - // } + if *db::ZED_STATELESS { + return IsOnlyInstance::Yes; + } if check_got_handshake() { return IsOnlyInstance::No; From 1a0cd3e09b59a454fdc0c6a9a26b6654c6d0aefe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 3 Nov 2023 13:20:34 +0200 Subject: [PATCH 156/156] Remove and add more todosmerge . --- crates/workspace2/src/dock.rs | 209 ++++---- crates/workspace2/src/notifications.rs | 58 +-- crates/workspace2/src/pane.rs | 26 +- crates/workspace2/src/workspace2.rs | 640 ++++++++++++------------- crates/zed2/src/zed2.rs | 2 +- 5 files changed, 468 insertions(+), 467 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 9ade6278bb..e6b6c7561d 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -226,9 +226,9 @@ impl Dock { // }) } - // 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 { @@ -241,84 +241,87 @@ impl Dock { } } - // 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(); - // } - - // 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); + // todo!() + // 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(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); - // } - // }), - // ]; + // 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 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 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(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); + // todo!() + // 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 fn panels_len(&self) -> usize { - // self.panel_entries.len() - // } + // todo!() + // 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 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 { @@ -352,38 +355,38 @@ impl Dock { } } - // 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!() diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 0e0b291926..5dd5b2c7ae 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -220,36 +220,36 @@ 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, + } + } + + 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 + Send + Sync + Fn(&mut ViewContext), + { + self.on_click = Some(Arc::new(on_click)); + self + } + // 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 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 Render for MessageNotification { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 7c27b8158b..16dbfda361 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -416,17 +416,17 @@ impl Pane { } } - // pub(crate) fn workspace(&self) -> &WeakView { - // &self.workspace - // } + pub(crate) fn workspace(&self) -> &WeakView { + &self.workspace + } 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 @@ -1865,14 +1865,14 @@ impl Pane { // .into_any() // } - // pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { - // self.zoomed = zoomed; - // cx.notify(); - // } + pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.zoomed = zoomed; + cx.notify(); + } - // pub fn is_zoomed(&self) -> bool { - // self.zoomed - // } + pub fn is_zoomed(&self) -> bool { + self.zoomed + } } // impl Entity for Pane { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a754daaffa..bb9cb7e527 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -8,6 +8,7 @@ pub mod pane; pub mod pane_group; mod persistence; pub mod searchable; +// todo!() // pub mod shared_screen; mod status_bar; mod toolbar; @@ -23,7 +24,7 @@ use client2::{ proto::{self, PeerId}, Client, TypedEnvelope, UserStore, }; -use collections::{HashMap, HashSet}; +use collections::{hash_map, HashMap, HashSet}; use dock::{Dock, DockPosition, PanelButtons}; use futures::{ channel::{mpsc, oneshot}, @@ -38,6 +39,7 @@ use gpui::{ WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; +use itertools::Itertools; use language2::LanguageRegistry; use lazy_static::lazy_static; use node_runtime::NodeRuntime; @@ -174,42 +176,42 @@ pub struct Toast { 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, -// } -// } +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 -// } -// } + 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 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(), -// } -// } -// } +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 { @@ -476,8 +478,7 @@ impl AppState { 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)); - // todo!() - // theme::init((), cx); + theme2::init(cx); client2::init(&client, cx); crate::init_settings(cx); @@ -549,7 +550,7 @@ pub struct Workspace { weak_self: WeakView, // modal: Option, zoomed: Option, - // zoomed_position: Option, + zoomed_position: Option, center: PaneGroup, left_dock: View, bottom_dock: View, @@ -626,7 +627,7 @@ impl Workspace { } project2::Event::Closed => { - // cx.remove_window(); + cx.remove_window(); } project2::Event::DeletedEntry(entry_id) => { @@ -768,7 +769,7 @@ impl Workspace { weak_self: weak_handle.clone(), // modal: None, zoomed: None, - // zoomed_position: None, + zoomed_position: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -1059,183 +1060,185 @@ impl Workspace { &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)); - // } - // } - // }); - // } + 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)) - // }); + 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() - // } + 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: WeakView, - // mode: NavigationMode, - // cx: &mut ViewContext, - // ) -> Task> { - // let to_load = if let Some(pane) = pane.upgrade(cx) { - // cx.focus(&pane); + fn navigate_history( + &mut self, + pane: WeakView, + mode: NavigationMode, + cx: &mut ViewContext, + ) -> Task> { + let to_load = if let Some(pane) = pane.upgrade() { + // todo!("focus") + // 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)?; + 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); + // If the item is still present in this pane, then activate it. + if let Some(index) = entry + .item + .upgrade() + .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); - // } + 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 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()) - // })?; + 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); - // } - // })?; - // } + 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?; - // } + if !navigated { + workspace + .update(&mut cx, |workspace, cx| { + Self::navigate_history(workspace, pane, mode, cx) + })? + .await?; + } - // Ok(()) - // }) - // } else { - // Task::ready(Ok(())) - // } - // } + Ok(()) + }) + } else { + Task::ready(Ok(())) + } + } - // pub fn go_back( - // &mut self, - // pane: WeakView, - // cx: &mut ViewContext, - // ) -> Task> { - // self.navigate_history(pane, NavigationMode::GoingBack, cx) - // } + pub fn go_back( + &mut self, + pane: WeakView, + cx: &mut ViewContext, + ) -> Task> { + self.navigate_history(pane, NavigationMode::GoingBack, cx) + } - // pub fn go_forward( - // &mut self, - // pane: WeakView, - // cx: &mut ViewContext, - // ) -> Task> { - // self.navigate_history(pane, NavigationMode::GoingForward, cx) - // } + pub fn go_forward( + &mut self, + pane: WeakView, + 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 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 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(); - // } + // todo!() + // 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() - // } + // pub fn titlebar_item(&self) -> Option { + // self.titlebar_item.clone() + // } // /// Call the given callback with a workspace whose project is local. // /// @@ -1261,32 +1264,29 @@ impl Workspace { // } // } - // pub fn worktrees<'a>( - // &self, - // cx: &'a AppContext, - // ) -> impl 'a + Iterator> { - // self.project.read(cx).worktrees(cx) - // } + pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator> { + self.project.read(cx).worktrees() + } - // pub fn visible_worktrees<'a>( - // &self, - // cx: &'a AppContext, - // ) -> impl 'a + Iterator> { - // self.project.read(cx).visible_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 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 { @@ -1699,31 +1699,31 @@ impl Workspace { 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)) - // } + 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(); + 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(()) - // } - // }) - // } + 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, @@ -1825,19 +1825,20 @@ impl Workspace { // 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]; + 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); - // }); - // } + for dock in docks { + dock.update(cx, |dock, cx| { + dock.set_open(false, cx); + }); + } - // cx.focus_self(); - // cx.notify(); - // self.serialize_workspace(cx); - // } + // todo!("focus") + // 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> { @@ -1904,19 +1905,19 @@ impl Workspace { // None // } - // fn zoom_out(&mut self, cx: &mut ViewContext) { - // for pane in &self.panes { - // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - // } + 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; + 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(); - // } + cx.notify(); + } // #[cfg(any(test, feature = "test-support"))] // pub fn zoomed_view(&self, cx: &AppContext) -> Option { @@ -1962,22 +1963,21 @@ impl Workspace { // cx.notify(); // } - fn add_pane(&mut self, _cx: &mut ViewContext) -> View { - 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()); + 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()); // todo!() // cx.focus(&pane); - // cx.emit(Event::PaneAdded(pane.clone())); - // pane + cx.emit(Event::PaneAdded(pane.clone())); + pane } // pub fn add_item_to_center( @@ -3122,6 +3122,7 @@ impl Workspace { None } + // todo!() // fn shared_screen_for_peer( // &self, // peer_id: PeerId, @@ -3498,6 +3499,7 @@ impl Workspace { }) } + // todo!() // #[cfg(any(test, feature = "test-support"))] // pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { // use node_runtime::FakeNodeRuntime; @@ -3658,6 +3660,7 @@ fn open_items( }) } +// todo!() // 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"; @@ -3738,23 +3741,22 @@ fn open_items( // }) // .ok(); -fn notify_if_database_failed(_workspace: WindowHandle, _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!() - // 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(); + workspace + .update(cx, |workspace, cx| { + if (*db2::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.open_url(REPORT_ISSUE_URL)) + }) + }); + } + }) + .log_err(); } impl EventEmitter for Workspace { @@ -4176,36 +4178,32 @@ 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; + 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!() + this.update(&mut cx, |this, cx| { + for workspace in &this.workspaces { + 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(()) + })? } } -// impl Entity for WorkspaceStore { -// type Event = (); -// } +impl EventEmitter for WorkspaceStore { + type Event = (); +} impl ViewId { pub(crate) fn from_proto(message: proto::ViewId) -> Result { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 713345b2ee..a33498538e 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -114,7 +114,7 @@ pub async fn handle_cli_connection( match item { Some(Ok(mut item)) => { if let Some(point) = caret_positions.remove(path) { - todo!() + todo!("editor") // if let Some(active_editor) = item.downcast::() { // active_editor // .downgrade()