Merge branch 'main' into nate/storybook-components

Nate Butler created

Change summary

Cargo.lock                                            |  10 
Cargo.toml                                            |   1 
README.md                                             |  37 +
assets/keymaps/vim.json                               |  12 
crates/collab_ui/src/collab_titlebar_item.rs          |   8 
crates/collab_ui/src/face_pile.rs                     |   6 
crates/editor/src/element.rs                          |  36 
crates/file_finder/src/file_finder.rs                 |   9 
crates/gpui/examples/corner_radii.rs                  |   4 
crates/gpui/src/app.rs                                | 177 ------
crates/gpui/src/app/window.rs                         |  11 
crates/gpui/src/elements.rs                           |  48 -
crates/gpui/src/elements/align.rs                     |   6 
crates/gpui/src/elements/canvas.rs                    |  10 
crates/gpui/src/elements/clipped.rs                   |   6 
crates/gpui/src/elements/component.rs                 |   8 
crates/gpui/src/elements/constrained_box.rs           |  13 
crates/gpui/src/elements/container.rs                 |   6 
crates/gpui/src/elements/empty.rs                     |   6 
crates/gpui/src/elements/expanded.rs                  |   6 
crates/gpui/src/elements/flex.rs                      |  15 
crates/gpui/src/elements/hook.rs                      |   6 
crates/gpui/src/elements/image.rs                     |   6 
crates/gpui/src/elements/keystroke_label.rs           |   4 
crates/gpui/src/elements/label.rs                     |   6 
crates/gpui/src/elements/list.rs                      |  26 
crates/gpui/src/elements/mouse_event_handler.rs       |  14 
crates/gpui/src/elements/overlay.rs                   |   7 
crates/gpui/src/elements/resizable.rs                 |  11 
crates/gpui/src/elements/stack.rs                     |   6 
crates/gpui/src/elements/svg.rs                       |   7 
crates/gpui/src/elements/text.rs                      |  10 
crates/gpui/src/elements/tooltip.rs                   |   7 
crates/gpui/src/elements/uniform_list.rs              |   6 
crates/gpui/src/scene.rs                              |   2 
crates/gpui2/src/adapter.rs                           |  14 
crates/gpui2/src/element.rs                           |   9 
crates/gpui2/src/elements/div.rs                      |   7 
crates/gpui2/src/elements/hoverable.rs                |   3 
crates/gpui2/src/elements/img.rs                      |   2 
crates/gpui2/src/elements/pressable.rs                |   3 
crates/gpui2/src/elements/svg.rs                      |   2 
crates/gpui2/src/elements/text.rs                     |   3 
crates/gpui2/src/gpui2.rs                             |   1 
crates/gpui2/src/interactive.rs                       |   4 
crates/gpui2/src/paint_context.rs                     |  50 --
crates/gpui2/src/style.rs                             |   6 
crates/gpui2/src/view_context.rs                      |  34 +
crates/gpui2_macros/src/derive_element.rs             |   2 
crates/gpui_macros/src/gpui_macros.rs                 |   4 
crates/live_kit_client/LiveKitBridge/Package.resolved |   4 
crates/search/src/project_search.rs                   |  45 +
crates/semantic_index/src/embedding.rs                |  70 ++
crates/semantic_index/src/semantic_index.rs           |  11 
crates/semantic_index/src/semantic_index_tests.rs     |   6 
crates/storybook/src/theme.rs                         |   7 
crates/terminal_view/src/terminal_element.rs          |   9 
crates/terminal_view/src/terminal_view.rs             |   7 
crates/workspace/src/item.rs                          |  10 
crates/workspace/src/pane.rs                          | 206 ++++++--
crates/workspace/src/pane_group.rs                    |  10 
crates/workspace/src/status_bar.rs                    |   8 
crates/workspace/src/workspace.rs                     |  31 
crates/zed/Cargo.toml                                 |   1 
crates/zed/src/languages.rs                           |   1 
crates/zed/src/languages/nu/brackets.scm              |   4 
crates/zed/src/languages/nu/config.toml               |   9 
crates/zed/src/languages/nu/highlights.scm            | 302 +++++++++++++
crates/zed/src/languages/nu/indents.scm               |   3 
crates/zed/src/menus.rs                               |   7 
crates/zed/src/zed.rs                                 |  52 +
71 files changed, 966 insertions(+), 534 deletions(-)

Detailed changes

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",

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" }

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:

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",

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<CollabTitlebarItem> for AvatarRibbon {
         &mut self,
         constraint: gpui::SizeConstraint,
         _: &mut CollabTitlebarItem,
-        _: &mut LayoutContext<CollabTitlebarItem>,
+        _: &mut ViewContext<CollabTitlebarItem>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
@@ -1176,7 +1176,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
         _: RectF,
         _: &mut Self::LayoutState,
         _: &mut CollabTitlebarItem,
-        cx: &mut PaintContext<CollabTitlebarItem>,
+        cx: &mut ViewContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let mut path = PathBuilder::new();
         path.reset(bounds.lower_left());

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<V: View> {
@@ -32,7 +32,7 @@ impl<V: View> Element<V> for FacePile<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
 
@@ -57,7 +57,7 @@ impl<V: View> Element<V> for FacePile<V> {
         visible_bounds: RectF,
         _layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 

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<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) {
         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<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) {
         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<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) {
         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<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> (f32, Vec<BlockLayout>) {
         let mut block_id = 0;
         let scroll_x = snapshot.scroll_anchor.offset.x();
@@ -2092,7 +2092,7 @@ impl Element<Editor> for EditorElement {
         &mut self,
         constraint: SizeConstraint,
         editor: &mut Editor,
-        cx: &mut LayoutContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         if size.x().is_infinite() {
@@ -2570,7 +2570,7 @@ impl Element<Editor> for EditorElement {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         editor: &mut Editor,
-        cx: &mut PaintContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> 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,
             )
         });
 

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();

crates/gpui/examples/corner_radii.rs ๐Ÿ”—

@@ -42,7 +42,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
         &mut self,
         constraint: gpui::SizeConstraint,
         _: &mut V,
-        _: &mut gpui::LayoutContext<V>,
+        _: &mut gpui::ViewContext<V>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
@@ -53,7 +53,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
         _: pathfinder_geometry::rect::RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut gpui::PaintContext<V>,
+        cx: &mut gpui::ViewContext<V>,
     ) -> Self::PaintState {
         cx.scene().push_quad(Quad {
             bounds,

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<F, R>(&mut self, clip_bounds: Option<RectF>, 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<V: View> ViewContext<'_, '_, V> {
@@ -3495,151 +3505,6 @@ impl<V> 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<V> DerefMut for LayoutContext<'_, '_, '_, V> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.view_context
-    }
-}
-
-impl<V> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.view_context, f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.view_context, f)
-    }
-}
-
-impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
-    type Result<T> = T;
-
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
-        BorrowWindowContext::read_window(&*self.view_context, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
-    }
-
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window: AnyWindowHandle,
-        f: F,
-    ) -> T {
-        BorrowWindowContext::update_window(&mut *self.view_context, window, f)
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        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<F, R>(&mut self, clip_bounds: Option<RectF>, 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<V> DerefMut for PaintContext<'_, '_, '_, V> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.view_context
-    }
-}
-
-impl<V> BorrowAppContext for PaintContext<'_, '_, '_, V> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.view_context, f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.view_context, f)
-    }
-}
-
-impl<V> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
-    type Result<T> = T;
-
-    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&WindowContext) -> T,
-    {
-        BorrowWindowContext::read_window(self.view_context, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window_optional(self.view_context, window, f)
-    }
-
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&mut WindowContext) -> T,
-    {
-        BorrowWindowContext::update_window(self.view_context, window, f)
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        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);
             });
         });
 

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<V: 'static> Element<V> for ChildView {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (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<V: 'static> Element<V> for ChildView {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
             rendered_view

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<V: 'static>: 'static {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState);
 
     fn paint(
@@ -68,7 +68,7 @@ pub trait Element<V: 'static>: 'static {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState;
 
     fn rect_for_text_range(
@@ -259,7 +259,7 @@ trait AnyElementState<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Vector2F;
 
     fn paint(
@@ -267,7 +267,7 @@ trait AnyElementState<V> {
         origin: Vector2F,
         visible_bounds: RectF,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     );
 
     fn rect_for_text_range(
@@ -310,7 +310,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Vector2F {
         let result;
         *self = match mem::take(self) {
@@ -347,7 +347,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
         origin: Vector2F,
         visible_bounds: RectF,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         *self = match mem::take(self) {
             ElementState::PostLayout {
@@ -357,13 +357,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
                 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<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
                 ..
             } => {
                 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<V> AnyElement<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Vector2F {
         self.state.layout(constraint, view, cx)
     }
@@ -520,7 +508,7 @@ impl<V> AnyElement<V> {
         origin: Vector2F,
         visible_bounds: RectF,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         self.state.paint(origin, visible_bounds, view, cx);
     }
@@ -570,7 +558,7 @@ impl<V: 'static> Element<V> for AnyElement<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.layout(constraint, view, cx);
         (size, ())
@@ -582,7 +570,7 @@ impl<V: 'static> Element<V> for AnyElement<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         self.paint(bounds.origin(), visible_bounds, view, cx);
     }
@@ -659,10 +647,7 @@ impl<V: View> AnyRootElement for RootElement<V> {
             .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<V: View> AnyRootElement for RootElement<V> {
             .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(())
         })
     }

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<V: 'static> Element<V> for Align<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         constraint.min = Vector2F::zero();
@@ -68,7 +68,7 @@ impl<V: 'static> Element<V> for Align<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let my_center = bounds.size() / 2.;
         let my_target = my_center + my_center * self.alignment;

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<V, F>(F, PhantomData<V>);
 
 impl<V, F> Canvas<V, F>
 where
-    F: FnMut(RectF, RectF, &mut V, &mut PaintContext<V>),
+    F: FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
 {
     pub fn new(f: F) -> Self {
         Self(f, PhantomData)
@@ -24,7 +24,7 @@ where
 
 impl<V: 'static, F> Element<V> for Canvas<V, F>
 where
-    F: 'static + FnMut(RectF, RectF, &mut V, &mut PaintContext<V>),
+    F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
 {
     type LayoutState = ();
     type PaintState = ();
@@ -33,7 +33,7 @@ where
         &mut self,
         constraint: crate::SizeConstraint,
         _: &mut V,
-        _: &mut crate::LayoutContext<V>,
+        _: &mut crate::ViewContext<V>,
     ) -> (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<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         self.0(bounds, visible_bounds, view, cx)
     }

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<V> {
     child: AnyElement<V>,
@@ -23,7 +23,7 @@ impl<V: 'static> Element<V> for Clipped<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }
@@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Clipped<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         cx.scene().push_layer(Some(bounds));
         let state = self.child.paint(bounds.origin(), visible_bounds, view, cx);

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<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (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<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         self.element
             .as_mut()

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<V> {
@@ -15,7 +15,7 @@ pub struct ConstrainedBox<V> {
 
 pub enum Constraint<V> {
     Static(SizeConstraint),
-    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
+    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
 }
 
 impl<V> ToJson for Constraint<V> {
@@ -37,8 +37,7 @@ impl<V: 'static> ConstrainedBox<V> {
 
     pub fn dynamically(
         mut self,
-        constraint: impl 'static
-            + FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
+        constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
     ) -> Self {
         self.constraint = Constraint::Dynamic(Box::new(constraint));
         self
@@ -120,7 +119,7 @@ impl<V: 'static> ConstrainedBox<V> {
         &mut self,
         input_constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> SizeConstraint {
         match &mut self.constraint {
             Constraint::Static(constraint) => *constraint,
@@ -139,7 +138,7 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
         &mut self,
         mut parent_constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (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<V: 'static> Element<V> for ConstrainedBox<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         cx.scene().push_layer(Some(visible_bounds));
         self.child.paint(bounds.origin(), visible_bounds, view, cx);

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<V: 'static> Element<V> for Container<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size_buffer = self.margin_size() + self.padding_size();
         if !self.style.border.overlay {
@@ -391,7 +391,7 @@ impl<V: 'static> Element<V> for Container<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let quad_bounds = RectF::from_points(
             bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),

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<V: 'static> Element<V> for Empty {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        _: &mut LayoutContext<V>,
+        _: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() && !self.collapsed {
             constraint.max.x()
@@ -56,7 +56,7 @@ impl<V: 'static> Element<V> for Empty {
         _: RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut PaintContext<V>,
+        _: &mut ViewContext<V>,
     ) -> Self::PaintState {
     }
 

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<V: 'static> Element<V> for Expanded<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if self.full_width {
             constraint.min.set_x(constraint.max.x());
@@ -60,7 +60,7 @@ impl<V: 'static> Element<V> for Expanded<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         self.child.paint(bounds.origin(), visible_bounds, view, cx);
     }

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<V: 'static> Flex<V> {
         remaining_flex: &mut f32,
         cross_axis_max: &mut f32,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let cross_axis = self.axis.invert();
         for child in self.children.iter_mut() {
@@ -136,7 +135,7 @@ impl<V: 'static> Element<V> for Flex<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (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<V: 'static> Element<V> for Flex<V> {
         }
 
         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<V: 'static> Element<V> for Flex<V> {
         visible_bounds: RectF,
         remaining_space: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
@@ -442,7 +441,7 @@ impl<V: 'static> Element<V> for FlexItem<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         (size, ())
@@ -454,7 +453,7 @@ impl<V: 'static> Element<V> for FlexItem<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         self.child.paint(bounds.origin(), visible_bounds, view, cx)
     }

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<V> {
@@ -36,7 +36,7 @@ impl<V: 'static> Element<V> for Hook<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (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<V: 'static> Element<V> for Hook<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         self.child.paint(bounds.origin(), visible_bounds, view, cx);
     }

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<V: 'static> Element<V> for Image {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let data = match &self.source {
             ImageSource::Path(path) => match cx.asset_cache.png(path) {
@@ -95,7 +95,7 @@ impl<V: 'static> Element<V> for Image {
         _: RectF,
         layout: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         if let Some(data) = layout {
             cx.scene().push_image(scene::Image {

crates/gpui/src/elements/keystroke_label.rs ๐Ÿ”—

@@ -39,7 +39,7 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, AnyElement<V>) {
         let mut element = if let Some(keystrokes) =
             cx.keystrokes_for_action(self.view_id, self.action.as_ref())
@@ -65,7 +65,7 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
         visible_bounds: RectF,
         element: &mut AnyElement<V>,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         element.paint(bounds.origin(), visible_bounds, view, cx);
     }

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<V: 'static> Element<V> for Label {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let runs = self.compute_runs();
         let line = cx.text_layout_cache().layout_str(
@@ -162,7 +162,7 @@ impl<V: 'static> Element<V> for Label {
         visible_bounds: RectF,
         line: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
         line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)

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<V: 'static> Element<V> for List<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let state = &mut *self.state.0.borrow_mut();
         let size = constraint.max;
@@ -253,7 +253,7 @@ impl<V: 'static> Element<V> for List<V> {
         visible_bounds: RectF,
         scroll_top: &mut ListOffset,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
         cx.scene().push_layer(Some(visible_bounds));
@@ -449,7 +449,7 @@ impl<V: 'static> StateInner<V> {
         existing_element: Option<&ListItem<V>>,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Option<Rc<RefCell<AnyElement<V>>>> {
         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<V>,
+            _: &mut ViewContext<V>,
         ) -> (Vector2F, ()) {
             (self.size, ())
         }
 
-        fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut PaintContext<V>) {
+        fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext<V>) {
             unimplemented!()
         }
 

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<V: 'static> Element<V> for MouseEventHandler<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }
@@ -281,13 +281,13 @@ impl<V: 'static> Element<V> for MouseEventHandler<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> 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);

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<V: 'static> Element<V> for Overlay<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let constraint = if self.anchor_position.is_some() {
             SizeConstraint::new(Vector2F::zero(), cx.window_size())
@@ -142,7 +141,7 @@ impl<V: 'static> Element<V> for Overlay<V> {
         _: RectF,
         size: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let (anchor_position, mut bounds) = match self.position_mode {
             OverlayPositionMode::Window => {

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<V: 'static> Element<V> for Resizable<V> {
         &mut self,
         constraint: crate::SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), constraint)
     }
@@ -116,7 +115,7 @@ impl<V: 'static> Element<V> for Resizable<V> {
         visible_bounds: pathfinder_geometry::rect::RectF,
         constraint: &mut SizeConstraint,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         cx.scene().push_stacking_context(None, None);
 
@@ -241,7 +240,7 @@ impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
         &mut self,
         constraint: crate::SizeConstraint,
         view: &mut V,
-        cx: &mut crate::LayoutContext<V>,
+        cx: &mut crate::ViewContext<V>,
     ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }
@@ -252,7 +251,7 @@ impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
         visible_bounds: pathfinder_geometry::rect::RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut crate::PaintContext<V>,
+        cx: &mut crate::ViewContext<V>,
     ) -> Self::PaintState {
         cx.update_default_global::<ProviderMap, _, _>(|map, _| {
             map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));

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<V: 'static> Element<V> for Stack<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.min;
         let mut children = self.children.iter_mut();
@@ -56,7 +56,7 @@ impl<V: 'static> Element<V> for Stack<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         for child in &mut self.children {
             cx.scene().push_layer(None);

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<V: 'static> Element<V> for Svg {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         match cx.asset_cache.svg(&self.path) {
             Ok(tree) => {
@@ -73,7 +72,7 @@ impl<V: 'static> Element<V> for Svg {
         _visible_bounds: RectF,
         svg: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         if let Some(svg) = svg.clone() {
             cx.scene().push_icon(scene::Icon {

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<V: 'static> Element<V> for Text {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         // Convert the string and highlight ranges into an iterator of highlighted chunks.
 
@@ -170,7 +169,7 @@ impl<V: 'static> Element<V> for Text {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> 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);

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<V: 'static> Element<V> for Tooltip<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {
@@ -208,7 +207,7 @@ impl<V: 'static> Element<V> for Tooltip<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         self.child.paint(bounds.origin(), visible_bounds, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {

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<V: 'static> Element<V> for UniformList<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if constraint.max.y().is_infinite() {
             unimplemented!(
@@ -276,7 +276,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
 

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 {

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<V: 'static> gpui::Element<V> for AdapterElement<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut gpui::LayoutContext<V>,
+        cx: &mut gpui::ViewContext<V>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         cx.push_layout_engine(LayoutEngine::new());
 
@@ -40,13 +40,13 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
         _visible_bounds: RectF,
         layout_data: &mut Option<(LayoutEngine, LayoutId)>,
         view: &mut V,
-        legacy_cx: &mut gpui::PaintContext<V>,
+        cx: &mut gpui::ViewContext<V>,
     ) -> 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());
     }
 

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<V: 'static>: 'static + IntoElement<V> {
         parent_origin: Vector2F,
         layout: &Layout,
         state: &mut Self::PaintState,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) where
         Self: Sized;
 
@@ -40,7 +39,7 @@ pub trait Element<V: 'static>: 'static + IntoElement<V> {
 /// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
 trait AnyStatefulElement<V> {
     fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId>;
-    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>);
+    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>);
 }
 
 /// A wrapper around an element that stores its layout state.
@@ -105,7 +104,7 @@ impl<V, E: Element<V>> AnyStatefulElement<V> for StatefulElement<V, E> {
         result
     }
 
-    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>) {
+    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
         self.phase = match std::mem::take(&mut self.phase) {
             ElementPhase::PostLayout {
                 layout_id,
@@ -149,7 +148,7 @@ impl<V> AnyElement<V> {
         self.0.layout(view, cx)
     }
 
-    pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>) {
+    pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
         self.0.paint(view, parent_origin, cx)
     }
 }

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<V: 'static> Element<V> for Div<V> {
         parent_origin: Vector2F,
         layout: &Layout,
         child_layouts: &mut Vec<LayoutId>,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) where
         Self: Sized,
     {
@@ -167,7 +166,7 @@ impl<V: 'static> Div<V> {
         bounds: RectF,
         overflow: Point<Overflow>,
         child_layout_ids: &[LayoutId],
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
             let mut scroll_max = Vector2F::zero();
@@ -214,7 +213,7 @@ impl<V: 'static> Div<V> {
         }
     }
 
-    fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut PaintContext<V>) {
+    fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut ViewContext<V>) {
         let style = self.styles.merged();
         let bounds = layout.bounds + parent_origin;
 

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<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
         parent_origin: Vector2F,
         layout: &Layout,
         paint_state: &mut Self::PaintState,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) where
         Self: Sized,
     {

crates/gpui2/src/elements/img.rs ๐Ÿ”—

@@ -53,7 +53,7 @@ impl<V: 'static> Element<V> for Img {
         parent_origin: Vector2F,
         layout: &gpui::Layout,
         _: &mut Self::PaintState,
-        cx: &mut crate::paint_context::PaintContext<V>,
+        cx: &mut crate::ViewContext<V>,
     ) where
         Self: Sized,
     {

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<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
         parent_origin: Vector2F,
         layout: &Layout,
         paint_state: &mut Self::PaintState,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) where
         Self: Sized,
     {

crates/gpui2/src/elements/svg.rs ๐Ÿ”—

@@ -49,7 +49,7 @@ impl<V: 'static> Element<V> for Svg {
         parent_origin: Vector2F,
         layout: &Layout,
         _: &mut Self::PaintState,
-        cx: &mut crate::paint_context::PaintContext<V>,
+        cx: &mut crate::ViewContext<V>,
     ) where
         Self: Sized,
     {

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<V: 'static> Element<V> for Text {
         parent_origin: Vector2F,
         layout: &Layout,
         paint_state: &mut Self::PaintState,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let bounds = layout.bounds + parent_origin;
 

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;

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<V: 'static> {
     fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
@@ -121,7 +121,7 @@ pub struct InteractionHandlers<V: 'static> {
 }
 
 impl<V: 'static> InteractionHandlers<V> {
-    pub fn paint(&self, order: u32, bounds: RectF, cx: &mut PaintContext<V>) {
+    pub fn paint(&self, order: u32, bounds: RectF, cx: &mut ViewContext<V>) {
         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) {

crates/gpui2/src/paint_context.rs ๐Ÿ”—

@@ -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<E: 'static>(
-        &mut self,
-        order: u32,
-        handler: impl Fn(&mut V, &E, &mut EventContext<V>) + '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::<E>(),
-        })
-    }
-
-    pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
-        self.layout_engine()
-            .ok_or_else(|| anyhow!("no layout engine present"))?
-            .computed_layout(layout_id)
-    }
-}

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<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
+    pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
         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<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
+    pub fn paint_foreground<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
         let rem_size = cx.rem_size();
 
         if let Some(color) = self.border_color {

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<E: 'static>(
+        &mut self,
+        order: u32,
+        handler: impl Fn(&mut V, &E, &mut EventContext<V>) + '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::<E>(),
+        })
+    }
+
+    pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
+        self.layout_engine()
+            .ok_or_else(|| anyhow!("no layout engine present"))?
+            .computed_layout(layout_id)
+    }
 }

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<V>,
+                cx: &mut gpui2::ViewContext<V>,
             ) {
                 rendered_element.paint(view, parent_origin, cx);
             }

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<V>,
+                cx: &mut gpui::ViewContext<V>,
             ) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
                 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<V>,
                 view: &mut V,
-                cx: &mut gpui::PaintContext<V>,
+                cx: &mut gpui::ViewContext<V>,
             ) {
                 element.paint(bounds.origin(), visible_bounds, view, cx);
             }

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"
         }
       }
     ]

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<Task<()>>,
     _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;
         }
     }
 

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<dyn HttpClient>,
     pub executor: Arc<Background>,
+    rate_limit_count_rx: watch::Receiver<Option<Instant>>,
+    rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
 }
 
 #[derive(Serialize)]
@@ -114,12 +119,16 @@ pub trait EmbeddingProvider: Sync + Send {
     async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>>;
     fn max_tokens_per_batch(&self) -> usize;
     fn truncate(&self, span: &str) -> (String, usize);
+    fn rate_limit_expiration(&self) -> Option<Instant>;
 }
 
 pub struct DummyEmbeddings {}
 
 #[async_trait]
 impl EmbeddingProvider for DummyEmbeddings {
+    fn rate_limit_expiration(&self) -> Option<Instant> {
+        None
+    }
     async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
         // 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<dyn HttpClient>, executor: Arc<Background>) -> 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<Instant> {
+        *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<AsyncBody>;
         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

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<Instant>,
+    },
 }
 
 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 {

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<Instant> {
+        None
+    }
+
     async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
         self.embedding_count
             .fetch_add(spans.len(), atomic::Ordering::SeqCst);

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<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
         parent_origin: Vector2F,
         layout: &gpui2::Layout,
         state: &mut Self::PaintState,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) where
         Self: Sized,
     {

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<TerminalView> for TerminalElement {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut TerminalView,
-        cx: &mut LayoutContext<TerminalView>,
+        cx: &mut ViewContext<TerminalView>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         let settings = settings::get::<ThemeSettings>(cx);
         let terminal_settings = settings::get::<TerminalSettings>(cx);
@@ -734,7 +733,7 @@ impl Element<TerminalView> for TerminalElement {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut TerminalView,
-        cx: &mut PaintContext<TerminalView>,
+        cx: &mut ViewContext<TerminalView>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 

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<Self>) {
         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| {

crates/workspace/src/item.rs ๐Ÿ”—

@@ -474,8 +474,14 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                     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;
                             }
 

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<Pane>,
 }
 
+#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+pub struct CloseActiveItem {
+    pub save_behavior: Option<SaveBehavior>,
+}
+
 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<Self>,
     ) -> Option<Task<Result<()>>> {
         if self.items.is_empty() {
             return None;
         }
         let active_item_id = self.items[self.active_item_index].id();
-        Some(self.close_item_by_id(active_item_id, 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<Self>,
     ) -> Task<Result<()>> {
-        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<Pane>,
+        save_behavior: SaveBehavior,
         should_close: impl 'static + Fn(usize) -> bool,
     ) -> Task<Result<()>> {
         // 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<Pane>,
         item_ix: usize,
         item: &dyn ItemHandle,
-        should_prompt_for_save: bool,
+        save_behavior: SaveBehavior,
         cx: &mut AsyncAppContext,
     ) -> Result<bool> {
         const CONFLICT_MESSAGE: &str =
             "This file has changed on disk since you started editing it. Do you want to overwrite it?";
         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<V: 'static> Element<V> for PaneBackdrop<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         (size, ())
@@ -2011,7 +2078,7 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> 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);
     }
 

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<Workspace>,
+            cx: &mut ViewContext<Workspace>,
         ) {
             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<Workspace>,
+            cx: &mut ViewContext<Workspace>,
         ) -> (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<Workspace>,
+            cx: &mut ViewContext<Workspace>,
         ) -> Self::PaintState {
             let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
             let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

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<StatusBar> for StatusBarElement {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut StatusBar,
-        cx: &mut LayoutContext<StatusBar>,
+        cx: &mut ViewContext<StatusBar>,
     ) -> (Vector2F, Self::LayoutState) {
         let max_width = constraint.max.x();
         constraint.min = vec2f(0., constraint.min.y());
@@ -230,7 +230,7 @@ impl Element<StatusBar> for StatusBarElement {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut StatusBar,
-        cx: &mut PaintContext<StatusBar>,
+        cx: &mut ViewContext<StatusBar>,
     ) -> Self::PaintState {
         let origin_y = bounds.upper_right().y();
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

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<Self>) -> Option<Task<Result<()>>> {
-        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<Self>,
     ) -> Task<Result<bool>> {
         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));

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"

crates/zed/src/languages.rs ๐Ÿ”—

@@ -170,6 +170,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<dyn NodeRuntime>
     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"))]

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 },
+]

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

crates/zed/src/menus.rs ๐Ÿ”—

@@ -41,7 +41,12 @@ pub fn menus() -> Vec<Menu<'static>> {
                 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),
             ],
         },

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::<Editor>().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