diff --git a/Cargo.lock b/Cargo.lock index 789b73f99a..775e1d2b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8425,6 +8425,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-nu" +version = "0.0.1" +source = "git+https://github.com/nushell/tree-sitter-nu?rev=786689b0562b9799ce53e824cb45a1a2a04dc673#786689b0562b9799ce53e824cb45a1a2a04dc673" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-php" version = "0.19.1" @@ -9887,6 +9896,7 @@ dependencies = [ "tree-sitter-lua", "tree-sitter-markdown", "tree-sitter-nix", + "tree-sitter-nu", "tree-sitter-php", "tree-sitter-python", "tree-sitter-racket", diff --git a/Cargo.toml b/Cargo.toml index 789652f379..96070658b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,6 +142,7 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } +tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } diff --git a/README.md b/README.md index 8849f1aa73..72936e2746 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,31 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea ### Dependencies -* Install [Postgres.app](https://postgresapp.com) and start it. +* Install Xcode from https://apps.apple.com/us/app/xcode/id497799835?mt=12, and accept the license: + ``` + sudo xcodebuild -license + ``` + +* Install homebrew, rust and node + ``` + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew install rust + brew install node + ``` + +* Ensure rust executables are in your $PATH + ``` + echo $HOME/.cargo/bin | sudo tee /etc/paths.d/10-rust + ``` + +* Install postgres and configure the database + ``` + brew install postgresql@15 + brew services start postgresql@15 + psql -c "CREATE ROLE postgres SUPERUSER LOGIN" postgres + psql -U postgres -c "CREATE DATABASE zed" + ``` + * Install the `LiveKit` server and the `foreman` process supervisor: ``` @@ -41,6 +65,17 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea GITHUB_TOKEN=<$token> script/bootstrap ``` +* Now try running zed with collaboration disabled: + ``` + cargo run + ``` + +### Common errors + +* `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` + * You need to install Xcode and then run: `xcode-select --switch /Applications/Xcode.app/Contents/Developer` + * (see https://github.com/gfx-rs/gfx/issues/2309) + ### Testing against locally-running servers Start the web and collab servers: diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 45891adee6..b47907783e 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -198,6 +198,18 @@ "z c": "editor::Fold", "z o": "editor::UnfoldLines", "z f": "editor::FoldSelectedRanges", + "shift-z shift-q": [ + "pane::CloseActiveItem", + { + "saveBehavior": "dontSave" + } + ], + "shift-z shift-z": [ + "pane::CloseActiveItem", + { + "saveBehavior": "promptOnConflict" + } + ], // Count support "1": [ "vim::Number", diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 8f3a434a83..f0e09e139e 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -13,8 +13,8 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, Subscription, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle, + WeakViewHandle, }; use picker::PickerEvent; use project::{Project, RepositoryEntry}; @@ -1165,7 +1165,7 @@ impl Element for AvatarRibbon { &mut self, constraint: gpui::SizeConstraint, _: &mut CollabTitlebarItem, - _: &mut LayoutContext, + _: &mut ViewContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { (constraint.max, ()) } @@ -1176,7 +1176,7 @@ impl Element for AvatarRibbon { _: RectF, _: &mut Self::LayoutState, _: &mut CollabTitlebarItem, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let mut path = PathBuilder::new(); path.reset(bounds.lower_left()); diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index 835d730d95..5017666f7b 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -7,7 +7,7 @@ use gpui::{ }, json::ToJson, serde_json::{self, json}, - AnyElement, Axis, Element, LayoutContext, PaintContext, View, ViewContext, + AnyElement, Axis, Element, View, ViewContext, }; pub(crate) struct FacePile { @@ -32,7 +32,7 @@ impl Element for FacePile { &mut self, constraint: gpui::SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); @@ -57,7 +57,7 @@ impl Element for FacePile { visible_bounds: RectF, _layout: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 942b4550bc..b7e34fda53 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,8 +32,8 @@ use gpui::{ json::{self, ToJson}, platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent}, text_layout::{self, Line, RunStyle, TextLayoutCache}, - AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, LayoutContext, MouseRegion, - PaintContext, Quad, SizeConstraint, ViewContext, WindowContext, + AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, MouseRegion, Quad, + SizeConstraint, ViewContext, WindowContext, }; use itertools::Itertools; use json::json; @@ -635,7 +635,7 @@ impl EditorElement { visible_bounds: RectF, layout: &mut LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let line_height = layout.position_map.line_height; @@ -778,7 +778,7 @@ impl EditorElement { visible_bounds: RectF, layout: &mut LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let style = &self.style; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -1351,7 +1351,7 @@ impl EditorElement { visible_bounds: RectF, layout: &mut LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_left = scroll_position.x() * layout.position_map.em_width; @@ -1670,7 +1670,7 @@ impl EditorElement { style: &EditorStyle, line_layouts: &[LineWithInvisibles], editor: &mut Editor, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (f32, Vec) { let mut block_id = 0; let scroll_x = snapshot.scroll_anchor.offset.x(); @@ -2092,7 +2092,7 @@ impl Element for EditorElement { &mut self, constraint: SizeConstraint, editor: &mut Editor, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.max; if size.x().is_infinite() { @@ -2570,7 +2570,7 @@ impl Element for EditorElement { visible_bounds: RectF, layout: &mut Self::LayoutState, editor: &mut Editor, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); cx.scene().push_layer(Some(visible_bounds)); @@ -3177,11 +3177,10 @@ mod tests { Point::new(5, 6)..Point::new(6, 0), ]); }); - let mut layout_cx = LayoutContext::new(cx); element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), editor, - &mut layout_cx, + cx, ) }); assert_eq!(state.selections.len(), 1); @@ -3262,11 +3261,10 @@ mod tests { DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0), ]); }); - let mut layout_cx = LayoutContext::new(cx); element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), editor, - &mut layout_cx, + cx, ) }); @@ -3322,11 +3320,10 @@ mod tests { let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let (size, mut state) = editor.update(cx, |editor, cx| { - let mut layout_cx = LayoutContext::new(cx); element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), editor, - &mut layout_cx, + cx, ) }); @@ -3343,13 +3340,7 @@ mod tests { // Don't panic. let bounds = RectF::new(Default::default(), size); editor.update(cx, |editor, cx| { - element.paint( - bounds, - bounds, - &mut state, - editor, - &mut PaintContext::new(cx), - ); + element.paint(bounds, bounds, &mut state, editor, cx); }); } @@ -3517,11 +3508,10 @@ mod tests { editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); editor.set_wrap_width(Some(editor_width), cx); - let mut layout_cx = LayoutContext::new(cx); element.layout( SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)), editor, - &mut layout_cx, + cx, ) }); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 523d6e8a5c..c2d8cc52b2 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1528,8 +1528,13 @@ mod tests { let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); active_pane .update(cx, |pane, cx| { - pane.close_active_item(&workspace::CloseActiveItem, cx) - .unwrap() + pane.close_active_item( + &workspace::CloseActiveItem { + save_behavior: None, + }, + cx, + ) + .unwrap() }) .await .unwrap(); diff --git a/crates/gpui/examples/corner_radii.rs b/crates/gpui/examples/corner_radii.rs index 8e5393e31a..75ea3aeec6 100644 --- a/crates/gpui/examples/corner_radii.rs +++ b/crates/gpui/examples/corner_radii.rs @@ -42,7 +42,7 @@ impl gpui::Element for CornersElement { &mut self, constraint: gpui::SizeConstraint, _: &mut V, - _: &mut gpui::LayoutContext, + _: &mut gpui::ViewContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { (constraint.max, ()) } @@ -53,7 +53,7 @@ impl gpui::Element for CornersElement { _: pathfinder_geometry::rect::RectF, _: &mut Self::LayoutState, _: &mut V, - cx: &mut gpui::PaintContext, + cx: &mut gpui::ViewContext, ) -> Self::PaintState { cx.scene().push_quad(Quad { bounds, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 7f2af36a90..c95c0a6105 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3404,6 +3404,16 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> { .or_default() .push(self_view_id); } + + pub fn paint_layer(&mut self, clip_bounds: Option, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + self.scene().push_layer(clip_bounds); + let result = f(self); + self.scene().pop_layer(); + result + } } impl ViewContext<'_, '_, V> { @@ -3495,151 +3505,6 @@ impl BorrowWindowContext for ViewContext<'_, '_, V> { } } -pub struct LayoutContext<'a, 'b, 'c, V> { - // Nathan: Making this is public while I work on gpui2. - pub view_context: &'c mut ViewContext<'a, 'b, V>, -} - -impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> { - pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self { - Self { view_context } - } - - pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> { - self.view_context - } -} - -impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> { - type Target = ViewContext<'a, 'b, V>; - - fn deref(&self) -> &Self::Target { - &self.view_context - } -} - -impl DerefMut for LayoutContext<'_, '_, '_, V> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.view_context - } -} - -impl BorrowAppContext for LayoutContext<'_, '_, '_, V> { - fn read_with T>(&self, f: F) -> T { - BorrowAppContext::read_with(&*self.view_context, f) - } - - fn update T>(&mut self, f: F) -> T { - BorrowAppContext::update(&mut *self.view_context, f) - } -} - -impl BorrowWindowContext for LayoutContext<'_, '_, '_, V> { - type Result = T; - - fn read_window T>(&self, window: AnyWindowHandle, f: F) -> T { - BorrowWindowContext::read_window(&*self.view_context, window, f) - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - BorrowWindowContext::read_window_optional(&*self.view_context, window, f) - } - - fn update_window T>( - &mut self, - window: AnyWindowHandle, - f: F, - ) -> T { - BorrowWindowContext::update_window(&mut *self.view_context, window, f) - } - - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f) - } -} - -pub struct PaintContext<'a, 'b, 'c, V> { - pub view_context: &'c mut ViewContext<'a, 'b, V>, -} - -impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> { - pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self { - Self { view_context } - } - - pub fn paint_layer(&mut self, clip_bounds: Option, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - self.scene().push_layer(clip_bounds); - let result = f(self); - self.scene().pop_layer(); - result - } -} - -impl<'a, 'b, 'c, V> Deref for PaintContext<'a, 'b, 'c, V> { - type Target = ViewContext<'a, 'b, V>; - - fn deref(&self) -> &Self::Target { - &self.view_context - } -} - -impl DerefMut for PaintContext<'_, '_, '_, V> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.view_context - } -} - -impl BorrowAppContext for PaintContext<'_, '_, '_, V> { - fn read_with T>(&self, f: F) -> T { - BorrowAppContext::read_with(&*self.view_context, f) - } - - fn update T>(&mut self, f: F) -> T { - BorrowAppContext::update(&mut *self.view_context, f) - } -} - -impl BorrowWindowContext for PaintContext<'_, '_, '_, V> { - type Result = T; - - fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&WindowContext) -> T, - { - BorrowWindowContext::read_window(self.view_context, window, f) - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - BorrowWindowContext::read_window_optional(self.view_context, window, f) - } - - fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&mut WindowContext) -> T, - { - BorrowWindowContext::update_window(self.view_context, window, f) - } - - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - BorrowWindowContext::update_window_optional(self.view_context, window, f) - } -} - pub struct EventContext<'a, 'b, 'c, V> { view_context: &'c mut ViewContext<'a, 'b, V>, pub(crate) handled: bool, @@ -6489,25 +6354,21 @@ mod tests { view_1.update(cx, |_, cx| { view_2.update(cx, |_, cx| { // Sanity check - let mut layout_cx = LayoutContext::new(cx); assert_eq!( - layout_cx - .keystrokes_for_action(view_1_id, &Action1) + cx.keystrokes_for_action(view_1_id, &Action1) .unwrap() .as_slice(), &[Keystroke::parse("a").unwrap()] ); assert_eq!( - layout_cx - .keystrokes_for_action(view_2.id(), &Action2) + cx.keystrokes_for_action(view_2.id(), &Action2) .unwrap() .as_slice(), &[Keystroke::parse("b").unwrap()] ); - assert_eq!(layout_cx.keystrokes_for_action(view_1.id(), &Action3), None); + assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None); assert_eq!( - layout_cx - .keystrokes_for_action(view_2.id(), &Action3) + cx.keystrokes_for_action(view_2.id(), &Action3) .unwrap() .as_slice(), &[Keystroke::parse("c").unwrap()] @@ -6516,21 +6377,17 @@ mod tests { // The 'a' keystroke propagates up the view tree from view_2 // to view_1. The action, Action1, is handled by view_1. assert_eq!( - layout_cx - .keystrokes_for_action(view_2.id(), &Action1) + cx.keystrokes_for_action(view_2.id(), &Action1) .unwrap() .as_slice(), &[Keystroke::parse("a").unwrap()] ); // Actions that are handled below the current view don't have bindings - assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None); + assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None); // Actions that are handled in other branches of the tree should not have a binding - assert_eq!( - layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction), - None - ); + assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None); }); }); diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 63462abad9..e17795910f 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -16,9 +16,8 @@ use crate::{ text_layout::TextLayoutCache, util::post_inc, Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext, - BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion, - MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle, - WindowInvalidation, + BorrowWindowContext, Effect, Element, Entity, Handle, MouseRegion, MouseRegionId, SceneBuilder, + Subscription, View, ViewContext, ViewHandle, WindowInvalidation, }; use anyhow::{anyhow, bail, Result}; use collections::{HashMap, HashSet}; @@ -1677,13 +1676,13 @@ impl Element for ChildView { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) { let parent_id = cx.view_id(); cx.window.new_parents.insert(self.view_id, parent_id); let size = rendered_view - .layout(constraint, cx.view_context) + .layout(constraint, cx) .log_err() .unwrap_or(Vector2F::zero()); cx.window.rendered_views.insert(self.view_id, rendered_view); @@ -1704,7 +1703,7 @@ impl Element for ChildView { visible_bounds: RectF, _: &mut Self::LayoutState, _: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) { rendered_view diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 9924db0f1a..cf01a1ddc1 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -34,8 +34,8 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, Action, Entity, LayoutContext, PaintContext, SizeConstraint, TypeTag, View, ViewContext, - WeakViewHandle, WindowContext, + json, Action, Entity, SizeConstraint, TypeTag, View, ViewContext, WeakViewHandle, + WindowContext, }; use anyhow::{anyhow, Result}; use core::panic; @@ -59,7 +59,7 @@ pub trait Element: 'static { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState); fn paint( @@ -68,7 +68,7 @@ pub trait Element: 'static { visible_bounds: RectF, layout: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState; fn rect_for_text_range( @@ -259,7 +259,7 @@ trait AnyElementState { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> Vector2F; fn paint( @@ -267,7 +267,7 @@ trait AnyElementState { origin: Vector2F, visible_bounds: RectF, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ); fn rect_for_text_range( @@ -310,7 +310,7 @@ impl> AnyElementState for ElementState { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> Vector2F { let result; *self = match mem::take(self) { @@ -347,7 +347,7 @@ impl> AnyElementState for ElementState { origin: Vector2F, visible_bounds: RectF, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { *self = match mem::take(self) { ElementState::PostLayout { @@ -357,13 +357,7 @@ impl> AnyElementState for ElementState { mut layout, } => { let bounds = RectF::new(origin, size); - let paint = element.paint( - bounds, - visible_bounds, - &mut layout, - view, - &mut PaintContext::new(cx), - ); + let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx); ElementState::PostPaint { element, constraint, @@ -381,13 +375,7 @@ impl> AnyElementState for ElementState { .. } => { let bounds = RectF::new(origin, bounds.size()); - let paint = element.paint( - bounds, - visible_bounds, - &mut layout, - view, - &mut PaintContext::new(cx), - ); + let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx); ElementState::PostPaint { element, constraint, @@ -510,7 +498,7 @@ impl AnyElement { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> Vector2F { self.state.layout(constraint, view, cx) } @@ -520,7 +508,7 @@ impl AnyElement { origin: Vector2F, visible_bounds: RectF, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { self.state.paint(origin, visible_bounds, view, cx); } @@ -570,7 +558,7 @@ impl Element for AnyElement { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let size = self.layout(constraint, view, cx); (size, ()) @@ -582,7 +570,7 @@ impl Element for AnyElement { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { self.paint(bounds.origin(), visible_bounds, view, cx); } @@ -659,10 +647,7 @@ impl AnyRootElement for RootElement { .view .upgrade(cx) .ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?; - view.update(cx, |view, cx| { - let mut cx = LayoutContext::new(cx); - Ok(self.element.layout(constraint, view, &mut cx)) - }) + view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx))) } fn paint( @@ -677,8 +662,7 @@ impl AnyRootElement for RootElement { .ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?; view.update(cx, |view, cx| { - let mut cx = PaintContext::new(cx); - self.element.paint(origin, visible_bounds, view, &mut cx); + self.element.paint(origin, visible_bounds, view, cx); Ok(()) }) } diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index e79b37299d..ba302f4094 100644 --- a/crates/gpui/src/elements/align.rs +++ b/crates/gpui/src/elements/align.rs @@ -1,6 +1,6 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + json, AnyElement, Element, SizeConstraint, ViewContext, }; use json::ToJson; @@ -48,7 +48,7 @@ impl Element for Align { &mut self, mut constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.max; constraint.min = Vector2F::zero(); @@ -68,7 +68,7 @@ impl Element for Align { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let my_center = bounds.size() / 2.; let my_target = my_center + my_center * self.alignment; diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 93cc0bdfdb..494d9747c5 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::Element; use crate::{ json::{self, json}, - PaintContext, ViewContext, + ViewContext, }; use json::ToJson; use pathfinder_geometry::{ @@ -15,7 +15,7 @@ pub struct Canvas(F, PhantomData); impl Canvas where - F: FnMut(RectF, RectF, &mut V, &mut PaintContext), + F: FnMut(RectF, RectF, &mut V, &mut ViewContext), { pub fn new(f: F) -> Self { Self(f, PhantomData) @@ -24,7 +24,7 @@ where impl Element for Canvas where - F: 'static + FnMut(RectF, RectF, &mut V, &mut PaintContext), + F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext), { type LayoutState = (); type PaintState = (); @@ -33,7 +33,7 @@ where &mut self, constraint: crate::SizeConstraint, _: &mut V, - _: &mut crate::LayoutContext, + _: &mut crate::ViewContext, ) -> (Vector2F, Self::LayoutState) { let x = if constraint.max.x().is_finite() { constraint.max.x() @@ -54,7 +54,7 @@ where visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { self.0(bounds, visible_bounds, view, cx) } diff --git a/crates/gpui/src/elements/clipped.rs b/crates/gpui/src/elements/clipped.rs index e521794f3c..3bd16306bc 100644 --- a/crates/gpui/src/elements/clipped.rs +++ b/crates/gpui/src/elements/clipped.rs @@ -3,7 +3,7 @@ use std::ops::Range; use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use serde_json::json; -use crate::{json, AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext}; +use crate::{json, AnyElement, Element, SizeConstraint, ViewContext}; pub struct Clipped { child: AnyElement, @@ -23,7 +23,7 @@ impl Element for Clipped { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), ()) } @@ -34,7 +34,7 @@ impl Element for Clipped { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { cx.scene().push_layer(Some(bounds)); let state = self.child.paint(bounds.origin(), visible_bounds, view, cx); diff --git a/crates/gpui/src/elements/component.rs b/crates/gpui/src/elements/component.rs index 48f139e793..1d52ede456 100644 --- a/crates/gpui/src/elements/component.rs +++ b/crates/gpui/src/elements/component.rs @@ -2,7 +2,7 @@ use std::{any::Any, marker::PhantomData}; use pathfinder_geometry::{rect::RectF, vector::Vector2F}; -use crate::{AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext}; +use crate::{AnyElement, Element, SizeConstraint, ViewContext}; use super::Empty; @@ -282,14 +282,14 @@ impl + 'static> Element for ComponentAdap &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { if self.element.is_none() { let element = self .component .take() .expect("Component can only be rendered once") - .render(view, cx.view_context()); + .render(view, cx); self.element = Some(element); } let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx); @@ -302,7 +302,7 @@ impl + 'static> Element for ComponentAdap visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { self.element .as_mut() diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs index fe49bd0919..0b49b0951d 100644 --- a/crates/gpui/src/elements/constrained_box.rs +++ b/crates/gpui/src/elements/constrained_box.rs @@ -5,7 +5,7 @@ use serde_json::json; use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + json, AnyElement, Element, SizeConstraint, ViewContext, }; pub struct ConstrainedBox { @@ -15,7 +15,7 @@ pub struct ConstrainedBox { pub enum Constraint { Static(SizeConstraint), - Dynamic(Box) -> SizeConstraint>), + Dynamic(Box) -> SizeConstraint>), } impl ToJson for Constraint { @@ -37,8 +37,7 @@ impl ConstrainedBox { pub fn dynamically( mut self, - constraint: impl 'static - + FnMut(SizeConstraint, &mut V, &mut LayoutContext) -> SizeConstraint, + constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext) -> SizeConstraint, ) -> Self { self.constraint = Constraint::Dynamic(Box::new(constraint)); self @@ -120,7 +119,7 @@ impl ConstrainedBox { &mut self, input_constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> SizeConstraint { match &mut self.constraint { Constraint::Static(constraint) => *constraint, @@ -139,7 +138,7 @@ impl Element for ConstrainedBox { &mut self, mut parent_constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let constraint = self.constraint(parent_constraint, view, cx); parent_constraint.min = parent_constraint.min.max(constraint.min); @@ -155,7 +154,7 @@ impl Element for ConstrainedBox { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { cx.scene().push_layer(Some(visible_bounds)); self.child.paint(bounds.origin(), visible_bounds, view, cx); diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index 6a0f860594..c2d8fd38bd 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -10,7 +10,7 @@ use crate::{ json::ToJson, platform::CursorStyle, scene::{self, CornerRadii, CursorRegion, Quad}, - AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + AnyElement, Element, SizeConstraint, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -371,7 +371,7 @@ impl Element for Container { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let mut size_buffer = self.margin_size() + self.padding_size(); if !self.style.border.overlay { @@ -391,7 +391,7 @@ impl Element for Container { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let quad_bounds = RectF::from_points( bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top), diff --git a/crates/gpui/src/elements/empty.rs b/crates/gpui/src/elements/empty.rs index 021c58ecba..4344199278 100644 --- a/crates/gpui/src/elements/empty.rs +++ b/crates/gpui/src/elements/empty.rs @@ -6,7 +6,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - LayoutContext, PaintContext, ViewContext, + ViewContext, }; use crate::{Element, SizeConstraint}; @@ -34,7 +34,7 @@ impl Element for Empty { &mut self, constraint: SizeConstraint, _: &mut V, - _: &mut LayoutContext, + _: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let x = if constraint.max.x().is_finite() && !self.collapsed { constraint.max.x() @@ -56,7 +56,7 @@ impl Element for Empty { _: RectF, _: &mut Self::LayoutState, _: &mut V, - _: &mut PaintContext, + _: &mut ViewContext, ) -> Self::PaintState { } diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs index 0baab03ed8..0cafa3f119 100644 --- a/crates/gpui/src/elements/expanded.rs +++ b/crates/gpui/src/elements/expanded.rs @@ -2,7 +2,7 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + json, AnyElement, Element, SizeConstraint, ViewContext, }; use serde_json::json; @@ -42,7 +42,7 @@ impl Element for Expanded { &mut self, mut constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { if self.full_width { constraint.min.set_x(constraint.max.x()); @@ -60,7 +60,7 @@ impl Element for Expanded { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { self.child.paint(bounds.origin(), visible_bounds, view, cx); } diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 37cb512bd3..cdce0423fd 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -2,8 +2,7 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc}; use crate::{ json::{self, ToJson, Value}, - AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SizeConstraint, - Vector2FExt, ViewContext, + AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext, }; use pathfinder_geometry::{ rect::RectF, @@ -85,7 +84,7 @@ impl Flex { remaining_flex: &mut f32, cross_axis_max: &mut f32, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) { let cross_axis = self.axis.invert(); for child in self.children.iter_mut() { @@ -136,7 +135,7 @@ impl Element for Flex { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let mut total_flex = None; let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing; @@ -225,7 +224,7 @@ impl Element for Flex { } if let Some(scroll_state) = self.scroll_state.as_ref() { - scroll_state.0.update(cx.view_context(), |scroll_state, _| { + scroll_state.0.update(cx, |scroll_state, _| { if let Some(scroll_to) = scroll_state.scroll_to.take() { let visible_start = scroll_state.scroll_position.get(); let visible_end = visible_start + size.along(self.axis); @@ -264,7 +263,7 @@ impl Element for Flex { visible_bounds: RectF, remaining_space: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -442,7 +441,7 @@ impl Element for FlexItem { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); (size, ()) @@ -454,7 +453,7 @@ impl Element for FlexItem { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { self.child.paint(bounds.origin(), visible_bounds, view, cx) } diff --git a/crates/gpui/src/elements/hook.rs b/crates/gpui/src/elements/hook.rs index c3fa0946f3..8797899f03 100644 --- a/crates/gpui/src/elements/hook.rs +++ b/crates/gpui/src/elements/hook.rs @@ -3,7 +3,7 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::json, - AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + AnyElement, Element, SizeConstraint, ViewContext, }; pub struct Hook { @@ -36,7 +36,7 @@ impl Element for Hook { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); if let Some(handler) = self.after_layout.as_mut() { @@ -51,7 +51,7 @@ impl Element for Hook { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { self.child.paint(bounds.origin(), visible_bounds, view, cx); } diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index c1dbe33e68..7e0c7d5daa 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -5,7 +5,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - scene, Element, ImageData, LayoutContext, PaintContext, SizeConstraint, ViewContext, + scene, Element, ImageData, SizeConstraint, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -64,7 +64,7 @@ impl Element for Image { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let data = match &self.source { ImageSource::Path(path) => match cx.asset_cache.png(path) { @@ -95,7 +95,7 @@ impl Element for Image { _: RectF, layout: &mut Self::LayoutState, _: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { if let Some(data) = layout { cx.scene().push_image(scene::Image { diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index cb473eeb08..5ebb9ea688 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -39,7 +39,7 @@ impl Element for KeystrokeLabel { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, AnyElement) { let mut element = if let Some(keystrokes) = cx.keystrokes_for_action(self.view_id, self.action.as_ref()) @@ -65,7 +65,7 @@ impl Element for KeystrokeLabel { visible_bounds: RectF, element: &mut AnyElement, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { element.paint(bounds.origin(), visible_bounds, view, cx); } diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs index 54d89bdaec..d8e6bd3ea6 100644 --- a/crates/gpui/src/elements/label.rs +++ b/crates/gpui/src/elements/label.rs @@ -8,7 +8,7 @@ use crate::{ }, json::{ToJson, Value}, text_layout::{Line, RunStyle}, - Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + Element, SizeConstraint, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -136,7 +136,7 @@ impl Element for Label { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let runs = self.compute_runs(); let line = cx.text_layout_cache().layout_str( @@ -162,7 +162,7 @@ impl Element for Label { visible_bounds: RectF, line: &mut Self::LayoutState, _: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx) diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e1052e9f8c..a23b6fc5e3 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -4,7 +4,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::json, - AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SizeConstraint, ViewContext, + AnyElement, Element, MouseRegion, SizeConstraint, ViewContext, }; use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc}; use sum_tree::{Bias, SumTree}; @@ -99,7 +99,7 @@ impl Element for List { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let state = &mut *self.state.0.borrow_mut(); let size = constraint.max; @@ -253,7 +253,7 @@ impl Element for List { visible_bounds: RectF, scroll_top: &mut ListOffset, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); cx.scene().push_layer(Some(visible_bounds)); @@ -449,7 +449,7 @@ impl StateInner { existing_element: Option<&ListItem>, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> Option>>> { if let Some(ListItem::Rendered(element)) = existing_element { Some(element.clone()) @@ -643,7 +643,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { #[cfg(test)] mod tests { use super::*; - use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext}; + use crate::{elements::Empty, geometry::vector::vec2f, Entity}; use rand::prelude::*; use std::env; @@ -662,8 +662,7 @@ mod tests { }); let mut list = List::new(state.clone()); - let mut layout_cx = LayoutContext::new(cx); - let (size, _) = list.layout(constraint, &mut view, &mut layout_cx); + let (size, _) = list.layout(constraint, &mut view, cx); assert_eq!(size, vec2f(100., 40.)); assert_eq!( state.0.borrow().items.summary().clone(), @@ -687,8 +686,7 @@ mod tests { cx, ); - let mut layout_cx = LayoutContext::new(cx); - let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx); + let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx); assert_eq!( logical_scroll_top, ListOffset { @@ -712,8 +710,7 @@ mod tests { } ); - let mut layout_cx = LayoutContext::new(cx); - let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx); + let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx); assert_eq!(size, vec2f(100., 40.)); assert_eq!( state.0.borrow().items.summary().clone(), @@ -831,11 +828,10 @@ mod tests { let mut list = List::new(state.clone()); let window_size = vec2f(width, height); - let mut layout_cx = LayoutContext::new(cx); let (size, logical_scroll_top) = list.layout( SizeConstraint::new(vec2f(0., 0.), window_size), &mut view, - &mut layout_cx, + cx, ); assert_eq!(size, window_size); last_logical_scroll_top = Some(logical_scroll_top); @@ -948,12 +944,12 @@ mod tests { &mut self, _: SizeConstraint, _: &mut V, - _: &mut LayoutContext, + _: &mut ViewContext, ) -> (Vector2F, ()) { (self.size, ()) } - fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut PaintContext) { + fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext) { unimplemented!() } diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 0b073f4f0f..0a632ba382 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -10,8 +10,8 @@ use crate::{ CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, - AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext, - SizeConstraint, TypeTag, ViewContext, + AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag, + ViewContext, }; use serde_json::json; use std::ops::Range; @@ -270,7 +270,7 @@ impl Element for MouseEventHandler { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), ()) } @@ -281,13 +281,13 @@ impl Element for MouseEventHandler { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { if self.above { self.child.paint(bounds.origin(), visible_bounds, view, cx); - cx.scene().push_layer(None); - self.paint_regions(bounds, visible_bounds, cx); - cx.scene().pop_layer(); + cx.paint_layer(None, |cx| { + self.paint_regions(bounds, visible_bounds, cx); + }); } else { self.paint_regions(bounds, visible_bounds, cx); self.child.paint(bounds.origin(), visible_bounds, view, cx); diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index f8d4708623..11956bc211 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -3,8 +3,7 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, - AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SizeConstraint, - ViewContext, + AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext, }; use serde_json::json; @@ -125,7 +124,7 @@ impl Element for Overlay { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let constraint = if self.anchor_position.is_some() { SizeConstraint::new(Vector2F::zero(), cx.window_size()) @@ -142,7 +141,7 @@ impl Element for Overlay { _: RectF, size: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let (anchor_position, mut bounds) = match self.position_mode { OverlayPositionMode::Window => { diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index e67ad70304..123225cc64 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -7,8 +7,7 @@ use serde_json::json; use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, - SizeConstraint, TypeTag, View, ViewContext, + AnyElement, AppContext, Axis, Element, MouseRegion, SizeConstraint, TypeTag, View, ViewContext, }; #[derive(Copy, Clone, Debug)] @@ -105,7 +104,7 @@ impl Element for Resizable { &mut self, constraint: crate::SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), constraint) } @@ -116,7 +115,7 @@ impl Element for Resizable { visible_bounds: pathfinder_geometry::rect::RectF, constraint: &mut SizeConstraint, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { cx.scene().push_stacking_context(None, None); @@ -241,7 +240,7 @@ impl Element for BoundsProvider { &mut self, constraint: crate::SizeConstraint, view: &mut V, - cx: &mut crate::LayoutContext, + cx: &mut crate::ViewContext, ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), ()) } @@ -252,7 +251,7 @@ impl Element for BoundsProvider { visible_bounds: pathfinder_geometry::rect::RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut crate::PaintContext, + cx: &mut crate::ViewContext, ) -> Self::PaintState { cx.update_default_global::(|map, _| { map.0.insert(TypeTag::new::

(), (bounds, visible_bounds)); diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs index 72f129b1da..ad5080907b 100644 --- a/crates/gpui/src/elements/stack.rs +++ b/crates/gpui/src/elements/stack.rs @@ -3,7 +3,7 @@ use std::ops::Range; use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::{self, json, ToJson}, - AnyElement, Element, LayoutContext, PaintContext, SizeConstraint, ViewContext, + AnyElement, Element, SizeConstraint, ViewContext, }; /// Element which renders it's children in a stack on top of each other. @@ -34,7 +34,7 @@ impl Element for Stack { &mut self, mut constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.min; let mut children = self.children.iter_mut(); @@ -56,7 +56,7 @@ impl Element for Stack { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { for child in &mut self.children { cx.scene().push_layer(None); diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index c81e5a2892..7f67719d8a 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,13 +1,12 @@ use super::constrain_size_preserving_aspect_ratio; use crate::json::ToJson; -use crate::PaintContext; use crate::{ color::Color, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, - scene, Element, LayoutContext, SizeConstraint, ViewContext, + scene, Element, SizeConstraint, ViewContext, }; use schemars::JsonSchema; use serde_derive::Deserialize; @@ -49,7 +48,7 @@ impl Element for Svg { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { match cx.asset_cache.svg(&self.path) { Ok(tree) => { @@ -73,7 +72,7 @@ impl Element for Svg { _visible_bounds: RectF, svg: &mut Self::LayoutState, _: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { if let Some(svg) = svg.clone() { cx.scene().push_icon(scene::Icon { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 224397d067..c823840692 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -7,8 +7,7 @@ use crate::{ }, json::{ToJson, Value}, text_layout::{Line, RunStyle, ShapedBoundary}, - Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache, ViewContext, - WindowContext, + Element, FontCache, SizeConstraint, TextLayoutCache, ViewContext, WindowContext, }; use log::warn; use serde_json::json; @@ -78,7 +77,7 @@ impl Element for Text { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { // Convert the string and highlight ranges into an iterator of highlighted chunks. @@ -170,7 +169,7 @@ impl Element for Text { visible_bounds: RectF, layout: &mut Self::LayoutState, _: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let mut origin = bounds.origin(); let empty = Vec::new(); @@ -409,11 +408,10 @@ mod tests { let mut view = TestView; fonts::with_font_cache(cx.font_cache().clone(), || { let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true); - let mut layout_cx = LayoutContext::new(cx); let (_, state) = text.layout( SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)), &mut view, - &mut layout_cx, + cx, ); assert_eq!(state.shaped_lines.len(), 2); assert_eq!(state.wrap_boundaries.len(), 2); diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index 1c521751f9..59892b6279 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -6,8 +6,7 @@ use crate::{ fonts::TextStyle, geometry::{rect::RectF, vector::Vector2F}, json::json, - Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SizeConstraint, Task, TypeTag, - ViewContext, + Action, Axis, ElementStateHandle, SizeConstraint, Task, TypeTag, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; @@ -189,7 +188,7 @@ impl Element for Tooltip { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); if let Some(tooltip) = self.tooltip.as_mut() { @@ -208,7 +207,7 @@ impl Element for Tooltip { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { self.child.paint(bounds.origin(), visible_bounds, view, cx); if let Some(tooltip) = self.tooltip.as_mut() { diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index e75e134386..037b003b20 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -6,7 +6,7 @@ use crate::{ }, json::{self, json}, platform::ScrollWheelEvent, - AnyElement, LayoutContext, MouseRegion, PaintContext, ViewContext, + AnyElement, MouseRegion, ViewContext, }; use json::ToJson; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -158,7 +158,7 @@ impl Element for UniformList { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { if constraint.max.y().is_infinite() { unimplemented!( @@ -276,7 +276,7 @@ impl Element for UniformList { visible_bounds: RectF, layout: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 9557c6fe91..59584bf086 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -261,6 +261,8 @@ impl SceneBuilder { self.stacking_contexts.push(StackingContext::new(None, 0)); self.active_stacking_context_stack.clear(); self.active_stacking_context_stack.push(0); + #[cfg(debug_assertions)] + self.mouse_region_ids.clear(); } pub fn build(&mut self, scale_factor: f32) -> Scene { diff --git a/crates/gpui2/src/adapter.rs b/crates/gpui2/src/adapter.rs index e0b5483d06..c36966d722 100644 --- a/crates/gpui2/src/adapter.rs +++ b/crates/gpui2/src/adapter.rs @@ -1,4 +1,4 @@ -use crate::{paint_context::PaintContext, ViewContext}; +use crate::ViewContext; use gpui::{geometry::rect::RectF, LayoutEngine, LayoutId}; use util::ResultExt; @@ -13,7 +13,7 @@ impl gpui::Element for AdapterElement { &mut self, constraint: gpui::SizeConstraint, view: &mut V, - cx: &mut gpui::LayoutContext, + cx: &mut gpui::ViewContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { cx.push_layout_engine(LayoutEngine::new()); @@ -40,13 +40,13 @@ impl gpui::Element for AdapterElement { _visible_bounds: RectF, layout_data: &mut Option<(LayoutEngine, LayoutId)>, view: &mut V, - legacy_cx: &mut gpui::PaintContext, + cx: &mut gpui::ViewContext, ) -> Self::PaintState { let (layout_engine, layout_id) = layout_data.take().unwrap(); - legacy_cx.push_layout_engine(layout_engine); - let mut cx = PaintContext::new(legacy_cx); - self.0.paint(view, bounds.origin(), &mut cx); - *layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id)); + cx.push_layout_engine(layout_engine); + self.0 + .paint(view, bounds.origin(), &mut ViewContext::new(cx)); + *layout_data = cx.pop_layout_engine().zip(Some(layout_id)); debug_assert!(layout_data.is_some()); } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index d2a8efee83..ba90a7d7df 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -1,4 +1,3 @@ -pub use crate::paint_context::PaintContext; pub use crate::ViewContext; use anyhow::Result; use gpui::geometry::vector::Vector2F; @@ -22,7 +21,7 @@ pub trait Element: 'static + IntoElement { parent_origin: Vector2F, layout: &Layout, state: &mut Self::PaintState, - cx: &mut PaintContext, + cx: &mut ViewContext, ) where Self: Sized; @@ -40,7 +39,7 @@ pub trait Element: 'static + IntoElement { /// Used to make ElementState into a trait object, so we can wrap it in AnyElement. trait AnyStatefulElement { fn layout(&mut self, view: &mut V, cx: &mut ViewContext) -> Result; - fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext); + fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext); } /// A wrapper around an element that stores its layout state. @@ -105,7 +104,7 @@ impl> AnyStatefulElement for StatefulElement { result } - fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext) { + fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext) { self.phase = match std::mem::take(&mut self.phase) { ElementPhase::PostLayout { layout_id, @@ -149,7 +148,7 @@ impl AnyElement { self.0.layout(view, cx) } - pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext) { + pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext) { self.0.paint(view, parent_origin, cx) } } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 598b564ab1..885b14f2dd 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -3,7 +3,6 @@ use std::{cell::Cell, rc::Rc}; use crate::{ element::{AnyElement, Element, IntoElement, Layout, ParentElement}, hsla, - paint_context::PaintContext, style::{CornerRadii, Overflow, Style, StyleHelpers, Styleable}, InteractionHandlers, Interactive, ViewContext, }; @@ -69,7 +68,7 @@ impl Element for Div { parent_origin: Vector2F, layout: &Layout, child_layouts: &mut Vec, - cx: &mut PaintContext, + cx: &mut ViewContext, ) where Self: Sized, { @@ -167,7 +166,7 @@ impl Div { bounds: RectF, overflow: Point, child_layout_ids: &[LayoutId], - cx: &mut PaintContext, + cx: &mut ViewContext, ) { if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll { let mut scroll_max = Vector2F::zero(); @@ -214,7 +213,7 @@ impl Div { } } - fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut PaintContext) { + fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut ViewContext) { let style = self.styles.merged(); let bounds = layout.bounds + parent_origin; diff --git a/crates/gpui2/src/elements/hoverable.rs b/crates/gpui2/src/elements/hoverable.rs index ba8109038f..eec4b4ff61 100644 --- a/crates/gpui2/src/elements/hoverable.rs +++ b/crates/gpui2/src/elements/hoverable.rs @@ -1,7 +1,6 @@ use crate::{ element::{AnyElement, Element, IntoElement, Layout, ParentElement}, interactive::{InteractionHandlers, Interactive}, - paint_context::PaintContext, style::{Style, StyleHelpers, Styleable}, ViewContext, }; @@ -59,7 +58,7 @@ impl + Styleable> Element for Hoverable { parent_origin: Vector2F, layout: &Layout, paint_state: &mut Self::PaintState, - cx: &mut PaintContext, + cx: &mut ViewContext, ) where Self: Sized, { diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 829a6d1116..866f2fa971 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -53,7 +53,7 @@ impl Element for Img { parent_origin: Vector2F, layout: &gpui::Layout, _: &mut Self::PaintState, - cx: &mut crate::paint_context::PaintContext, + cx: &mut crate::ViewContext, ) where Self: Sized, { diff --git a/crates/gpui2/src/elements/pressable.rs b/crates/gpui2/src/elements/pressable.rs index 1b696e7ef6..960342a5e6 100644 --- a/crates/gpui2/src/elements/pressable.rs +++ b/crates/gpui2/src/elements/pressable.rs @@ -1,7 +1,6 @@ use crate::{ element::{AnyElement, Element, IntoElement, Layout, ParentElement}, interactive::{InteractionHandlers, Interactive}, - paint_context::PaintContext, style::{Style, StyleHelpers, Styleable}, ViewContext, }; @@ -59,7 +58,7 @@ impl + Styleable> Element for Pressable { parent_origin: Vector2F, layout: &Layout, paint_state: &mut Self::PaintState, - cx: &mut PaintContext, + cx: &mut ViewContext, ) where Self: Sized, { diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index c17cdbe3c6..07f31d8b6e 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -49,7 +49,7 @@ impl Element for Svg { parent_origin: Vector2F, layout: &Layout, _: &mut Self::PaintState, - cx: &mut crate::paint_context::PaintContext, + cx: &mut crate::ViewContext, ) where Self: Sized, { diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 3b3acb4523..6f89375df0 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,6 +1,5 @@ use crate::{ element::{Element, IntoElement, Layout}, - paint_context::PaintContext, ViewContext, }; use anyhow::Result; @@ -71,7 +70,7 @@ impl Element for Text { parent_origin: Vector2F, layout: &Layout, paint_state: &mut Self::PaintState, - cx: &mut PaintContext, + cx: &mut ViewContext, ) { let bounds = layout.bounds + parent_origin; diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 11d5279ec1..355697595f 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -3,7 +3,6 @@ pub mod color; pub mod element; pub mod elements; pub mod interactive; -pub mod paint_context; pub mod style; pub mod view; pub mod view_context; diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index aa96272860..4533e7dbec 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -6,7 +6,7 @@ use gpui::{ use smallvec::SmallVec; use std::{cell::Cell, rc::Rc}; -use crate::element::PaintContext; +use crate::ViewContext; pub trait Interactive { fn interaction_handlers(&mut self) -> &mut InteractionHandlers; @@ -121,7 +121,7 @@ pub struct InteractionHandlers { } impl InteractionHandlers { - pub fn paint(&self, order: u32, bounds: RectF, cx: &mut PaintContext) { + pub fn paint(&self, order: u32, bounds: RectF, cx: &mut ViewContext) { for handler in self.mouse_down.iter().cloned() { cx.on_event(order, move |view, event: &MouseButtonEvent, cx| { if event.is_down && bounds.contains_point(event.position) { diff --git a/crates/gpui2/src/paint_context.rs b/crates/gpui2/src/paint_context.rs deleted file mode 100644 index 9f8548d519..0000000000 --- a/crates/gpui2/src/paint_context.rs +++ /dev/null @@ -1,50 +0,0 @@ -use anyhow::{anyhow, Result}; -use derive_more::{Deref, DerefMut}; -pub use gpui::taffy::tree::NodeId; -use gpui::{ - scene::EventHandler, EventContext, Layout, LayoutId, PaintContext as LegacyPaintContext, -}; -use std::{any::TypeId, rc::Rc}; - -#[derive(Deref, DerefMut)] -pub struct PaintContext<'a, 'b, 'c, 'd, V> { - #[deref] - #[deref_mut] - pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>, -} - -impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> { - pub fn new(legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>) -> Self { - Self { legacy_cx } - } - - pub fn on_event( - &mut self, - order: u32, - handler: impl Fn(&mut V, &E, &mut EventContext) + 'static, - ) { - let view = self.weak_handle(); - - self.scene().event_handlers.push(EventHandler { - order, - handler: Rc::new(move |event, window_cx| { - if let Some(view) = view.upgrade(window_cx) { - view.update(window_cx, |view, view_cx| { - let mut event_cx = EventContext::new(view_cx); - handler(view, event.downcast_ref().unwrap(), &mut event_cx); - event_cx.bubble - }) - } else { - true - } - }), - event_type: TypeId::of::(), - }) - } - - pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result { - self.layout_engine() - .ok_or_else(|| anyhow!("no layout engine present"))? - .computed_layout(layout_id) - } -} diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 8a02dda5d7..e3e0f5b0c4 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -2,7 +2,7 @@ use crate::{ color::Hsla, elements::hoverable::{hoverable, Hoverable}, elements::pressable::{pressable, Pressable}, - paint_context::PaintContext, + ViewContext, }; pub use fonts::Style as FontStyle; pub use fonts::Weight as FontWeight; @@ -164,7 +164,7 @@ impl Style { } /// Paints the background of an element styled with this style. - pub fn paint_background(&self, bounds: RectF, cx: &mut PaintContext) { + pub fn paint_background(&self, bounds: RectF, cx: &mut ViewContext) { let rem_size = cx.rem_size(); if let Some(color) = self.fill.as_ref().and_then(Fill::color) { cx.scene().push_quad(gpui::Quad { @@ -177,7 +177,7 @@ impl Style { } /// Paints the foreground of an element styled with this style. - pub fn paint_foreground(&self, bounds: RectF, cx: &mut PaintContext) { + pub fn paint_foreground(&self, bounds: RectF, cx: &mut ViewContext) { let rem_size = cx.rem_size(); if let Some(color) = self.border_color { diff --git a/crates/gpui2/src/view_context.rs b/crates/gpui2/src/view_context.rs index 43d8093240..d6c0960a34 100644 --- a/crates/gpui2/src/view_context.rs +++ b/crates/gpui2/src/view_context.rs @@ -1,7 +1,9 @@ +use std::{any::TypeId, rc::Rc}; + use crate::{element::LayoutId, style::Style}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; -use gpui::{geometry::Size, MeasureParams}; +use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams}; pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext}; #[derive(Deref, DerefMut)] @@ -44,4 +46,34 @@ impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> { Ok(layout_id) } + + pub fn on_event( + &mut self, + order: u32, + handler: impl Fn(&mut V, &E, &mut EventContext) + 'static, + ) { + let view = self.weak_handle(); + + self.scene().event_handlers.push(EventHandler { + order, + handler: Rc::new(move |event, window_cx| { + if let Some(view) = view.upgrade(window_cx) { + view.update(window_cx, |view, view_cx| { + let mut event_cx = EventContext::new(view_cx); + handler(view, event.downcast_ref().unwrap(), &mut event_cx); + event_cx.bubble + }) + } else { + true + } + }), + event_type: TypeId::of::(), + }) + } + + pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result { + self.layout_engine() + .ok_or_else(|| anyhow!("no layout engine present"))? + .computed_layout(layout_id) + } } diff --git a/crates/gpui2_macros/src/derive_element.rs b/crates/gpui2_macros/src/derive_element.rs index a769437676..304caa99e6 100644 --- a/crates/gpui2_macros/src/derive_element.rs +++ b/crates/gpui2_macros/src/derive_element.rs @@ -80,7 +80,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream { parent_origin: gpui2::Vector2F, _: &gpui2::element::Layout, rendered_element: &mut Self::PaintState, - cx: &mut gpui2::element::PaintContext, + cx: &mut gpui2::ViewContext, ) { rendered_element.paint(view, parent_origin, cx); } diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index b712aace69..aa55c27eaa 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -329,7 +329,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream { &mut self, constraint: gpui::SizeConstraint, view: &mut V, - cx: &mut gpui::LayoutContext, + cx: &mut gpui::ViewContext, ) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement) { let mut element = self.render(view, cx).into_any(); let size = element.layout(constraint, view, cx); @@ -342,7 +342,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream { visible_bounds: gpui::geometry::rect::RectF, element: &mut gpui::elements::AnyElement, view: &mut V, - cx: &mut gpui::PaintContext, + cx: &mut gpui::ViewContext, ) { element.paint(bounds.origin(), visible_bounds, view, cx); } diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index 85ae088565..b925bc8f0d 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": "0af9125c4eae12a4973fb66574c53a54962a9e1e", - "version": "1.21.0" + "revision": "ce20dc083ee485524b802669890291c0d8090170", + "version": "1.22.1" } } ] diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c52be64141..6ca4928803 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -34,6 +34,7 @@ use std::{ ops::{Not, Range}, path::PathBuf, sync::Arc, + time::{Duration, Instant}, }; use util::ResultExt as _; use workspace::{ @@ -130,6 +131,7 @@ pub struct ProjectSearchView { struct SemanticState { index_status: SemanticIndexStatus, + maintain_rate_limit: Option>, _subscription: Subscription, } @@ -319,11 +321,28 @@ impl View for ProjectSearchView { let status = semantic.index_status; match status { SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), - SemanticIndexStatus::Indexing { remaining_files } => { + SemanticIndexStatus::Indexing { + remaining_files, + rate_limit_expiry, + } => { if remaining_files == 0 { Some(format!("Indexing...")) } else { - Some(format!("Remaining files to index: {}", remaining_files)) + if let Some(rate_limit_expiry) = rate_limit_expiry { + let remaining_seconds = + rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) { + Some(format!( + "Remaining files to index (rate limit resets in {}s): {}", + remaining_seconds.as_secs(), + remaining_files + )) + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } } } SemanticIndexStatus::NotIndexed => None, @@ -651,9 +670,10 @@ impl ProjectSearchView { self.semantic_state = Some(SemanticState { index_status: semantic_index.read(cx).status(&project), + maintain_rate_limit: None, _subscription: cx.observe(&semantic_index, Self::semantic_index_changed), }); - cx.notify(); + self.semantic_index_changed(semantic_index, cx); } } @@ -664,8 +684,25 @@ impl ProjectSearchView { ) { let project = self.model.read(cx).project.clone(); if let Some(semantic_state) = self.semantic_state.as_mut() { - semantic_state.index_status = semantic_index.read(cx).status(&project); cx.notify(); + semantic_state.index_status = semantic_index.read(cx).status(&project); + if let SemanticIndexStatus::Indexing { + rate_limit_expiry: Some(_), + .. + } = &semantic_state.index_status + { + if semantic_state.maintain_rate_limit.is_none() { + semantic_state.maintain_rate_limit = + Some(cx.spawn(|this, mut cx| async move { + loop { + cx.background().timer(Duration::from_secs(1)).await; + this.update(&mut cx, |_, cx| cx.notify()).log_err(); + } + })); + return; + } + } + semantic_state.maintain_rate_limit = None; } } diff --git a/crates/semantic_index/src/embedding.rs b/crates/semantic_index/src/embedding.rs index 7228738525..42d90f0fdb 100644 --- a/crates/semantic_index/src/embedding.rs +++ b/crates/semantic_index/src/embedding.rs @@ -7,13 +7,16 @@ use isahc::http::StatusCode; use isahc::prelude::Configurable; use isahc::{AsyncBody, Response}; use lazy_static::lazy_static; +use parking_lot::Mutex; use parse_duration::parse; +use postage::watch; use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::ToSql; use serde::{Deserialize, Serialize}; use std::env; +use std::ops::Add; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, Instant}; use tiktoken_rs::{cl100k_base, CoreBPE}; use util::http::{HttpClient, Request}; @@ -82,6 +85,8 @@ impl ToSql for Embedding { pub struct OpenAIEmbeddings { pub client: Arc, pub executor: Arc, + rate_limit_count_rx: watch::Receiver>, + rate_limit_count_tx: Arc>>>, } #[derive(Serialize)] @@ -114,12 +119,16 @@ pub trait EmbeddingProvider: Sync + Send { async fn embed_batch(&self, spans: Vec) -> Result>; fn max_tokens_per_batch(&self) -> usize; fn truncate(&self, span: &str) -> (String, usize); + fn rate_limit_expiration(&self) -> Option; } pub struct DummyEmbeddings {} #[async_trait] impl EmbeddingProvider for DummyEmbeddings { + fn rate_limit_expiration(&self) -> Option { + None + } async fn embed_batch(&self, spans: Vec) -> Result> { // 1024 is the OpenAI Embeddings size for ada models. // the model we will likely be starting with. @@ -149,6 +158,50 @@ impl EmbeddingProvider for DummyEmbeddings { const OPENAI_INPUT_LIMIT: usize = 8190; impl OpenAIEmbeddings { + 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)); + + OpenAIEmbeddings { + client, + executor, + rate_limit_count_rx, + rate_limit_count_tx, + } + } + + fn resolve_rate_limit(&self) { + let reset_time = *self.rate_limit_count_tx.lock().borrow(); + + if let Some(reset_time) = reset_time { + if Instant::now() >= reset_time { + *self.rate_limit_count_tx.lock().borrow_mut() = None + } + } + + log::trace!( + "resolving reset time: {:?}", + *self.rate_limit_count_tx.lock().borrow() + ); + } + + fn update_reset_time(&self, reset_time: Instant) { + let original_time = *self.rate_limit_count_tx.lock().borrow(); + + let updated_time = if let Some(original_time) = original_time { + if reset_time < original_time { + Some(reset_time) + } else { + Some(original_time) + } + } else { + Some(reset_time) + }; + + log::trace!("updating rate limit time: {:?}", updated_time); + + *self.rate_limit_count_tx.lock().borrow_mut() = updated_time; + } async fn send_request( &self, api_key: &str, @@ -179,6 +232,9 @@ impl EmbeddingProvider for OpenAIEmbeddings { 50000 } + fn rate_limit_expiration(&self) -> Option { + *self.rate_limit_count_rx.borrow() + } fn truncate(&self, span: &str) -> (String, usize) { let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span); let output = if tokens.len() > OPENAI_INPUT_LIMIT { @@ -203,6 +259,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { .ok_or_else(|| anyhow!("no api key"))?; let mut request_number = 0; + let mut rate_limiting = false; let mut request_timeout: u64 = 15; let mut response: Response; while request_number < MAX_RETRIES { @@ -229,6 +286,12 @@ impl EmbeddingProvider for OpenAIEmbeddings { response.usage.total_tokens ); + // If we complete a request successfully that was previously rate_limited + // resolve the rate limit + if rate_limiting { + self.resolve_rate_limit() + } + return Ok(response .data .into_iter() @@ -236,6 +299,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { .collect()); } StatusCode::TOO_MANY_REQUESTS => { + rate_limiting = true; let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; @@ -254,6 +318,10 @@ impl EmbeddingProvider for OpenAIEmbeddings { } }; + // If we've previously rate limited, increment the duration but not the count + let reset_time = Instant::now().add(delay_duration); + self.update_reset_time(reset_time); + log::trace!( "openai rate limiting: waiting {:?} until lifted", &delay_duration diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 0e18c42049..115bf5d7a8 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -91,10 +91,7 @@ pub fn init( let semantic_index = SemanticIndex::new( fs, db_file_path, - Arc::new(OpenAIEmbeddings { - client: http_client, - executor: cx.background(), - }), + Arc::new(OpenAIEmbeddings::new(http_client, cx.background())), language_registry, cx.clone(), ) @@ -113,7 +110,10 @@ pub fn init( pub enum SemanticIndexStatus { NotIndexed, Indexed, - Indexing { remaining_files: usize }, + Indexing { + remaining_files: usize, + rate_limit_expiry: Option, + }, } pub struct SemanticIndex { @@ -293,6 +293,7 @@ impl SemanticIndex { } else { SemanticIndexStatus::Indexing { remaining_files: project_state.pending_file_count_rx.borrow().clone(), + rate_limit_expiry: self.embedding_provider.rate_limit_expiration(), } } } else { diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index ffd8db8781..9035327b2e 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -21,7 +21,7 @@ use std::{ atomic::{self, AtomicUsize}, Arc, }, - time::SystemTime, + time::{Instant, SystemTime}, }; use unindent::Unindent; use util::RandomCharIter; @@ -1275,6 +1275,10 @@ impl EmbeddingProvider for FakeEmbeddingProvider { 200 } + fn rate_limit_expiration(&self) -> Option { + None + } + async fn embed_batch(&self, spans: Vec) -> Result> { self.embedding_count .fetch_add(spans.len(), atomic::Ordering::SeqCst); diff --git a/crates/storybook/src/theme.rs b/crates/storybook/src/theme.rs index 45327e1ffc..0a86a61499 100644 --- a/crates/storybook/src/theme.rs +++ b/crates/storybook/src/theme.rs @@ -1,7 +1,6 @@ use gpui2::{ - color::Hsla, - element::{Element, PaintContext}, - serde_json, AppContext, IntoElement, Vector2F, ViewContext, WindowContext, + color::Hsla, element::Element, serde_json, AppContext, IntoElement, Vector2F, ViewContext, + WindowContext, }; use serde::{de::Visitor, Deserialize, Deserializer}; use std::{collections::HashMap, fmt, marker::PhantomData}; @@ -162,7 +161,7 @@ impl> Element for Themed { parent_origin: Vector2F, layout: &gpui2::Layout, state: &mut Self::PaintState, - cx: &mut PaintContext, + cx: &mut ViewContext, ) where Self: Sized, { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 7892dfe70e..30dbccf455 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -10,9 +10,8 @@ use gpui::{ platform::{CursorStyle, MouseButton}, serde_json::json, text_layout::{Line, RunStyle}, - AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, - PaintContext, Quad, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle, - WindowContext, + AnyElement, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SizeConstraint, + TextLayoutCache, ViewContext, WeakModelHandle, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -527,7 +526,7 @@ impl Element for TerminalElement { &mut self, constraint: gpui::SizeConstraint, view: &mut TerminalView, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let settings = settings::get::(cx); let terminal_settings = settings::get::(cx); @@ -734,7 +733,7 @@ impl Element for TerminalElement { visible_bounds: RectF, layout: &mut Self::LayoutState, view: &mut TerminalView, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 104d181a7b..a12f9d3c3c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -283,7 +283,12 @@ impl TerminalView { pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext) { let menu_entries = vec![ ContextMenuItem::action("Clear", Clear), - ContextMenuItem::action("Close", pane::CloseActiveItem), + ContextMenuItem::action( + "Close", + pane::CloseActiveItem { + save_behavior: None, + }, + ), ]; self.context_menu.update(cx, |menu, cx| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 4e24c831f4..ea747b3a36 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -474,8 +474,14 @@ impl ItemHandle for ViewHandle { 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(), cx)) - .detach_and_log_err(cx); + pane.update(cx, |pane, cx| { + pane.close_item_by_id( + item.id(), + crate::SaveBehavior::PromptOnWrite, + cx, + ) + }) + .detach_and_log_err(cx); return; } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index d31f0d9749..90a1fdd3f2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -25,8 +25,8 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel}, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, - LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -43,6 +43,19 @@ use std::{ }; use theme::{Theme, ThemeSettings}; +#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum SaveBehavior { + /// ask before overwriting conflicting files (used by default with %s) + PromptOnConflict, + /// ask before writing any file that wouldn't be auto-saved (used by default with %w) + PromptOnWrite, + /// never prompt, write on conflict (used with vim's :w!) + SilentlyOverwrite, + /// skip all save-related behaviour (used with vim's :cq) + DontSave, +} + #[derive(Clone, Deserialize, PartialEq)] pub struct ActivateItem(pub usize); @@ -64,13 +77,17 @@ pub struct CloseItemsToTheRightById { pub pane: WeakViewHandle, } +#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +pub struct CloseActiveItem { + pub save_behavior: Option, +} + actions!( pane, [ ActivatePrevItem, ActivateNextItem, ActivateLastItem, - CloseActiveItem, CloseInactiveItems, CloseCleanItems, CloseItemsToTheLeft, @@ -86,7 +103,7 @@ actions!( ] ); -impl_actions!(pane, [ActivateItem]); +impl_actions!(pane, [ActivateItem, CloseActiveItem]); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -696,22 +713,29 @@ impl Pane { pub fn close_active_item( &mut self, - _: &CloseActiveItem, + 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, cx)) + Some(self.close_item_by_id( + active_item_id, + action.save_behavior.unwrap_or(SaveBehavior::PromptOnWrite), + cx, + )) } pub fn close_item_by_id( &mut self, item_id_to_close: usize, + save_behavior: SaveBehavior, cx: &mut ViewContext, ) -> Task> { - self.close_items(cx, move |view_id| view_id == item_id_to_close) + self.close_items(cx, save_behavior, move |view_id| { + view_id == item_id_to_close + }) } pub fn close_inactive_items( @@ -724,7 +748,11 @@ impl Pane { } let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items(cx, move |item_id| item_id != active_item_id)) + Some( + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_id != active_item_id + }), + ) } pub fn close_clean_items( @@ -737,7 +765,11 @@ impl Pane { .filter(|item| !item.is_dirty(cx)) .map(|item| item.id()) .collect(); - Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id))) + Some( + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_ids.contains(&item_id) + }), + ) } pub fn close_items_to_the_left( @@ -762,7 +794,9 @@ impl Pane { .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - self.close_items(cx, move |item_id| item_ids.contains(&item_id)) + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_ids.contains(&item_id) + }) } pub fn close_items_to_the_right( @@ -788,7 +822,9 @@ impl Pane { .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - self.close_items(cx, move |item_id| item_ids.contains(&item_id)) + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_ids.contains(&item_id) + }) } pub fn close_all_items( @@ -800,12 +836,13 @@ impl Pane { return None; } - Some(self.close_items(cx, move |_| true)) + Some(self.close_items(cx, SaveBehavior::PromptOnWrite, |_| true)) } pub fn close_items( &mut self, cx: &mut ViewContext, + save_behavior: SaveBehavior, should_close: impl 'static + Fn(usize) -> bool, ) -> Task> { // Find the items to close. @@ -858,8 +895,15 @@ impl Pane { .any(|id| saved_project_items_ids.insert(*id)); if should_save - && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) - .await? + && !Self::save_item( + project.clone(), + &pane, + item_ix, + &*item, + save_behavior, + &mut cx, + ) + .await? { break; } @@ -954,13 +998,17 @@ impl Pane { pane: &WeakViewHandle, item_ix: usize, item: &dyn ItemHandle, - should_prompt_for_save: bool, + save_behavior: SaveBehavior, 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?"; const DIRTY_MESSAGE: &str = "This file contains unsaved edits. Do you want to save it?"; + if save_behavior == SaveBehavior::DontSave { + return Ok(true); + } + let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| { ( item.has_conflict(cx), @@ -971,18 +1019,22 @@ impl Pane { }); 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), + if save_behavior == SaveBehavior::SilentlyOverwrite { + pane.update(cx, |_, cx| item.save(project, cx))?.await?; + } else { + 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 || is_singleton) { let will_autosave = cx.read(|cx| { @@ -991,7 +1043,7 @@ impl Pane { AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange ) && Self::can_autosave_item(&*item, cx) }); - let should_save = if should_prompt_for_save && !will_autosave { + let should_save = if save_behavior == SaveBehavior::PromptOnWrite && !will_autosave { let mut answer = pane.update(cx, |pane, cx| { pane.activate_item(item_ix, true, true, cx); cx.prompt( @@ -1113,7 +1165,12 @@ impl Pane { AnchorCorner::TopLeft, if is_active_item { vec![ - ContextMenuItem::action("Close Active Item", CloseActiveItem), + ContextMenuItem::action( + "Close Active Item", + CloseActiveItem { + save_behavior: None, + }, + ), ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), @@ -1128,8 +1185,12 @@ impl Pane { move |cx| { if let Some(pane) = pane.upgrade(cx) { pane.update(cx, |pane, cx| { - pane.close_item_by_id(target_item_id, cx) - .detach_and_log_err(cx); + pane.close_item_by_id( + target_item_id, + SaveBehavior::PromptOnWrite, + cx, + ) + .detach_and_log_err(cx); }) } } @@ -1278,7 +1339,12 @@ impl Pane { .on_click(MouseButton::Middle, { let item_id = item.id(); move |_, pane, cx| { - pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); + pane.close_item_by_id( + item_id, + SaveBehavior::PromptOnWrite, + cx, + ) + .detach_and_log_err(cx); } }) .on_down( @@ -1486,7 +1552,8 @@ impl Pane { 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, cx).detach_and_log_err(cx); + pane.close_item_by_id(item_id, SaveBehavior::PromptOnWrite, cx) + .detach_and_log_err(cx); }); } }); @@ -1999,7 +2066,7 @@ impl Element for PaneBackdrop { &mut self, constraint: gpui::SizeConstraint, view: &mut V, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); (size, ()) @@ -2011,7 +2078,7 @@ impl Element for PaneBackdrop { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut V, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let background = theme::current(cx).editor.background; @@ -2087,7 +2154,14 @@ mod tests { let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); pane.update(cx, |pane, cx| { - assert!(pane.close_active_item(&CloseActiveItem, cx).is_none()) + assert!(pane + .close_active_item( + &CloseActiveItem { + save_behavior: None + }, + cx + ) + .is_none()) }); } @@ -2337,31 +2411,59 @@ mod tests { 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, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: 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, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A*"], cx); } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 9747bda2d5..bffdce0f3e 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -594,8 +594,8 @@ mod element { json::{self, ToJson}, platform::{CursorStyle, MouseButton}, scene::MouseDrag, - AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion, - PaintContext, RectFExt, SizeConstraint, Vector2FExt, ViewContext, + AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt, + SizeConstraint, Vector2FExt, ViewContext, }; use crate::{ @@ -641,7 +641,7 @@ mod element { remaining_flex: &mut f32, cross_axis_max: &mut f32, view: &mut Workspace, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) { let flexes = self.flexes.borrow(); let cross_axis = self.axis.invert(); @@ -789,7 +789,7 @@ mod element { &mut self, constraint: SizeConstraint, view: &mut Workspace, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { debug_assert!(self.children.len() == self.flexes.borrow().len()); @@ -855,7 +855,7 @@ mod element { visible_bounds: RectF, remaining_space: &mut Self::LayoutState, view: &mut Workspace, - cx: &mut PaintContext, + 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(); diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index c05b93ff95..b62dae2114 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -8,8 +8,8 @@ use gpui::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SizeConstraint, Subscription, - View, ViewContext, ViewHandle, WindowContext, + AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle, + WindowContext, }; pub trait StatusItemView: View { @@ -208,7 +208,7 @@ impl Element for StatusBarElement { &mut self, mut constraint: SizeConstraint, view: &mut StatusBar, - cx: &mut LayoutContext, + cx: &mut ViewContext, ) -> (Vector2F, Self::LayoutState) { let max_width = constraint.max.x(); constraint.min = vec2f(0., constraint.min.y()); @@ -230,7 +230,7 @@ impl Element for StatusBarElement { visible_bounds: RectF, _: &mut Self::LayoutState, view: &mut StatusBar, - cx: &mut PaintContext, + cx: &mut ViewContext, ) -> Self::PaintState { let origin_y = bounds.upper_right().y(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 953a529281..7d0f6db917 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1308,13 +1308,15 @@ impl Workspace { } Ok(this - .update(&mut cx, |this, cx| this.save_all_internal(true, cx))? + .update(&mut cx, |this, cx| { + this.save_all_internal(SaveBehavior::PromptOnWrite, cx) + })? .await?) }) } fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext) -> Option>> { - let save_all = self.save_all_internal(false, cx); + let save_all = self.save_all_internal(SaveBehavior::PromptOnConflict, cx); Some(cx.foreground().spawn(async move { save_all.await?; Ok(()) @@ -1323,7 +1325,7 @@ impl Workspace { fn save_all_internal( &mut self, - should_prompt_to_save: bool, + save_behaviour: SaveBehavior, cx: &mut ViewContext, ) -> Task> { if self.project.read(cx).is_read_only() { @@ -1358,7 +1360,7 @@ impl Workspace { &pane, ix, &*item, - should_prompt_to_save, + save_behaviour, &mut cx, ) .await? @@ -4358,7 +4360,9 @@ mod tests { let item1_id = item1.id(); let item3_id = item3.id(); let item4_id = item4.id(); - pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id)) + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| { + [item1_id, item3_id, item4_id].contains(&id) + }) }); cx.foreground().run_until_parked(); @@ -4493,7 +4497,9 @@ mod tests { // 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, |_| true)); + let close = left_pane.update(cx, |pane, cx| { + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true) + }); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { assert_eq!( @@ -4609,9 +4615,11 @@ mod tests { item.is_dirty = true; }); - pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id) + }) + .await + .unwrap(); assert!(!window.has_pending_prompt(cx)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); @@ -4630,8 +4638,9 @@ mod tests { 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, move |id| id == item_id)); + let _close_items = pane.update(cx, |pane, cx| { + pane.close_items(cx, SaveBehavior::PromptOnWrite, 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)); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e102a66519..1d014197e1 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -132,6 +132,7 @@ tree-sitter-racket.workspace = true tree-sitter-yaml.workspace = true tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true +tree-sitter-nu.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 3fbb5aa14f..0b1fa750c0 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -170,6 +170,7 @@ pub fn init(languages: Arc, node_runtime: Arc language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); + language("nu", tree_sitter_nu::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/nu/brackets.scm b/crates/zed/src/languages/nu/brackets.scm new file mode 100644 index 0000000000..7ede7a6192 --- /dev/null +++ b/crates/zed/src/languages/nu/brackets.scm @@ -0,0 +1,4 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) +(parameter_pipes "|" @open "|" @close) diff --git a/crates/zed/src/languages/nu/config.toml b/crates/zed/src/languages/nu/config.toml new file mode 100644 index 0000000000..d382b0705a --- /dev/null +++ b/crates/zed/src/languages/nu/config.toml @@ -0,0 +1,9 @@ +name = "Nu" +path_suffixes = ["nu"] +line_comment = "# " +autoclose_before = ";:.,=}])>` \n\t\"" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/nu/highlights.scm b/crates/zed/src/languages/nu/highlights.scm new file mode 100644 index 0000000000..97f46d3879 --- /dev/null +++ b/crates/zed/src/languages/nu/highlights.scm @@ -0,0 +1,302 @@ +;;; --- +;;; keywords +[ + "def" + "def-env" + "alias" + "export-env" + "export" + "extern" + "module" + + "let" + "let-env" + "mut" + "const" + + "hide-env" + + "source" + "source-env" + + "overlay" + "register" + + "loop" + "while" + "error" + + "do" + "if" + "else" + "try" + "catch" + "match" + + "break" + "continue" + "return" + +] @keyword + +(hide_mod "hide" @keyword) +(decl_use "use" @keyword) + +(ctrl_for + "for" @keyword + "in" @keyword +) +(overlay_list "list" @keyword) +(overlay_hide "hide" @keyword) +(overlay_new "new" @keyword) +(overlay_use + "use" @keyword + "as" @keyword +) +(ctrl_error "make" @keyword) + +;;; --- +;;; literals +(val_number) @constant +(val_duration + unit: [ + "ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk" + ] @variable +) +(val_filesize + unit: [ + "b" "B" + + "kb" "kB" "Kb" "KB" + "mb" "mB" "Mb" "MB" + "gb" "gB" "Gb" "GB" + "tb" "tB" "Tb" "TB" + "pb" "pB" "Pb" "PB" + "eb" "eB" "Eb" "EB" + "zb" "zB" "Zb" "ZB" + + "kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB" + "mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB" + "gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB" + "tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB" + "pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB" + "eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB" + "zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB" + ] @variable +) +(val_binary + [ + "0b" + "0o" + "0x" + ] @constant + "[" @punctuation.bracket + digit: [ + "," @punctuation.delimiter + (hex_digit) @constant + ] + "]" @punctuation.bracket +) @constant +(val_bool) @constant.builtin +(val_nothing) @constant.builtin +(val_string) @string +(val_date) @constant +(inter_escape_sequence) @constant +(escape_sequence) @constant +(val_interpolated [ + "$\"" + "$\'" + "\"" + "\'" +] @string) +(unescaped_interpolated_content) @string +(escaped_interpolated_content) @string +(expr_interpolated ["(" ")"] @variable) + +;;; --- +;;; operators +(expr_binary [ + "+" + "-" + "*" + "/" + "mod" + "//" + "++" + "**" + "==" + "!=" + "<" + "<=" + ">" + ">=" + "=~" + "!~" + "and" + "or" + "xor" + "bit-or" + "bit-xor" + "bit-and" + "bit-shl" + "bit-shr" + "in" + "not-in" + "starts-with" + "ends-with" +] @operator) + +(expr_binary opr: ([ + "and" + "or" + "xor" + "bit-or" + "bit-xor" + "bit-and" + "bit-shl" + "bit-shr" + "in" + "not-in" + "starts-with" + "ends-with" +]) @keyword) + +(where_command [ + "+" + "-" + "*" + "/" + "mod" + "//" + "++" + "**" + "==" + "!=" + "<" + "<=" + ">" + ">=" + "=~" + "!~" + "and" + "or" + "xor" + "bit-or" + "bit-xor" + "bit-and" + "bit-shl" + "bit-shr" + "in" + "not-in" + "starts-with" + "ends-with" +] @operator) + +(assignment [ + "=" + "+=" + "-=" + "*=" + "/=" + "++=" +] @operator) + +(expr_unary ["not" "-"] @operator) + +(val_range [ + ".." + "..=" + "..<" +] @operator) + +["=>" "=" "|"] @operator + +[ + "o>" "out>" + "e>" "err>" + "e+o>" "err+out>" + "o+e>" "out+err>" +] @special + +;;; --- +;;; punctuation +[ + "," + ";" +] @punctuation.delimiter + +(param_short_flag "-" @punctuation.delimiter) +(param_long_flag ["--"] @punctuation.delimiter) +(long_flag ["--"] @punctuation.delimiter) +(param_rest "..." @punctuation.delimiter) +(param_type [":"] @punctuation.special) +(param_value ["="] @punctuation.special) +(param_cmd ["@"] @punctuation.special) +(param_opt ["?"] @punctuation.special) + +[ + "(" ")" + "{" "}" + "[" "]" +] @punctuation.bracket + +(val_record + (record_entry ":" @punctuation.delimiter)) +;;; --- +;;; identifiers +(param_rest + name: (_) @variable) +(param_opt + name: (_) @variable) +(parameter + param_name: (_) @variable) +(param_cmd + (cmd_identifier) @string) +(param_long_flag) @variable +(param_short_flag) @variable + +(short_flag) @variable +(long_flag) @variable + +(scope_pattern [(wild_card) @function]) + +(cmd_identifier) @function + +(command + "^" @punctuation.delimiter + head: (_) @function +) + +"where" @function + +(path + ["." "?"] @punctuation.delimiter +) @variable + +(val_variable + "$" @operator + [ + (identifier) @variable + "in" @type.builtin + "nu" @type.builtin + "env" @type.builtin + "nothing" @type.builtin + ] ; If we have a special styling, use it here +) +;;; --- +;;; types +(flat_type) @type.builtin +(list_type + "list" @type + ["<" ">"] @punctuation.bracket +) +(collection_type + ["record" "table"] @type + "<" @punctuation.bracket + key: (_) @variable + ["," ":"] @punctuation.delimiter + ">" @punctuation.bracket +) + +(shebang) @comment +(comment) @comment diff --git a/crates/zed/src/languages/nu/indents.scm b/crates/zed/src/languages/nu/indents.scm new file mode 100644 index 0000000000..112b414aa4 --- /dev/null +++ b/crates/zed/src/languages/nu/indents.scm @@ -0,0 +1,3 @@ +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 22a260b588..6b5f7b3a35 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -41,7 +41,12 @@ pub fn menus() -> Vec> { MenuItem::action("Save", workspace::Save), MenuItem::action("Save As…", workspace::SaveAs), MenuItem::action("Save All", workspace::SaveAll), - MenuItem::action("Close Editor", workspace::CloseActiveItem), + MenuItem::action( + "Close Editor", + workspace::CloseActiveItem { + save_behavior: None, + }, + ), MenuItem::action("Close Window", workspace::CloseWindow), ], }, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 424bce60f2..f12dc8a98b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -733,7 +733,7 @@ mod tests { use theme::{ThemeRegistry, ThemeSettings}; use workspace::{ item::{Item, ItemHandle}, - open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle, + open_new, open_paths, pane, NewFile, SaveBehavior, SplitDirection, WorkspaceHandle, }; #[gpui::test] @@ -1495,7 +1495,12 @@ mod tests { pane2_item.downcast::().unwrap().downgrade() }); - cx.dispatch_action(window.into(), workspace::CloseActiveItem); + cx.dispatch_action( + window.into(), + workspace::CloseActiveItem { + save_behavior: None, + }, + ); cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, _| { @@ -1503,7 +1508,12 @@ mod tests { assert_eq!(workspace.active_pane(), &pane_1); }); - cx.dispatch_action(window.into(), workspace::CloseActiveItem); + cx.dispatch_action( + window.into(), + workspace::CloseActiveItem { + save_behavior: None, + }, + ); cx.foreground().run_until_parked(); window.simulate_prompt_answer(1, cx); cx.foreground().run_until_parked(); @@ -1661,7 +1671,7 @@ mod tests { pane.update(cx, |pane, cx| { let editor3_id = editor3.id(); drop(editor3); - pane.close_item_by_id(editor3_id, cx) + pane.close_item_by_id(editor3_id, SaveBehavior::PromptOnWrite, cx) }) .await .unwrap(); @@ -1696,7 +1706,7 @@ mod tests { pane.update(cx, |pane, cx| { let editor2_id = editor2.id(); drop(editor2); - pane.close_item_by_id(editor2_id, cx) + pane.close_item_by_id(editor2_id, SaveBehavior::PromptOnWrite, cx) }) .await .unwrap(); @@ -1852,24 +1862,32 @@ mod tests { assert_eq!(active_path(&workspace, cx), Some(file4.clone())); // Close all the pane items in some arbitrary order. - pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file1_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file4.clone())); - pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file4_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file2_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file3_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), None); // Reopen all the closed items, ensuring they are reopened in the same order