Merge branch 'main' into add-app-events

Joseph T. Lyons created

Change summary

.github/actions/run_tests/action.yml              |    6 
.github/workflows/ci.yml                          |    5 
.github/workflows/release_nightly.yml             |    2 
Cargo.lock                                        |   55 
Cargo.toml                                        |    3 
assets/settings/default.json                      |    2 
assets/settings/initial_user_settings.json        |    3 
crates/auto_update2/src/update_notification.rs    |    4 
crates/collab/Cargo.toml                          |    2 
crates/collab_ui2/src/collab_panel.rs             |    4 
crates/collab_ui2/src/collab_titlebar_item.rs     |   18 
crates/command_palette2/src/command_palette.rs    |    6 
crates/diagnostics2/src/diagnostics.rs            |   25 
crates/diagnostics2/src/items.rs                  |   24 
crates/diagnostics2/src/toolbar_controls.rs       |   12 
crates/editor2/src/display_map/block_map.rs       |    6 
crates/editor2/src/editor.rs                      |  550 +-
crates/editor2/src/element.rs                     | 1115 ++++---
crates/editor2/src/hover_popover.rs               |    4 
crates/editor2/src/items.rs                       |   48 
crates/file_finder2/src/file_finder.rs            |    6 
crates/go_to_line2/src/go_to_line.rs              |   12 
crates/gpui2/src/app.rs                           |    2 
crates/gpui2/src/app/async_context.rs             |    6 
crates/gpui2/src/app/test_context.rs              |   33 
crates/gpui2/src/element.rs                       |  275 -
crates/gpui2/src/elements/div.rs                  |  480 +-
crates/gpui2/src/elements/img.rs                  |   33 
crates/gpui2/src/elements/overlay.rs              |   28 
crates/gpui2/src/elements/svg.rs                  |   32 
crates/gpui2/src/elements/text.rs                 |  188 
crates/gpui2/src/elements/uniform_list.rs         |  100 
crates/gpui2/src/gpui2.rs                         |    4 
crates/gpui2/src/input.rs                         |    8 
crates/gpui2/src/interactive.rs                   |   38 
crates/gpui2/src/style.rs                         |   13 
crates/gpui2/src/styled.rs                        |   20 
crates/gpui2/src/view.rs                          |  182 
crates/gpui2/src/window.rs                        |  160 
crates/gpui2_macros/src/derive_render_once.rs     |   45 
crates/language2/src/buffer.rs                    |   41 
crates/language2/src/proto.rs                     |    1 
crates/picker2/src/picker2.rs                     |   82 
crates/project2/src/lsp_command.rs                |   25 
crates/project_panel2/src/project_panel.rs        |   86 
crates/rich_text2/src/rich_text.rs                |    4 
crates/search2/Cargo.toml                         |   40 
crates/search2/src/buffer_search.rs               | 1704 ++++++++++
crates/search2/src/history.rs                     |  184 +
crates/search2/src/mode.rs                        |   32 
crates/search2/src/project_search.rs              | 2661 +++++++++++++++++
crates/search2/src/search.rs                      |  117 
crates/search2/src/search_bar.rs                  |   35 
crates/story/Cargo.toml                           |   10 
crates/story/src/lib.rs                           |    3 
crates/story/src/story.rs                         |   35 
crates/storybook2/Cargo.toml                      |    1 
crates/storybook2/src/stories.rs                  |    2 
crates/storybook2/src/stories/colors.rs           |   44 
crates/storybook2/src/stories/focus.rs            |   60 
crates/storybook2/src/stories/kitchen_sink.rs     |   14 
crates/storybook2/src/stories/picker.rs           |    6 
crates/storybook2/src/stories/scroll.rs           |    6 
crates/storybook2/src/stories/text.rs             |    4 
crates/storybook2/src/stories/z_index.rs          |   85 
crates/storybook2/src/story.rs                    |    1 
crates/storybook2/src/story_selector.rs           |   56 
crates/storybook2/src/storybook2.rs               |   10 
crates/storybook3/Cargo.toml                      |   17 
crates/storybook3/src/storybook3.rs               |   87 
crates/terminal_view2/src/terminal_panel.rs       |    4 
crates/terminal_view2/src/terminal_view.rs        |   73 
crates/theme2/Cargo.toml                          |    5 
crates/theme2/src/settings.rs                     |    2 
crates/theme2/src/story.rs                        |   41 
crates/theme2/src/styles.rs                       |    6 
crates/theme2/src/styles/players.rs               |  140 
crates/theme2/src/styles/stories/color.rs         |   41 
crates/theme2/src/styles/stories/mod.rs           |    5 
crates/theme2/src/styles/stories/players.rs       |  137 
crates/theme2/src/theme2.rs                       |   11 
crates/ui2/Cargo.toml                             |    7 
crates/ui2/src/components.rs                      |   36 
crates/ui2/src/components/avatar.rs               |   41 
crates/ui2/src/components/button.rs               |  312 -
crates/ui2/src/components/checkbox.rs             |  110 
crates/ui2/src/components/context_menu.rs         |  253 -
crates/ui2/src/components/details.rs              |   83 
crates/ui2/src/components/disclosure.rs           |   19 
crates/ui2/src/components/divider.rs              |    8 
crates/ui2/src/components/elevated_surface.rs     |   28 
crates/ui2/src/components/facepile.rs             |   64 
crates/ui2/src/components/icon.rs                 |   48 
crates/ui2/src/components/icon_button.rs          |   61 
crates/ui2/src/components/indicator.rs            |   37 
crates/ui2/src/components/input.rs                |   37 
crates/ui2/src/components/keybinding.rs           |   80 
crates/ui2/src/components/label.rs                |  102 
crates/ui2/src/components/list.rs                 |  114 
crates/ui2/src/components/modal.rs                |   86 
crates/ui2/src/components/notification_toast.rs   |   40 
crates/ui2/src/components/palette.rs              |  213 -
crates/ui2/src/components/panel.rs                |  154 
crates/ui2/src/components/player.rs               |  174 -
crates/ui2/src/components/player_stack.rs         |   67 
crates/ui2/src/components/popover.rs              |   89 
crates/ui2/src/components/stack.rs                |    4 
crates/ui2/src/components/stories.rs              |   17 
crates/ui2/src/components/stories/avatar.rs       |   23 
crates/ui2/src/components/stories/button.rs       |  145 
crates/ui2/src/components/stories/checkbox.rs     |   49 
crates/ui2/src/components/stories/context_menu.rs |  104 
crates/ui2/src/components/stories/icon.rs         |   21 
crates/ui2/src/components/stories/input.rs        |   18 
crates/ui2/src/components/stories/keybinding.rs   |   61 
crates/ui2/src/components/stories/label.rs        |   27 
crates/ui2/src/components/tab.rs                  |  276 -
crates/ui2/src/components/toast.rs                |  117 
crates/ui2/src/components/toggle.rs               |   20 
crates/ui2/src/components/tool_divider.rs         |   23 
crates/ui2/src/components/tooltip.rs              |   12 
crates/ui2/src/prelude.rs                         |  153 
crates/ui2/src/settings.rs                        |   74 
crates/ui2/src/static_data.rs                     | 1123 -------
crates/ui2/src/story.rs                           |   43 
crates/ui2/src/styled_ext.rs                      |   12 
crates/ui2/src/styles.rs                          |    7 
crates/ui2/src/styles/color.rs                    |   44 
crates/ui2/src/styles/docs/elevation.md           |   43 
crates/ui2/src/styles/elevation.rs                |    8 
crates/ui2/src/styles/typography.rs               |   27 
crates/ui2/src/to_extract.rs                      |   47 
crates/ui2/src/to_extract/assistant_panel.rs      |   97 
crates/ui2/src/to_extract/breadcrumb.rs           |  118 
crates/ui2/src/to_extract/buffer.rs               |  281 -
crates/ui2/src/to_extract/buffer_search.rs        |   45 
crates/ui2/src/to_extract/chat_panel.rs           |  158 -
crates/ui2/src/to_extract/collab_panel.rs         |  118 
crates/ui2/src/to_extract/command_palette.rs      |   53 
crates/ui2/src/to_extract/copilot.rs              |   49 
crates/ui2/src/to_extract/editor_pane.rs          |   77 
crates/ui2/src/to_extract/language_selector.rs    |   83 
crates/ui2/src/to_extract/multi_buffer.rs         |   68 
crates/ui2/src/to_extract/notifications_panel.rs  |  379 --
crates/ui2/src/to_extract/panes.rs                |  139 
crates/ui2/src/to_extract/project_panel.rs        |  117 
crates/ui2/src/to_extract/recent_projects.rs      |   58 
crates/ui2/src/to_extract/status_bar.rs           |  201 -
crates/ui2/src/to_extract/tab_bar.rs              |  156 
crates/ui2/src/to_extract/terminal.rs             |  166 -
crates/ui2/src/to_extract/theme_selector.rs       |   66 
crates/ui2/src/to_extract/title_bar.rs            |  218 -
crates/ui2/src/to_extract/toolbar.rs              |  130 
crates/ui2/src/to_extract/traffic_lights.rs       |  109 
crates/ui2/src/to_extract/workspace.rs            |  398 --
crates/ui2/src/ui2.rs                             |   28 
crates/workspace2/src/dock.rs                     |   30 
crates/workspace2/src/item.rs                     |   10 
crates/workspace2/src/modal_layer.rs              |   12 
crates/workspace2/src/notifications.rs            |   12 
crates/workspace2/src/pane.rs                     |   68 
crates/workspace2/src/pane_group.rs               |    6 
crates/workspace2/src/searchable.rs               |   23 
crates/workspace2/src/status_bar.rs               |   10 
crates/workspace2/src/toolbar.rs                  |   63 
crates/workspace2/src/workspace2.rs               |  100 
crates/zed/Cargo.toml                             |    2 
crates/zed2/Cargo.toml                            |    2 
crates/zed2/src/main.rs                           |    3 
crates/zed2/src/zed2.rs                           |    4 
script/deploy-docs                                |  159 +
171 files changed, 8,374 insertions(+), 9,680 deletions(-)

Detailed changes

.github/actions/run_tests/action.yml 🔗

@@ -19,16 +19,12 @@ runs:
 
     - name: Limit target directory size
       shell: bash -euxo pipefail {0}
-      run: script/clear-target-dir-if-larger-than 70
+      run: script/clear-target-dir-if-larger-than 100
 
     - name: Run check
-      env:
-        RUSTFLAGS: -D warnings
       shell: bash -euxo pipefail {0}
       run: cargo check --tests --workspace
 
     - name: Run tests
-      env:
-        RUSTFLAGS: -D warnings
       shell: bash -euxo pipefail {0}
       run: cargo nextest run --workspace --no-fail-fast

.github/workflows/ci.yml 🔗

@@ -23,6 +23,9 @@ jobs:
       - self-hosted
       - test
     steps:
+      - name: Set up default .cargo/config.toml
+        run: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml
+
       - name: Checkout repo
         uses: actions/checkout@v3
         with:
@@ -87,7 +90,7 @@ jobs:
           submodules: "recursive"
 
       - name: Limit target directory size
-        run: script/clear-target-dir-if-larger-than 70
+        run: script/clear-target-dir-if-larger-than 100
 
       - name: Determine version and release channel
         if: ${{ startsWith(github.ref, 'refs/tags/v') }}

.github/workflows/release_nightly.yml 🔗

@@ -79,7 +79,7 @@ jobs:
           submodules: "recursive"
 
       - name: Limit target directory size
-        run: script/clear-target-dir-if-larger-than 70
+        run: script/clear-target-dir-if-larger-than 100
 
       - name: Set release channel to nightly
         run: |

Cargo.lock 🔗

@@ -1664,7 +1664,7 @@ dependencies = [
 
 [[package]]
 name = "collab"
-version = "0.28.0"
+version = "0.29.0"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -8031,6 +8031,35 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "search2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bitflags 1.3.2",
+ "client2",
+ "collections",
+ "editor2",
+ "futures 0.3.28",
+ "gpui2",
+ "language2",
+ "log",
+ "menu2",
+ "postage",
+ "project2",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "settings2",
+ "smallvec",
+ "smol",
+ "theme2",
+ "ui2",
+ "unindent",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "security-framework"
 version = "2.9.2"
@@ -8854,6 +8883,13 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "story"
+version = "0.1.0"
+dependencies = [
+ "gpui2",
+]
+
 [[package]]
 name = "storybook2"
 version = "0.1.0"
@@ -8875,6 +8911,7 @@ dependencies = [
  "settings2",
  "simplelog",
  "smallvec",
+ "story",
  "strum",
  "theme",
  "theme2",
@@ -8882,17 +8919,6 @@ dependencies = [
  "util",
 ]
 
-[[package]]
-name = "storybook3"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "gpui2",
- "settings2",
- "theme2",
- "ui2",
-]
-
 [[package]]
 name = "stringprep"
 version = "0.1.4"
@@ -9396,6 +9422,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "settings2",
+ "story",
  "toml 0.5.11",
  "util",
  "uuid 1.4.1",
@@ -10239,6 +10266,7 @@ dependencies = [
  "serde",
  "settings2",
  "smallvec",
+ "story",
  "strum",
  "theme2",
 ]
@@ -11465,7 +11493,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.114.0"
+version = "0.115.0"
 dependencies = [
  "activity_indicator",
  "ai",
@@ -11660,6 +11688,7 @@ dependencies = [
  "rsa 0.4.0",
  "rust-embed",
  "schemars",
+ "search2",
  "serde",
  "serde_derive",
  "serde_json",

Cargo.toml 🔗

@@ -90,6 +90,7 @@ members = [
     "crates/rpc",
     "crates/rpc2",
     "crates/search",
+    "crates/search2",
     "crates/settings",
     "crates/settings2",
     "crates/snippet",
@@ -97,7 +98,6 @@ members = [
     "crates/sqlez_macros",
     "crates/rich_text",
     "crates/storybook2",
-    "crates/storybook3",
     "crates/sum_tree",
     "crates/terminal",
     "crates/terminal2",
@@ -110,6 +110,7 @@ members = [
     "crates/ui2",
     "crates/util",
     "crates/semantic_index",
+    "crates/story",
     "crates/vim",
     "crates/vcs_menu",
     "crates/workspace2",

assets/settings/default.json 🔗

@@ -43,7 +43,7 @@
     "calt": false
   },
   // The default font size for text in the UI
-  "ui_font_size": 14,
+  "ui_font_size": 16,
   // The factor to grow the active pane by. Defaults to 1.0
   // which gives the same size as all other panes.
   "active_pane_magnification": 1.0,

assets/settings/initial_user_settings.json 🔗

@@ -7,5 +7,6 @@
 // custom settings, run the `open default settings` command
 // from the command palette or from `Zed` application menu.
 {
-  "buffer_font_size": 15
+  "ui_font_size": 16,
+  "buffer_font_size": 16
 }

crates/auto_update2/src/update_notification.rs 🔗

@@ -8,8 +8,8 @@ pub struct UpdateNotification {
 
 impl EventEmitter<NotificationEvent> for UpdateNotification {}
 
-impl Render<Self> for UpdateNotification {
-    type Element = Div<Self>;
+impl Render for UpdateNotification {
+    type Element = Div;
 
     fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         div().child("Updated zed!")

crates/collab/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
 default-run = "collab"
 edition = "2021"
 name = "collab"
-version = "0.28.0"
+version = "0.29.0"
 publish = false
 
 [[bin]]

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -3294,8 +3294,8 @@ impl CollabPanel {
 //     .with_width(size.x())
 // }
 
-impl Render<Self> for CollabPanel {
-    type Element = Focusable<Self, Div<Self>>;
+impl Render for CollabPanel {
+    type Element = Focusable<Div>;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
         div()

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -37,7 +37,7 @@ use gpui::{
 };
 use project::Project;
 use theme::ActiveTheme;
-use ui::{h_stack, Button, ButtonVariant, KeyBinding, Label, TextColor, Tooltip};
+use ui::{h_stack, Button, ButtonVariant, Color, KeyBinding, Label, Tooltip};
 use workspace::Workspace;
 
 // const MAX_PROJECT_NAME_LENGTH: usize = 40;
@@ -81,8 +81,8 @@ pub struct CollabTitlebarItem {
     _subscriptions: Vec<Subscription>,
 }
 
-impl Render<Self> for CollabTitlebarItem {
-    type Element = Stateful<Self, Div<Self>>;
+impl Render for CollabTitlebarItem {
+    type Element = Stateful<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         h_stack()
@@ -100,7 +100,7 @@ impl Render<Self> for CollabTitlebarItem {
                 |s| s.pl(px(68.)),
             )
             .bg(cx.theme().colors().title_bar_background)
-            .on_click(|_, event, cx| {
+            .on_click(|event, cx| {
                 if event.up.click_count == 2 {
                     cx.zoom_window();
                 }
@@ -115,16 +115,16 @@ impl Render<Self> for CollabTitlebarItem {
                             .child(
                                 Button::new("player")
                                     .variant(ButtonVariant::Ghost)
-                                    .color(Some(TextColor::Player(0))),
+                                    .color(Some(Color::Player(0))),
                             )
-                            .tooltip(move |_, cx| Tooltip::text("Toggle following", cx)),
+                            .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
                     )
                     // TODO - Add project menu
                     .child(
                         div()
                             .id("titlebar_project_menu_button")
                             .child(Button::new("project_name").variant(ButtonVariant::Ghost))
-                            .tooltip(move |_, cx| Tooltip::text("Recent Projects", cx)),
+                            .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
                     )
                     // TODO - Add git menu
                     .child(
@@ -133,9 +133,9 @@ impl Render<Self> for CollabTitlebarItem {
                             .child(
                                 Button::new("branch_name")
                                     .variant(ButtonVariant::Ghost)
-                                    .color(Some(TextColor::Muted)),
+                                    .color(Some(Color::Muted)),
                             )
-                            .tooltip(move |_, cx| {
+                            .tooltip(move |cx| {
                                 cx.build_view(|_| {
                                     Tooltip::new("Recent Branches")
                                         .key_binding(KeyBinding::new(gpui::KeyBinding::new(

crates/command_palette2/src/command_palette.rs 🔗

@@ -76,8 +76,8 @@ impl FocusableView for CommandPalette {
     }
 }
 
-impl Render<Self> for CommandPalette {
-    type Element = Div<Self>;
+impl Render for CommandPalette {
+    type Element = Div;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
         v_stack().w_96().child(self.picker.clone())
@@ -140,7 +140,7 @@ impl CommandPaletteDelegate {
 }
 
 impl PickerDelegate for CommandPaletteDelegate {
-    type ListItem = Div<Picker<Self>>;
+    type ListItem = Div;
 
     fn placeholder_text(&self) -> Arc<str> {
         "Execute a command...".into()

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -16,7 +16,7 @@ use gpui::{
     actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent,
     FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, Model,
     ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext,
-    VisualContext, WeakView,
+    VisualContext, WeakView, WindowContext,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -36,7 +36,7 @@ use std::{
 };
 use theme::ActiveTheme;
 pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, HighlightedLabel, Icon, IconElement, Label, TextColor};
+use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label};
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -90,8 +90,8 @@ struct DiagnosticGroupState {
 
 impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
 
-impl Render<Self> for ProjectDiagnosticsEditor {
-    type Element = Focusable<Self, Div<Self>>;
+impl Render for ProjectDiagnosticsEditor {
+    type Element = Focusable<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let child = if self.path_states.is_empty() {
@@ -109,8 +109,8 @@ impl Render<Self> for ProjectDiagnosticsEditor {
         div()
             .track_focus(&self.focus_handle)
             .size_full()
-            .on_focus_in(Self::focus_in)
-            .on_action(Self::toggle_warnings)
+            .on_focus_in(cx.listener(Self::focus_in))
+            .on_action(cx.listener(Self::toggle_warnings))
             .child(child)
     }
 }
@@ -662,7 +662,7 @@ impl Item for ProjectDiagnosticsEditor {
         Some("Project Diagnostics".into())
     }
 
-    fn tab_content<T: 'static>(&self, _detail: Option<usize>, _: &AppContext) -> AnyElement<T> {
+    fn tab_content(&self, _detail: Option<usize>, _: &WindowContext) -> AnyElement {
         render_summary(&self.summary)
     }
 
@@ -742,7 +742,7 @@ impl Item for ProjectDiagnosticsEditor {
     }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft { flex: None }
+        ToolbarItemLocation::PrimaryLeft
     }
 
     fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -778,15 +778,15 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
             .bg(gpui::red())
             .map(|stack| {
                 let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
-                    IconElement::new(Icon::XCircle).color(TextColor::Error)
+                    IconElement::new(Icon::XCircle).color(Color::Error)
                 } else {
-                    IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning)
+                    IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
                 };
 
                 stack.child(div().pl_8().child(icon))
             })
             .when_some(diagnostic.source.as_ref(), |stack, source| {
-                stack.child(Label::new(format!("{source}:")).color(TextColor::Accent))
+                stack.child(Label::new(format!("{source}:")).color(Color::Accent))
             })
             .child(HighlightedLabel::new(message.clone(), highlights.clone()))
             .when_some(diagnostic.code.as_ref(), |stack, code| {
@@ -796,11 +796,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
     })
 }
 
-pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> {
+pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement {
     if summary.error_count == 0 && summary.warning_count == 0 {
         let label = Label::new("No problems");
         label.render_into_any()
-        //.render()
     } else {
         h_stack()
             .bg(gpui::red())

crates/diagnostics2/src/items.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
 use language::Diagnostic;
 use lsp::LanguageServerId;
 use theme::ActiveTheme;
-use ui::{h_stack, Icon, IconElement, Label, TextColor, Tooltip};
+use ui::{h_stack, Color, Icon, IconElement, Label, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
 
 use crate::ProjectDiagnosticsEditor;
@@ -21,31 +21,31 @@ pub struct DiagnosticIndicator {
     _observe_active_editor: Option<Subscription>,
 }
 
-impl Render<Self> for DiagnosticIndicator {
-    type Element = Stateful<Self, Div<Self>>;
+impl Render for DiagnosticIndicator {
+    type Element = Stateful<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
-            (0, 0) => h_stack().child(IconElement::new(Icon::Check).color(TextColor::Success)),
+            (0, 0) => h_stack().child(IconElement::new(Icon::Check).color(Color::Success)),
             (0, warning_count) => h_stack()
                 .gap_1()
-                .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))
+                .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
                 .child(Label::new(warning_count.to_string())),
             (error_count, 0) => h_stack()
                 .gap_1()
-                .child(IconElement::new(Icon::XCircle).color(TextColor::Error))
+                .child(IconElement::new(Icon::XCircle).color(Color::Error))
                 .child(Label::new(error_count.to_string())),
             (error_count, warning_count) => h_stack()
                 .gap_1()
-                .child(IconElement::new(Icon::XCircle).color(TextColor::Error))
+                .child(IconElement::new(Icon::XCircle).color(Color::Error))
                 .child(Label::new(error_count.to_string()))
-                .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))
+                .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
                 .child(Label::new(warning_count.to_string())),
         };
 
         h_stack()
             .id(cx.entity_id())
-            .on_action(Self::go_to_next_diagnostic)
+            .on_action(cx.listener(Self::go_to_next_diagnostic))
             .rounded_md()
             .flex_none()
             .h(rems(1.375))
@@ -54,14 +54,14 @@ impl Render<Self> for DiagnosticIndicator {
             .bg(cx.theme().colors().ghost_element_background)
             .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
             .active(|style| style.bg(cx.theme().colors().ghost_element_active))
-            .tooltip(|_, cx| Tooltip::text("Project Diagnostics", cx))
-            .on_click(|this, _, cx| {
+            .tooltip(|cx| Tooltip::text("Project Diagnostics", cx))
+            .on_click(cx.listener(|this, _, cx| {
                 if let Some(workspace) = this.workspace.upgrade() {
                     workspace.update(cx, |workspace, cx| {
                         ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
                     })
                 }
-            })
+            }))
             .child(diagnostic_indicator)
     }
 }

crates/diagnostics2/src/toolbar_controls.rs 🔗

@@ -7,8 +7,8 @@ pub struct ToolbarControls {
     editor: Option<WeakView<ProjectDiagnosticsEditor>>,
 }
 
-impl Render<Self> for ToolbarControls {
-    type Element = Div<Self>;
+impl Render for ToolbarControls {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let include_warnings = self
@@ -26,14 +26,14 @@ impl Render<Self> for ToolbarControls {
 
         div().child(
             IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
-                .tooltip(move |_, cx| Tooltip::text(tooltip, cx))
-                .on_click(|this: &mut Self, cx| {
+                .tooltip(move |cx| Tooltip::text(tooltip, cx))
+                .on_click(cx.listener(|this, _, cx| {
                     if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
                         editor.update(cx, |editor, cx| {
                             editor.toggle_warnings(&Default::default(), cx);
                         });
                     }
-                }),
+                })),
         )
     }
 }
@@ -49,7 +49,7 @@ impl ToolbarItemView for ToolbarControls {
         if let Some(pane_item) = active_pane_item.as_ref() {
             if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
                 self.editor = Some(editor.downgrade());
-                ToolbarItemLocation::PrimaryRight { flex: None }
+                ToolbarItemLocation::PrimaryRight
             } else {
                 ToolbarItemLocation::Hidden
             }

crates/editor2/src/display_map/block_map.rs 🔗

@@ -50,7 +50,7 @@ struct BlockRow(u32);
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 struct WrapRow(u32);
 
-pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>;
+pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>;
 
 pub struct Block {
     id: BlockId,
@@ -69,7 +69,7 @@ where
     pub position: P,
     pub height: u8,
     pub style: BlockStyle,
-    pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>,
+    pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
     pub disposition: BlockDisposition,
 }
 
@@ -947,7 +947,7 @@ impl DerefMut for BlockContext<'_, '_> {
 }
 
 impl Block {
-    pub fn render(&self, cx: &mut BlockContext) -> AnyElement<Editor> {
+    pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
         self.render.lock()(cx)
     }
 

crates/editor2/src/editor.rs 🔗

@@ -44,7 +44,7 @@ use gpui::{
     EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
     Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
     SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
-    ViewContext, VisualContext, WeakView, WindowContext,
+    ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -54,13 +54,13 @@ use itertools::Itertools;
 pub use language::{char_kind, CharKind};
 use language::{
     language_settings::{self, all_language_settings, InlayHintSettings},
-    point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,
-    Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
-    OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
+    point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion,
+    CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, LanguageRegistry,
+    LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
 };
 use lazy_static::lazy_static;
 use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
-use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
+use lsp::{DiagnosticSeverity, LanguageServerId};
 use movement::TextLayoutDetails;
 use multi_buffer::ToOffsetUtf16;
 pub use multi_buffer::{
@@ -97,7 +97,7 @@ use text::{OffsetUtf16, Rope};
 use theme::{
     ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
 };
-use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};
+use ui::{h_stack, v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::{
     item::{ItemEvent, ItemHandle},
@@ -907,7 +907,7 @@ impl ContextMenu {
         style: &EditorStyle,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement<Editor>) {
+    ) -> (DisplayPoint, AnyElement) {
         match self {
             ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
             ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
@@ -1223,208 +1223,202 @@ impl CompletionsMenu {
         style: &EditorStyle,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> AnyElement<Editor> {
-        todo!("old implementation below")
-    }
+    ) -> AnyElement {
+        let settings = EditorSettings::get_global(cx);
+        let show_completion_documentation = settings.show_completion_documentation;
 
-    //     enum CompletionTag {}
+        let widest_completion_ix = self
+            .matches
+            .iter()
+            .enumerate()
+            .max_by_key(|(_, mat)| {
+                let completions = self.completions.read();
+                let completion = &completions[mat.candidate_id];
+                let documentation = &completion.documentation;
+
+                let mut len = completion.label.text.chars().count();
+                if let Some(Documentation::SingleLine(text)) = documentation {
+                    if show_completion_documentation {
+                        len += text.chars().count();
+                    }
+                }
 
-    //     let settings = EditorSettings>(cx);
-    //     let show_completion_documentation = settings.show_completion_documentation;
+                len
+            })
+            .map(|(ix, _)| ix);
 
-    //     let widest_completion_ix = self
-    //         .matches
-    //         .iter()
-    //         .enumerate()
-    //         .max_by_key(|(_, mat)| {
-    //             let completions = self.completions.read();
-    //             let completion = &completions[mat.candidate_id];
-    //             let documentation = &completion.documentation;
+        let completions = self.completions.clone();
+        let matches = self.matches.clone();
+        let selected_item = self.selected_item;
 
-    //             let mut len = completion.label.text.chars().count();
-    //             if let Some(Documentation::SingleLine(text)) = documentation {
-    //                 if show_completion_documentation {
-    //                     len += text.chars().count();
-    //                 }
-    //             }
+        let list = uniform_list(
+            cx.view().clone(),
+            "completions",
+            matches.len(),
+            move |editor, range, cx| {
+                let start_ix = range.start;
+                let completions_guard = completions.read();
 
-    //             len
-    //         })
-    //         .map(|(ix, _)| ix);
+                matches[range]
+                    .iter()
+                    .enumerate()
+                    .map(|(ix, mat)| {
+                        let item_ix = start_ix + ix;
+                        let candidate_id = mat.candidate_id;
+                        let completion = &completions_guard[candidate_id];
 
-    //     let completions = self.completions.clone();
-    //     let matches = self.matches.clone();
-    //     let selected_item = self.selected_item;
-
-    //     let list = UniformList::new(self.list.clone(), matches.len(), cx, {
-    //         let style = style.clone();
-    //         move |_, range, items, cx| {
-    //             let start_ix = range.start;
-    //             let completions_guard = completions.read();
-
-    //             for (ix, mat) in matches[range].iter().enumerate() {
-    //                 let item_ix = start_ix + ix;
-    //                 let candidate_id = mat.candidate_id;
-    //                 let completion = &completions_guard[candidate_id];
-
-    //                 let documentation = if show_completion_documentation {
-    //                     &completion.documentation
-    //                 } else {
-    //                     &None
-    //                 };
+                        let documentation = if show_completion_documentation {
+                            &completion.documentation
+                        } else {
+                            &None
+                        };
 
-    //                 items.push(
-    //                     MouseEventHandler::new::<CompletionTag, _>(
-    //                         mat.candidate_id,
-    //                         cx,
-    //                         |state, _| {
-    //                             let item_style = if item_ix == selected_item {
-    //                                 style.autocomplete.selected_item
-    //                             } else if state.hovered() {
-    //                                 style.autocomplete.hovered_item
-    //                             } else {
-    //                                 style.autocomplete.item
-    //                             };
-
-    //                             let completion_label =
-    //                                 Text::new(completion.label.text.clone(), style.text.clone())
-    //                                     .with_soft_wrap(false)
-    //                                     .with_highlights(
-    //                                         combine_syntax_and_fuzzy_match_highlights(
-    //                                             &completion.label.text,
-    //                                             style.text.color.into(),
-    //                                             styled_runs_for_code_label(
-    //                                                 &completion.label,
-    //                                                 &style.syntax,
-    //                                             ),
-    //                                             &mat.positions,
-    //                                         ),
-    //                                     );
-
-    //                             if let Some(Documentation::SingleLine(text)) = documentation {
-    //                                 Flex::row()
-    //                                     .with_child(completion_label)
-    //                                     .with_children((|| {
-    //                                         let text_style = TextStyle {
-    //                                             color: style.autocomplete.inline_docs_color,
-    //                                             font_size: style.text.font_size
-    //                                                 * style.autocomplete.inline_docs_size_percent,
-    //                                             ..style.text.clone()
-    //                                         };
-
-    //                                         let label = Text::new(text.clone(), text_style)
-    //                                             .aligned()
-    //                                             .constrained()
-    //                                             .dynamically(move |constraint, _, _| {
-    //                                                 gpui::SizeConstraint {
-    //                                                     min: constraint.min,
-    //                                                     max: vec2f(
-    //                                                         constraint.max.x(),
-    //                                                         constraint.min.y(),
-    //                                                     ),
-    //                                                 }
-    //                                             });
-
-    //                                         if Some(item_ix) == widest_completion_ix {
-    //                                             Some(
-    //                                                 label
-    //                                                     .contained()
-    //                                                     .with_style(
-    //                                                         style
-    //                                                             .autocomplete
-    //                                                             .inline_docs_container,
-    //                                                     )
-    //                                                     .into_any(),
-    //                                             )
-    //                                         } else {
-    //                                             Some(label.flex_float().into_any())
-    //                                         }
-    //                                     })())
-    //                                     .into_any()
-    //                             } else {
-    //                                 completion_label.into_any()
-    //                             }
-    //                             .contained()
-    //                             .with_style(item_style)
-    //                             .constrained()
-    //                             .dynamically(
-    //                                 move |constraint, _, _| {
-    //                                     if Some(item_ix) == widest_completion_ix {
-    //                                         constraint
-    //                                     } else {
-    //                                         gpui::SizeConstraint {
-    //                                             min: constraint.min,
-    //                                             max: constraint.min,
-    //                                         }
-    //                                     }
-    //                                 },
-    //                             )
-    //                         },
-    //                     )
-    //                     .with_cursor_style(CursorStyle::PointingHand)
-    //                     .on_down(MouseButton::Left, move |_, this, cx| {
-    //                         this.confirm_completion(
-    //                             &ConfirmCompletion {
-    //                                 item_ix: Some(item_ix),
-    //                             },
-    //                             cx,
-    //                         )
-    //                         .map(|task| task.detach());
-    //                     })
-    //                     .constrained()
-    //                     .with_min_width(style.autocomplete.completion_min_width)
-    //                     .with_max_width(style.autocomplete.completion_max_width)
-    //                     .into_any(),
-    //                 );
-    //             }
-    //         }
-    //     })
-    //     .with_width_from_item(widest_completion_ix);
-
-    //     enum MultiLineDocumentation {}
-
-    //     Flex::row()
-    //         .with_child(list.flex(1., false))
-    //         .with_children({
-    //             let mat = &self.matches[selected_item];
-    //             let completions = self.completions.read();
-    //             let completion = &completions[mat.candidate_id];
-    //             let documentation = &completion.documentation;
-
-    //             match documentation {
-    //                 Some(Documentation::MultiLinePlainText(text)) => Some(
-    //                     Flex::column()
-    //                         .scrollable::<MultiLineDocumentation>(0, None, cx)
-    //                         .with_child(
-    //                             Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
-    //                         )
-    //                         .contained()
-    //                         .with_style(style.autocomplete.alongside_docs_container)
-    //                         .constrained()
-    //                         .with_max_width(style.autocomplete.alongside_docs_max_width)
-    //                         .flex(1., false),
-    //                 ),
-
-    //                 Some(Documentation::MultiLineMarkdown(parsed)) => Some(
-    //                     Flex::column()
-    //                         .scrollable::<MultiLineDocumentation>(0, None, cx)
-    //                         .with_child(render_parsed_markdown::<MultiLineDocumentation>(
-    //                             parsed, &style, workspace, cx,
-    //                         ))
-    //                         .contained()
-    //                         .with_style(style.autocomplete.alongside_docs_container)
-    //                         .constrained()
-    //                         .with_max_width(style.autocomplete.alongside_docs_max_width)
-    //                         .flex(1., false),
-    //                 ),
-
-    //                 _ => None,
-    //             }
-    //         })
-    //         .contained()
-    //         .with_style(style.autocomplete.container)
-    //         .into_any()
-    // }
+                        // todo!("highlights")
+                        // let highlights = combine_syntax_and_fuzzy_match_highlights(
+                        //     &completion.label.text,
+                        //     style.text.color.into(),
+                        //     styled_runs_for_code_label(&completion.label, &style.syntax),
+                        //     &mat.positions,
+                        // )
+
+                        // todo!("documentation")
+                        // MouseEventHandler::new::<CompletionTag, _>(mat.candidate_id, cx, |state, _| {
+                        //     let completion_label = HighlightedLabel::new(
+                        //         completion.label.text.clone(),
+                        //         combine_syntax_and_fuzzy_match_highlights(
+                        //             &completion.label.text,
+                        //             style.text.color.into(),
+                        //             styled_runs_for_code_label(&completion.label, &style.syntax),
+                        //             &mat.positions,
+                        //         ),
+                        //     );
+                        //     Text::new(completion.label.text.clone(), style.text.clone())
+                        //         .with_soft_wrap(false)
+                        //         .with_highlights();
+
+                        //     if let Some(Documentation::SingleLine(text)) = documentation {
+                        //         h_stack()
+                        //             .child(completion_label)
+                        //             .with_children((|| {
+                        //                 let text_style = TextStyle {
+                        //                     color: style.autocomplete.inline_docs_color,
+                        //                     font_size: style.text.font_size
+                        //                         * style.autocomplete.inline_docs_size_percent,
+                        //                     ..style.text.clone()
+                        //                 };
+
+                        //                 let label = Text::new(text.clone(), text_style)
+                        //                     .aligned()
+                        //                     .constrained()
+                        //                     .dynamically(move |constraint, _, _| gpui::SizeConstraint {
+                        //                         min: constraint.min,
+                        //                         max: vec2f(constraint.max.x(), constraint.min.y()),
+                        //                     });
+
+                        //                 if Some(item_ix) == widest_completion_ix {
+                        //                     Some(
+                        //                         label
+                        //                             .contained()
+                        //                             .with_style(style.autocomplete.inline_docs_container)
+                        //                             .into_any(),
+                        //                     )
+                        //                 } else {
+                        //                     Some(label.flex_float().into_any())
+                        //                 }
+                        //             })())
+                        //             .into_any()
+                        //     } else {
+                        //         completion_label.into_any()
+                        //     }
+                        //     .contained()
+                        //     .with_style(item_style)
+                        //     .constrained()
+                        //     .dynamically(move |constraint, _, _| {
+                        //         if Some(item_ix) == widest_completion_ix {
+                        //             constraint
+                        //         } else {
+                        //             gpui::SizeConstraint {
+                        //                 min: constraint.min,
+                        //                 max: constraint.min,
+                        //             }
+                        //         }
+                        //     })
+                        // })
+                        // .with_cursor_style(CursorStyle::PointingHand)
+                        // .on_down(MouseButton::Left, move |_, this, cx| {
+                        //     this.confirm_completion(
+                        //         &ConfirmCompletion {
+                        //             item_ix: Some(item_ix),
+                        //         },
+                        //         cx,
+                        //     )
+                        //     .map(|task| task.detach());
+                        // })
+                        // .constrained()
+                        //
+                        div()
+                            .id(mat.candidate_id)
+                            .whitespace_nowrap()
+                            .overflow_hidden()
+                            .bg(gpui::green())
+                            .hover(|style| style.bg(gpui::blue()))
+                            .when(item_ix == selected_item, |div| div.bg(gpui::red()))
+                            .child(SharedString::from(completion.label.text.clone()))
+                            .min_w(px(300.))
+                            .max_w(px(700.))
+                    })
+                    .collect()
+            },
+        )
+        .track_scroll(self.scroll_handle.clone())
+        .with_width_from_item(widest_completion_ix);
+
+        list.render_into_any()
+        // todo!("multiline documentation")
+        //     enum MultiLineDocumentation {}
+
+        //     Flex::row()
+        //         .with_child(list.flex(1., false))
+        //         .with_children({
+        //             let mat = &self.matches[selected_item];
+        //             let completions = self.completions.read();
+        //             let completion = &completions[mat.candidate_id];
+        //             let documentation = &completion.documentation;
+
+        //             match documentation {
+        //                 Some(Documentation::MultiLinePlainText(text)) => Some(
+        //                     Flex::column()
+        //                         .scrollable::<MultiLineDocumentation>(0, None, cx)
+        //                         .with_child(
+        //                             Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
+        //                         )
+        //                         .contained()
+        //                         .with_style(style.autocomplete.alongside_docs_container)
+        //                         .constrained()
+        //                         .with_max_width(style.autocomplete.alongside_docs_max_width)
+        //                         .flex(1., false),
+        //                 ),
+
+        //                 Some(Documentation::MultiLineMarkdown(parsed)) => Some(
+        //                     Flex::column()
+        //                         .scrollable::<MultiLineDocumentation>(0, None, cx)
+        //                         .with_child(render_parsed_markdown::<MultiLineDocumentation>(
+        //                             parsed, &style, workspace, cx,
+        //                         ))
+        //                         .contained()
+        //                         .with_style(style.autocomplete.alongside_docs_container)
+        //                         .constrained()
+        //                         .with_max_width(style.autocomplete.alongside_docs_max_width)
+        //                         .flex(1., false),
+        //                 ),
+
+        //                 _ => None,
+        //             }
+        //         })
+        //         .contained()
+        //         .with_style(style.autocomplete.container)
+        //         .into_any()
+    }
 
     pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
         let mut matches = if let Some(query) = query {
@@ -1541,13 +1535,15 @@ impl CodeActionsMenu {
         mut cursor_position: DisplayPoint,
         style: &EditorStyle,
         cx: &mut ViewContext<Editor>,
-    ) -> (DisplayPoint, AnyElement<Editor>) {
+    ) -> (DisplayPoint, AnyElement) {
         let actions = self.actions.clone();
         let selected_item = self.selected_item;
+
         let element = uniform_list(
+            cx.view().clone(),
             "code_actions_menu",
             self.actions.len(),
-            move |editor, range, cx| {
+            move |this, range, cx| {
                 actions[range.clone()]
                     .iter()
                     .enumerate()
@@ -1569,17 +1565,20 @@ impl CodeActionsMenu {
                                     .bg(colors.element_hover)
                                     .text_color(colors.text_accent)
                             })
-                            .on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| {
-                                cx.stop_propagation();
-                                editor
-                                    .confirm_code_action(
-                                        &ConfirmCodeAction {
-                                            item_ix: Some(item_ix),
-                                        },
-                                        cx,
-                                    )
-                                    .map(|task| task.detach_and_log_err(cx));
-                            })
+                            .on_mouse_down(
+                                MouseButton::Left,
+                                cx.listener(move |editor, _, cx| {
+                                    cx.stop_propagation();
+                                    editor
+                                        .confirm_code_action(
+                                            &ConfirmCodeAction {
+                                                item_ix: Some(item_ix),
+                                            },
+                                            cx,
+                                        )
+                                        .map(|task| task.detach_and_log_err(cx));
+                                }),
+                            )
                             // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
                             .child(SharedString::from(action.lsp_action.title.clone()))
                     })
@@ -1589,6 +1588,7 @@ impl CodeActionsMenu {
         .elevation_1(cx)
         .px_2()
         .py_1()
+        .track_scroll(self.scroll_handle.clone())
         .with_width_from_item(
             self.actions
                 .iter()
@@ -2320,6 +2320,7 @@ impl Editor {
 
         self.blink_manager.update(cx, BlinkManager::pause_blinking);
         cx.emit(EditorEvent::SelectionsChanged { local });
+        cx.emit(SearchEvent::MatchesInvalidated);
 
         if self.selections.disjoint_anchors().len() == 1 {
             cx.emit(SearchEvent::ActiveMatchChanged)
@@ -4354,11 +4355,11 @@ impl Editor {
         style: &EditorStyle,
         is_active: bool,
         cx: &mut ViewContext<Self>,
-    ) -> Option<IconButton<Self>> {
+    ) -> Option<IconButton> {
         if self.available_code_actions.is_some() {
             Some(
-                IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(
-                    |editor: &mut Editor, cx| {
+                IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(cx.listener(
+                    |editor, e, cx| {
                         editor.toggle_code_actions(
                             &ToggleCodeActions {
                                 deployed_from_indicator: true,
@@ -4366,7 +4367,7 @@ impl Editor {
                             cx,
                         );
                     },
-                ),
+                )),
             )
         } else {
             None
@@ -4381,7 +4382,7 @@ impl Editor {
         line_height: Pixels,
         gutter_margin: Pixels,
         cx: &mut ViewContext<Self>,
-    ) -> Vec<Option<IconButton<Self>>> {
+    ) -> Vec<Option<IconButton>> {
         fold_data
             .iter()
             .enumerate()
@@ -4394,15 +4395,15 @@ impl Editor {
                                 FoldStatus::Foldable => ui::Icon::ChevronDown,
                             };
                             IconButton::new(ix as usize, icon)
-                                .on_click(move |editor: &mut Editor, cx| match fold_status {
+                                .on_click(cx.listener(move |editor, e, cx| match fold_status {
                                     FoldStatus::Folded => {
                                         editor.unfold_at(&UnfoldAt { buffer_row }, cx);
                                     }
                                     FoldStatus::Foldable => {
                                         editor.fold_at(&FoldAt { buffer_row }, cx);
                                     }
-                                })
-                                .color(ui::TextColor::Muted)
+                                }))
+                                .color(ui::Color::Muted)
                         })
                     })
                     .flatten()
@@ -4422,7 +4423,7 @@ impl Editor {
         cursor_position: DisplayPoint,
         style: &EditorStyle,
         cx: &mut ViewContext<Editor>,
-    ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
+    ) -> Option<(DisplayPoint, AnyElement)> {
         self.context_menu.read().as_ref().map(|menu| {
             menu.render(
                 cursor_position,
@@ -7781,7 +7782,7 @@ impl Editor {
                                     }
                                     div()
                                         .pl(cx.anchor_x)
-                                        .child(rename_editor.render_with(EditorElement::new(
+                                        .child(EditorElement::new(
                                             &rename_editor,
                                             EditorStyle {
                                                 background: cx.theme().system().transparent,
@@ -7789,10 +7790,12 @@ impl Editor {
                                                 text: text_style,
                                                 scrollbar_width: cx.editor_style.scrollbar_width,
                                                 syntax: cx.editor_style.syntax.clone(),
-                                                diagnostic_style:
-                                                    cx.editor_style.diagnostic_style.clone(),
+                                                diagnostic_style: cx
+                                                    .editor_style
+                                                    .diagnostic_style
+                                                    .clone(),
                                             },
-                                        )))
+                                        ))
                                         .render_into_any()
                                 }
                             }),
@@ -9388,7 +9391,7 @@ impl FocusableView for Editor {
     }
 }
 
-impl Render<Self> for Editor {
+impl Render for Editor {
     type Element = EditorElement;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -9403,6 +9406,7 @@ impl Render<Self> for Editor {
                 font_style: FontStyle::Normal,
                 line_height: relative(1.).into(),
                 underline: None,
+                white_space: WhiteSpace::Normal,
             },
 
             EditorMode::AutoHeight { max_lines } => todo!(),
@@ -9416,6 +9420,7 @@ impl Render<Self> for Editor {
                 font_style: FontStyle::Normal,
                 line_height: relative(settings.buffer_line_height.value()),
                 underline: None,
+                white_space: WhiteSpace::Normal,
             },
         };
 
@@ -10002,10 +10007,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
                     .ml(cx.anchor_x)
             }))
             .cursor_pointer()
-            .on_click(move |_, _, cx| {
+            .on_click(cx.listener(move |_, _, cx| {
                 cx.write_to_clipboard(ClipboardItem::new(message.clone()));
-            })
-            .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx))
+            }))
+            .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx))
             .render_into_any()
     })
 }
@@ -10124,49 +10129,50 @@ pub fn combine_syntax_and_fuzzy_match_highlights(
     result
 }
 
-// pub fn styled_runs_for_code_label<'a>(
-//     label: &'a CodeLabel,
-//     syntax_theme: &'a theme::SyntaxTheme,
-// ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
-//     let fade_out = HighlightStyle {
-//         fade_out: Some(0.35),
-//         ..Default::default()
-//     };
+pub fn styled_runs_for_code_label<'a>(
+    label: &'a CodeLabel,
+    syntax_theme: &'a theme::SyntaxTheme,
+) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
+    let fade_out = HighlightStyle {
+        fade_out: Some(0.35),
+        ..Default::default()
+    };
+
+    let mut prev_end = label.filter_range.end;
+    label
+        .runs
+        .iter()
+        .enumerate()
+        .flat_map(move |(ix, (range, highlight_id))| {
+            let style = if let Some(style) = highlight_id.style(syntax_theme) {
+                style
+            } else {
+                return Default::default();
+            };
+            let mut muted_style = style;
+            muted_style.highlight(fade_out);
 
-//     let mut prev_end = label.filter_range.end;
-//     label
-//         .runs
-//         .iter()
-//         .enumerate()
-//         .flat_map(move |(ix, (range, highlight_id))| {
-//             let style = if let Some(style) = highlight_id.style(syntax_theme) {
-//                 style
-//             } else {
-//                 return Default::default();
-//             };
-//             let mut muted_style = style;
-//             muted_style.highlight(fade_out);
-
-//             let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
-//             if range.start >= label.filter_range.end {
-//                 if range.start > prev_end {
-//                     runs.push((prev_end..range.start, fade_out));
-//                 }
-//                 runs.push((range.clone(), muted_style));
-//             } else if range.end <= label.filter_range.end {
-//                 runs.push((range.clone(), style));
-//             } else {
-//                 runs.push((range.start..label.filter_range.end, style));
-//                 runs.push((label.filter_range.end..range.end, muted_style));
-//             }
-//             prev_end = cmp::max(prev_end, range.end);
+            let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
+            if range.start >= label.filter_range.end {
+                if range.start > prev_end {
+                    runs.push((prev_end..range.start, fade_out));
+                }
+                runs.push((range.clone(), muted_style));
+            } else if range.end <= label.filter_range.end {
+                runs.push((range.clone(), style));
+            } else {
+                runs.push((range.start..label.filter_range.end, style));
+                runs.push((label.filter_range.end..range.end, muted_style));
+            }
+            prev_end = cmp::max(prev_end, range.end);
 
-//             if ix + 1 == label.runs.len() && label.text.len() > prev_end {
-//                 runs.push((prev_end..label.text.len(), fade_out));
-//             }
+            if ix + 1 == label.runs.len() && label.text.len() > prev_end {
+                runs.push((prev_end..label.text.len(), fade_out));
+            }
 
-//             runs
-//         })
+            runs
+        })
+}
 
 pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
     let mut index = 0;

crates/editor2/src/element.rs 🔗

@@ -23,7 +23,7 @@ use gpui::{
     ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, LineLayout,
     MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
     ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
-    TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,
+    TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -112,18 +112,192 @@ impl SelectionLayout {
 }
 
 pub struct EditorElement {
-    editor_id: EntityId,
+    editor: View<Editor>,
     style: EditorStyle,
 }
 
 impl EditorElement {
     pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
         Self {
-            editor_id: editor.entity_id(),
+            editor: editor.clone(),
             style,
         }
     }
 
+    fn register_actions(&self, cx: &mut WindowContext) {
+        let view = &self.editor;
+        register_action(view, cx, Editor::move_left);
+        register_action(view, cx, Editor::move_right);
+        register_action(view, cx, Editor::move_down);
+        register_action(view, cx, Editor::move_up);
+        // on_action(cx, Editor::new_file); todo!()
+        // on_action(cx, Editor::new_file_in_direction); todo!()
+        register_action(view, cx, Editor::cancel);
+        register_action(view, cx, Editor::newline);
+        register_action(view, cx, Editor::newline_above);
+        register_action(view, cx, Editor::newline_below);
+        register_action(view, cx, Editor::backspace);
+        register_action(view, cx, Editor::delete);
+        register_action(view, cx, Editor::tab);
+        register_action(view, cx, Editor::tab_prev);
+        register_action(view, cx, Editor::indent);
+        register_action(view, cx, Editor::outdent);
+        register_action(view, cx, Editor::delete_line);
+        register_action(view, cx, Editor::join_lines);
+        register_action(view, cx, Editor::sort_lines_case_sensitive);
+        register_action(view, cx, Editor::sort_lines_case_insensitive);
+        register_action(view, cx, Editor::reverse_lines);
+        register_action(view, cx, Editor::shuffle_lines);
+        register_action(view, cx, Editor::convert_to_upper_case);
+        register_action(view, cx, Editor::convert_to_lower_case);
+        register_action(view, cx, Editor::convert_to_title_case);
+        register_action(view, cx, Editor::convert_to_snake_case);
+        register_action(view, cx, Editor::convert_to_kebab_case);
+        register_action(view, cx, Editor::convert_to_upper_camel_case);
+        register_action(view, cx, Editor::convert_to_lower_camel_case);
+        register_action(view, cx, Editor::delete_to_previous_word_start);
+        register_action(view, cx, Editor::delete_to_previous_subword_start);
+        register_action(view, cx, Editor::delete_to_next_word_end);
+        register_action(view, cx, Editor::delete_to_next_subword_end);
+        register_action(view, cx, Editor::delete_to_beginning_of_line);
+        register_action(view, cx, Editor::delete_to_end_of_line);
+        register_action(view, cx, Editor::cut_to_end_of_line);
+        register_action(view, cx, Editor::duplicate_line);
+        register_action(view, cx, Editor::move_line_up);
+        register_action(view, cx, Editor::move_line_down);
+        register_action(view, cx, Editor::transpose);
+        register_action(view, cx, Editor::cut);
+        register_action(view, cx, Editor::copy);
+        register_action(view, cx, Editor::paste);
+        register_action(view, cx, Editor::undo);
+        register_action(view, cx, Editor::redo);
+        register_action(view, cx, Editor::move_page_up);
+        register_action(view, cx, Editor::move_page_down);
+        register_action(view, cx, Editor::next_screen);
+        register_action(view, cx, Editor::scroll_cursor_top);
+        register_action(view, cx, Editor::scroll_cursor_center);
+        register_action(view, cx, Editor::scroll_cursor_bottom);
+        register_action(view, cx, |editor, _: &LineDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Line(1.), cx)
+        });
+        register_action(view, cx, |editor, _: &LineUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
+        });
+        register_action(view, cx, |editor, _: &HalfPageDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
+        });
+        register_action(view, cx, |editor, _: &HalfPageUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+        });
+        register_action(view, cx, |editor, _: &PageDown, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(1.), cx)
+        });
+        register_action(view, cx, |editor, _: &PageUp, cx| {
+            editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
+        });
+        register_action(view, cx, Editor::move_to_previous_word_start);
+        register_action(view, cx, Editor::move_to_previous_subword_start);
+        register_action(view, cx, Editor::move_to_next_word_end);
+        register_action(view, cx, Editor::move_to_next_subword_end);
+        register_action(view, cx, Editor::move_to_beginning_of_line);
+        register_action(view, cx, Editor::move_to_end_of_line);
+        register_action(view, cx, Editor::move_to_start_of_paragraph);
+        register_action(view, cx, Editor::move_to_end_of_paragraph);
+        register_action(view, cx, Editor::move_to_beginning);
+        register_action(view, cx, Editor::move_to_end);
+        register_action(view, cx, Editor::select_up);
+        register_action(view, cx, Editor::select_down);
+        register_action(view, cx, Editor::select_left);
+        register_action(view, cx, Editor::select_right);
+        register_action(view, cx, Editor::select_to_previous_word_start);
+        register_action(view, cx, Editor::select_to_previous_subword_start);
+        register_action(view, cx, Editor::select_to_next_word_end);
+        register_action(view, cx, Editor::select_to_next_subword_end);
+        register_action(view, cx, Editor::select_to_beginning_of_line);
+        register_action(view, cx, Editor::select_to_end_of_line);
+        register_action(view, cx, Editor::select_to_start_of_paragraph);
+        register_action(view, cx, Editor::select_to_end_of_paragraph);
+        register_action(view, cx, Editor::select_to_beginning);
+        register_action(view, cx, Editor::select_to_end);
+        register_action(view, cx, Editor::select_all);
+        register_action(view, cx, |editor, action, cx| {
+            editor.select_all_matches(action, cx).log_err();
+        });
+        register_action(view, cx, Editor::select_line);
+        register_action(view, cx, Editor::split_selection_into_lines);
+        register_action(view, cx, Editor::add_selection_above);
+        register_action(view, cx, Editor::add_selection_below);
+        register_action(view, cx, |editor, action, cx| {
+            editor.select_next(action, cx).log_err();
+        });
+        register_action(view, cx, |editor, action, cx| {
+            editor.select_previous(action, cx).log_err();
+        });
+        register_action(view, cx, Editor::toggle_comments);
+        register_action(view, cx, Editor::select_larger_syntax_node);
+        register_action(view, cx, Editor::select_smaller_syntax_node);
+        register_action(view, cx, Editor::move_to_enclosing_bracket);
+        register_action(view, cx, Editor::undo_selection);
+        register_action(view, cx, Editor::redo_selection);
+        register_action(view, cx, Editor::go_to_diagnostic);
+        register_action(view, cx, Editor::go_to_prev_diagnostic);
+        register_action(view, cx, Editor::go_to_hunk);
+        register_action(view, cx, Editor::go_to_prev_hunk);
+        register_action(view, cx, Editor::go_to_definition);
+        register_action(view, cx, Editor::go_to_definition_split);
+        register_action(view, cx, Editor::go_to_type_definition);
+        register_action(view, cx, Editor::go_to_type_definition_split);
+        register_action(view, cx, Editor::fold);
+        register_action(view, cx, Editor::fold_at);
+        register_action(view, cx, Editor::unfold_lines);
+        register_action(view, cx, Editor::unfold_at);
+        register_action(view, cx, Editor::fold_selected_ranges);
+        register_action(view, cx, Editor::show_completions);
+        register_action(view, cx, Editor::toggle_code_actions);
+        // on_action(cx, Editor::open_excerpts); todo!()
+        register_action(view, cx, Editor::toggle_soft_wrap);
+        register_action(view, cx, Editor::toggle_inlay_hints);
+        register_action(view, cx, Editor::reveal_in_finder);
+        register_action(view, cx, Editor::copy_path);
+        register_action(view, cx, Editor::copy_relative_path);
+        register_action(view, cx, Editor::copy_highlight_json);
+        register_action(view, cx, |editor, action, cx| {
+            editor
+                .format(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        });
+        register_action(view, cx, Editor::restart_language_server);
+        register_action(view, cx, Editor::show_character_palette);
+        // on_action(cx, Editor::confirm_completion); todo!()
+        register_action(view, cx, |editor, action, cx| {
+            editor
+                .confirm_code_action(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        });
+        register_action(view, cx, |editor, action, cx| {
+            editor
+                .rename(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        });
+        register_action(view, cx, |editor, action, cx| {
+            editor
+                .confirm_rename(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        });
+        register_action(view, cx, |editor, action, cx| {
+            editor
+                .find_all_references(action, cx)
+                .map(|task| task.detach_and_log_err(cx));
+        });
+        register_action(view, cx, Editor::next_copilot_suggestion);
+        register_action(view, cx, Editor::previous_copilot_suggestion);
+        register_action(view, cx, Editor::copilot_suggest);
+        register_action(view, cx, Editor::context_menu_first);
+        register_action(view, cx, Editor::context_menu_prev);
+        register_action(view, cx, Editor::context_menu_next);
+        register_action(view, cx, Editor::context_menu_last);
+    }
+
     fn mouse_down(
         editor: &mut Editor,
         event: &MouseDownEvent,
@@ -349,7 +523,7 @@ impl EditorElement {
         gutter_bounds: Bounds<Pixels>,
         text_bounds: Bounds<Pixels>,
         layout: &LayoutState,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
         let bounds = gutter_bounds.union(&text_bounds);
         let scroll_top =
@@ -459,8 +633,7 @@ impl EditorElement {
         &mut self,
         bounds: Bounds<Pixels>,
         layout: &mut LayoutState,
-        editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
         let line_height = layout.position_map.line_height;
 
@@ -495,7 +668,7 @@ impl EditorElement {
                     AvailableSpace::MinContent,
                     AvailableSpace::Definite(line_height * 0.55),
                 );
-                let fold_indicator_size = fold_indicator.measure(available_space, editor, cx);
+                let fold_indicator_size = fold_indicator.measure(available_space, cx);
 
                 let position = point(
                     bounds.size.width - layout.gutter_padding,
@@ -506,7 +679,7 @@ impl EditorElement {
                     (line_height - fold_indicator_size.height) / 2.,
                 );
                 let origin = bounds.origin + position + centering_offset;
-                fold_indicator.draw(origin, available_space, editor, cx);
+                fold_indicator.draw(origin, available_space, cx);
             }
         }
 
@@ -516,7 +689,7 @@ impl EditorElement {
                 AvailableSpace::MinContent,
                 AvailableSpace::Definite(line_height),
             );
-            let indicator_size = button.measure(available_space, editor, cx);
+            let indicator_size = button.measure(available_space, cx);
 
             let mut x = Pixels::ZERO;
             let mut y = indicator.row as f32 * line_height - scroll_top;
@@ -524,15 +697,11 @@ impl EditorElement {
             x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
             y += (line_height - indicator_size.height) / 2.;
 
-            button.draw(bounds.origin + point(x, y), available_space, editor, cx);
+            button.draw(bounds.origin + point(x, y), available_space, cx);
         }
     }
 
-    fn paint_diff_hunks(
-        bounds: Bounds<Pixels>,
-        layout: &LayoutState,
-        cx: &mut ViewContext<Editor>,
-    ) {
+    fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
         // todo!()
         // let diff_style = &theme::current(cx).editor.diff.clone();
         // let line_height = layout.position_map.line_height;
@@ -620,14 +789,19 @@ impl EditorElement {
         &mut self,
         text_bounds: Bounds<Pixels>,
         layout: &mut LayoutState,
-        editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
         let scroll_position = layout.position_map.snapshot.scroll_position();
         let start_row = layout.visible_display_row_range.start;
         let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
         let line_end_overshoot = 0.15 * layout.position_map.line_height;
-        let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
+        let whitespace_setting = self
+            .editor
+            .read(cx)
+            .buffer
+            .read(cx)
+            .settings_at(0, cx)
+            .show_whitespaces;
 
         cx.with_content_mask(
             Some(ContentMask {
@@ -676,20 +850,22 @@ impl EditorElement {
                             div()
                                 .id(fold.id)
                                 .size_full()
-                                .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
-                                .on_click(move |editor: &mut Editor, _, cx| {
-                                    editor.unfold_ranges(
-                                        [fold_range.start..fold_range.end],
-                                        true,
-                                        false,
-                                        cx,
-                                    );
-                                    cx.stop_propagation();
-                                })
+                                .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
+                                .on_click(cx.listener_for(
+                                    &self.editor,
+                                    move |editor: &mut Editor, _, cx| {
+                                        editor.unfold_ranges(
+                                            [fold_range.start..fold_range.end],
+                                            true,
+                                            false,
+                                            cx,
+                                        );
+                                        cx.stop_propagation();
+                                    },
+                                ))
                                 .draw(
                                     fold_bounds.origin,
                                     fold_bounds.size,
-                                    editor,
                                     cx,
                                     |fold_element_state, cx| {
                                         if fold_element_state.is_active() {
@@ -750,7 +926,7 @@ impl EditorElement {
                             invisible_display_ranges.push(selection.range.clone());
                         }
 
-                        if !selection.is_local || editor.show_local_cursors(cx) {
+                        if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
                             let cursor_position = selection.head;
                             if layout
                                 .visible_display_row_range
@@ -802,12 +978,14 @@ impl EditorElement {
                                     * layout.position_map.line_height
                                     - layout.position_map.scroll_position.y;
                                 if selection.is_newest {
-                                    editor.pixel_position_of_newest_cursor = Some(point(
-                                        text_bounds.origin.x + x + block_width / 2.,
-                                        text_bounds.origin.y
-                                            + y
-                                            + layout.position_map.line_height / 2.,
-                                    ));
+                                    self.editor.update(cx, |editor, _| {
+                                        editor.pixel_position_of_newest_cursor = Some(point(
+                                            text_bounds.origin.x + x + block_width / 2.,
+                                            text_bounds.origin.y
+                                                + y
+                                                + layout.position_map.line_height / 2.,
+                                        ))
+                                    });
                                 }
                                 cursors.push(Cursor {
                                     color: selection_style.cursor,
@@ -852,7 +1030,7 @@ impl EditorElement {
                                     .min((text_bounds.size.height - line_height) / 2.),
                             ),
                         );
-                        let context_menu_size = context_menu.measure(available_space, editor, cx);
+                        let context_menu_size = context_menu.measure(available_space, cx);
 
                         let cursor_row_layout = &layout.position_map.line_layouts
                             [(position.row() - start_row) as usize]
@@ -876,7 +1054,7 @@ impl EditorElement {
                             list_origin.y -= layout.position_map.line_height - list_height;
                         }
 
-                        context_menu.draw(list_origin, available_space, editor, cx);
+                        context_menu.draw(list_origin, available_space, cx);
                     })
                 }
 
@@ -1167,7 +1345,7 @@ impl EditorElement {
         layout: &LayoutState,
         content_origin: gpui::Point<Pixels>,
         bounds: Bounds<Pixels>,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
         let start_row = layout.visible_display_row_range.start;
         let end_row = layout.visible_display_row_range.end;
@@ -1219,8 +1397,7 @@ impl EditorElement {
         &mut self,
         bounds: Bounds<Pixels>,
         layout: &mut LayoutState,
-        editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut WindowContext,
     ) {
         let scroll_position = layout.position_map.snapshot.scroll_position();
         let scroll_left = scroll_position.x * layout.position_map.em_width;
@@ -1235,13 +1412,11 @@ impl EditorElement {
             if !matches!(block.style, BlockStyle::Sticky) {
                 origin += point(-scroll_left, Pixels::ZERO);
             }
-            block
-                .element
-                .draw(origin, block.available_space, editor, cx);
+            block.element.draw(origin, block.available_space, cx);
         }
     }
 
-    fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
+    fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
         let style = &self.style;
         let font_size = style.text.font_size.to_pixels(cx.rem_size());
         let layout = cx
@@ -1262,7 +1437,7 @@ impl EditorElement {
         layout.width
     }
 
-    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {
+    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
         let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
         self.column_pixels(digit_count, cx)
     }
@@ -1417,7 +1592,7 @@ impl EditorElement {
     }
 
     fn layout_lines(
-        &mut self,
+        &self,
         rows: Range<u32>,
         line_number_layouts: &[Option<ShapedLine>],
         snapshot: &EditorSnapshot,
@@ -1473,483 +1648,469 @@ impl EditorElement {
 
     fn compute_layout(
         &mut self,
-        editor: &mut Editor,
-        cx: &mut ViewContext<'_, Editor>,
         mut bounds: Bounds<Pixels>,
+        cx: &mut WindowContext,
     ) -> LayoutState {
-        // let mut size = constraint.max;
-        // if size.x.is_infinite() {
-        //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-        // }
-
-        let snapshot = editor.snapshot(cx);
-        let style = self.style.clone();
+        self.editor.update(cx, |editor, cx| {
+            // let mut size = constraint.max;
+            // if size.x.is_infinite() {
+            //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
+            // }
+
+            let snapshot = editor.snapshot(cx);
+            let style = self.style.clone();
+
+            let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+            let font_size = style.text.font_size.to_pixels(cx.rem_size());
+            let line_height = style.text.line_height_in_pixels(cx.rem_size());
+            let em_width = cx
+                .text_system()
+                .typographic_bounds(font_id, font_size, 'm')
+                .unwrap()
+                .size
+                .width;
+            let em_advance = cx
+                .text_system()
+                .advance(font_id, font_size, 'm')
+                .unwrap()
+                .width;
+
+            let gutter_padding;
+            let gutter_width;
+            let gutter_margin;
+            if snapshot.show_gutter {
+                let descent = cx.text_system().descent(font_id, font_size).unwrap();
+
+                let gutter_padding_factor = 3.5;
+                gutter_padding = (em_width * gutter_padding_factor).round();
+                gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
+                gutter_margin = -descent;
+            } else {
+                gutter_padding = Pixels::ZERO;
+                gutter_width = Pixels::ZERO;
+                gutter_margin = Pixels::ZERO;
+            };
 
-        let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
-        let font_size = style.text.font_size.to_pixels(cx.rem_size());
-        let line_height = style.text.line_height_in_pixels(cx.rem_size());
-        let em_width = cx
-            .text_system()
-            .typographic_bounds(font_id, font_size, 'm')
-            .unwrap()
-            .size
-            .width;
-        let em_advance = cx
-            .text_system()
-            .advance(font_id, font_size, 'm')
-            .unwrap()
-            .width;
-
-        let gutter_padding;
-        let gutter_width;
-        let gutter_margin;
-        if snapshot.show_gutter {
-            let descent = cx.text_system().descent(font_id, font_size).unwrap();
-
-            let gutter_padding_factor = 3.5;
-            gutter_padding = (em_width * gutter_padding_factor).round();
-            gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
-            gutter_margin = -descent;
-        } else {
-            gutter_padding = Pixels::ZERO;
-            gutter_width = Pixels::ZERO;
-            gutter_margin = Pixels::ZERO;
-        };
+            editor.gutter_width = gutter_width;
+            let text_width = bounds.size.width - gutter_width;
+            let overscroll = size(em_width, px(0.));
+            let snapshot = {
+                editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
+
+                let editor_width = text_width - gutter_margin - overscroll.width - em_width;
+                let wrap_width = match editor.soft_wrap_mode(cx) {
+                    SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
+                    SoftWrap::EditorWidth => editor_width,
+                    SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
+                };
 
-        editor.gutter_width = gutter_width;
-        let text_width = bounds.size.width - gutter_width;
-        let overscroll = size(em_width, px(0.));
-        let snapshot = {
-            editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
-
-            let editor_width = text_width - gutter_margin - overscroll.width - em_width;
-            let wrap_width = match editor.soft_wrap_mode(cx) {
-                SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
-                SoftWrap::EditorWidth => editor_width,
-                SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
+                if editor.set_wrap_width(Some(wrap_width), cx) {
+                    editor.snapshot(cx)
+                } else {
+                    snapshot
+                }
             };
 
-            if editor.set_wrap_width(Some(wrap_width), cx) {
-                editor.snapshot(cx)
+            let wrap_guides = editor
+                .wrap_guides(cx)
+                .iter()
+                .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
+                .collect::<SmallVec<[_; 2]>>();
+
+            let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
+            // todo!("this should happen during layout")
+            let editor_mode = snapshot.mode;
+            if let EditorMode::AutoHeight { max_lines } = editor_mode {
+                todo!()
+                //     size.set_y(
+                //         scroll_height
+                //             .min(constraint.max_along(Axis::Vertical))
+                //             .max(constraint.min_along(Axis::Vertical))
+                //             .max(line_height)
+                //             .min(line_height * max_lines as f32),
+                //     )
+            } else if let EditorMode::SingleLine = editor_mode {
+                bounds.size.height = line_height.min(bounds.size.height);
+            }
+            // todo!()
+            // else if size.y.is_infinite() {
+            //     //     size.set_y(scroll_height);
+            // }
+            //
+            let gutter_size = size(gutter_width, bounds.size.height);
+            let text_size = size(text_width, bounds.size.height);
+
+            let autoscroll_horizontally =
+                editor.autoscroll_vertically(bounds.size.height, line_height, cx);
+            let mut snapshot = editor.snapshot(cx);
+
+            let scroll_position = snapshot.scroll_position();
+            // The scroll position is a fractional point, the whole number of which represents
+            // the top of the window in terms of display rows.
+            let start_row = scroll_position.y as u32;
+            let height_in_lines = f32::from(bounds.size.height / line_height);
+            let max_row = snapshot.max_point().row();
+
+            // Add 1 to ensure selections bleed off screen
+            let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
+
+            let start_anchor = if start_row == 0 {
+                Anchor::min()
             } else {
                 snapshot
-            }
-        };
+                    .buffer_snapshot
+                    .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
+            };
+            let end_anchor = if end_row > max_row {
+                Anchor::max()
+            } else {
+                snapshot
+                    .buffer_snapshot
+                    .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
+            };
 
-        let wrap_guides = editor
-            .wrap_guides(cx)
-            .iter()
-            .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
-            .collect::<SmallVec<[_; 2]>>();
-
-        let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
-        // todo!("this should happen during layout")
-        let editor_mode = snapshot.mode;
-        if let EditorMode::AutoHeight { max_lines } = editor_mode {
-            todo!()
-            //     size.set_y(
-            //         scroll_height
-            //             .min(constraint.max_along(Axis::Vertical))
-            //             .max(constraint.min_along(Axis::Vertical))
-            //             .max(line_height)
-            //             .min(line_height * max_lines as f32),
-            //     )
-        } else if let EditorMode::SingleLine = editor_mode {
-            bounds.size.height = line_height.min(bounds.size.height);
-        }
-        // todo!()
-        // else if size.y.is_infinite() {
-        //     //     size.set_y(scroll_height);
-        // }
-        //
-        let gutter_size = size(gutter_width, bounds.size.height);
-        let text_size = size(text_width, bounds.size.height);
-
-        let autoscroll_horizontally =
-            editor.autoscroll_vertically(bounds.size.height, line_height, cx);
-        let mut snapshot = editor.snapshot(cx);
-
-        let scroll_position = snapshot.scroll_position();
-        // The scroll position is a fractional point, the whole number of which represents
-        // the top of the window in terms of display rows.
-        let start_row = scroll_position.y as u32;
-        let height_in_lines = f32::from(bounds.size.height / line_height);
-        let max_row = snapshot.max_point().row();
-
-        // Add 1 to ensure selections bleed off screen
-        let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
-
-        let start_anchor = if start_row == 0 {
-            Anchor::min()
-        } else {
-            snapshot
-                .buffer_snapshot
-                .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
-        };
-        let end_anchor = if end_row > max_row {
-            Anchor::max()
-        } else {
-            snapshot
-                .buffer_snapshot
-                .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
-        };
+            let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
+            let mut active_rows = BTreeMap::new();
+            let is_singleton = editor.is_singleton(cx);
 
-        let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
-        let mut active_rows = BTreeMap::new();
-        let is_singleton = editor.is_singleton(cx);
+            let highlighted_rows = editor.highlighted_rows();
+            let highlighted_ranges = editor.background_highlights_in_range(
+                start_anchor..end_anchor,
+                &snapshot.display_snapshot,
+                cx.theme().colors(),
+            );
 
-        let highlighted_rows = editor.highlighted_rows();
-        let highlighted_ranges = editor.background_highlights_in_range(
-            start_anchor..end_anchor,
-            &snapshot.display_snapshot,
-            cx.theme().colors(),
-        );
+            let mut newest_selection_head = None;
+
+            if editor.show_local_selections {
+                let mut local_selections: Vec<Selection<Point>> = editor
+                    .selections
+                    .disjoint_in_range(start_anchor..end_anchor, cx);
+                local_selections.extend(editor.selections.pending(cx));
+                let mut layouts = Vec::new();
+                let newest = editor.selections.newest(cx);
+                for selection in local_selections.drain(..) {
+                    let is_empty = selection.start == selection.end;
+                    let is_newest = selection == newest;
+
+                    let layout = SelectionLayout::new(
+                        selection,
+                        editor.selections.line_mode,
+                        editor.cursor_shape,
+                        &snapshot.display_snapshot,
+                        is_newest,
+                        true,
+                    );
+                    if is_newest {
+                        newest_selection_head = Some(layout.head);
+                    }
 
-        let mut newest_selection_head = None;
-
-        if editor.show_local_selections {
-            let mut local_selections: Vec<Selection<Point>> = editor
-                .selections
-                .disjoint_in_range(start_anchor..end_anchor, cx);
-            local_selections.extend(editor.selections.pending(cx));
-            let mut layouts = Vec::new();
-            let newest = editor.selections.newest(cx);
-            for selection in local_selections.drain(..) {
-                let is_empty = selection.start == selection.end;
-                let is_newest = selection == newest;
-
-                let layout = SelectionLayout::new(
-                    selection,
-                    editor.selections.line_mode,
-                    editor.cursor_shape,
-                    &snapshot.display_snapshot,
-                    is_newest,
-                    true,
-                );
-                if is_newest {
-                    newest_selection_head = Some(layout.head);
+                    for row in cmp::max(layout.active_rows.start, start_row)
+                        ..=cmp::min(layout.active_rows.end, end_row)
+                    {
+                        let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
+                        *contains_non_empty_selection |= !is_empty;
+                    }
+                    layouts.push(layout);
                 }
 
-                for row in cmp::max(layout.active_rows.start, start_row)
-                    ..=cmp::min(layout.active_rows.end, end_row)
-                {
-                    let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
-                    *contains_non_empty_selection |= !is_empty;
-                }
-                layouts.push(layout);
+                selections.push((style.local_player, layouts));
             }
 
-            selections.push((style.local_player, layouts));
-        }
-
-        if let Some(collaboration_hub) = &editor.collaboration_hub {
-            // When following someone, render the local selections in their color.
-            if let Some(leader_id) = editor.leader_peer_id {
-                if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
-                    if let Some(participant_index) = collaboration_hub
-                        .user_participant_indices(cx)
-                        .get(&collaborator.user_id)
-                    {
-                        if let Some((local_selection_style, _)) = selections.first_mut() {
-                            *local_selection_style = cx
-                                .theme()
-                                .players()
-                                .color_for_participant(participant_index.0);
+            if let Some(collaboration_hub) = &editor.collaboration_hub {
+                // When following someone, render the local selections in their color.
+                if let Some(leader_id) = editor.leader_peer_id {
+                    if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
+                        if let Some(participant_index) = collaboration_hub
+                            .user_participant_indices(cx)
+                            .get(&collaborator.user_id)
+                        {
+                            if let Some((local_selection_style, _)) = selections.first_mut() {
+                                *local_selection_style = cx
+                                    .theme()
+                                    .players()
+                                    .color_for_participant(participant_index.0);
+                            }
                         }
                     }
                 }
-            }
 
-            let mut remote_selections = HashMap::default();
-            for selection in snapshot.remote_selections_in_range(
-                &(start_anchor..end_anchor),
-                collaboration_hub.as_ref(),
-                cx,
-            ) {
-                let selection_style = if let Some(participant_index) = selection.participant_index {
-                    cx.theme()
-                        .players()
-                        .color_for_participant(participant_index.0)
-                } else {
-                    cx.theme().players().absent()
-                };
-
-                // Don't re-render the leader's selections, since the local selections
-                // match theirs.
-                if Some(selection.peer_id) == editor.leader_peer_id {
-                    continue;
-                }
+                let mut remote_selections = HashMap::default();
+                for selection in snapshot.remote_selections_in_range(
+                    &(start_anchor..end_anchor),
+                    collaboration_hub.as_ref(),
+                    cx,
+                ) {
+                    let selection_style = if let Some(participant_index) = selection.participant_index {
+                        cx.theme()
+                            .players()
+                            .color_for_participant(participant_index.0)
+                    } else {
+                        cx.theme().players().absent()
+                    };
 
-                remote_selections
-                    .entry(selection.replica_id)
-                    .or_insert((selection_style, Vec::new()))
-                    .1
-                    .push(SelectionLayout::new(
-                        selection.selection,
-                        selection.line_mode,
-                        selection.cursor_shape,
-                        &snapshot.display_snapshot,
-                        false,
-                        false,
-                    ));
-            }
+                    // Don't re-render the leader's selections, since the local selections
+                    // match theirs.
+                    if Some(selection.peer_id) == editor.leader_peer_id {
+                        continue;
+                    }
 
-            selections.extend(remote_selections.into_values());
-        }
+                    remote_selections
+                        .entry(selection.replica_id)
+                        .or_insert((selection_style, Vec::new()))
+                        .1
+                        .push(SelectionLayout::new(
+                            selection.selection,
+                            selection.line_mode,
+                            selection.cursor_shape,
+                            &snapshot.display_snapshot,
+                            false,
+                            false,
+                        ));
+                }
 
-        let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
-        let show_scrollbars = match scrollbar_settings.show {
-            ShowScrollbar::Auto => {
-                // Git
-                (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
-                ||
-                // Selections
-                (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
-                // Scrollmanager
-                || editor.scroll_manager.scrollbars_visible()
+                selections.extend(remote_selections.into_values());
             }
-            ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
-            ShowScrollbar::Always => true,
-            ShowScrollbar::Never => false,
-        };
-
-        let head_for_relative = newest_selection_head.unwrap_or_else(|| {
-            let newest = editor.selections.newest::<Point>(cx);
-            SelectionLayout::new(
-                newest,
-                editor.selections.line_mode,
-                editor.cursor_shape,
-                &snapshot.display_snapshot,
-                true,
-                true,
-            )
-            .head
-        });
-
-        let (line_numbers, fold_statuses) = self.shape_line_numbers(
-            start_row..end_row,
-            &active_rows,
-            head_for_relative,
-            is_singleton,
-            &snapshot,
-            cx,
-        );
-
-        let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
-
-        let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
 
-        let mut max_visible_line_width = Pixels::ZERO;
-        let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
-        for line_with_invisibles in &line_layouts {
-            if line_with_invisibles.line.width > max_visible_line_width {
-                max_visible_line_width = line_with_invisibles.line.width;
-            }
-        }
+            let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
+            let show_scrollbars = match scrollbar_settings.show {
+                ShowScrollbar::Auto => {
+                    // Git
+                    (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
+                    ||
+                    // Selections
+                    (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
+                    // Scrollmanager
+                    || editor.scroll_manager.scrollbars_visible()
+                }
+                ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
+                ShowScrollbar::Always => true,
+                ShowScrollbar::Never => false,
+            };
 
-        let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
-            .unwrap()
-            .width;
-        let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
+            let head_for_relative = newest_selection_head.unwrap_or_else(|| {
+                let newest = editor.selections.newest::<Point>(cx);
+                SelectionLayout::new(
+                    newest,
+                    editor.selections.line_mode,
+                    editor.cursor_shape,
+                    &snapshot.display_snapshot,
+                    true,
+                    true,
+                )
+                .head
+            });
 
-        let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
-            self.layout_blocks(
+            let (line_numbers, fold_statuses) = self.shape_line_numbers(
                 start_row..end_row,
+                &active_rows,
+                head_for_relative,
+                is_singleton,
                 &snapshot,
-                bounds.size.width,
-                scroll_width,
-                gutter_padding,
-                gutter_width,
-                em_width,
-                gutter_width + gutter_margin,
-                line_height,
-                &style,
-                &line_layouts,
-                editor,
                 cx,
-            )
-        });
+            );
 
-        let scroll_max = point(
-            f32::from((scroll_width - text_size.width) / em_width).max(0.0),
-            max_row as f32,
-        );
+            let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
 
-        let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
+            let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
 
-        let autoscrolled = if autoscroll_horizontally {
-            editor.autoscroll_horizontally(
-                start_row,
-                text_size.width,
-                scroll_width,
-                em_width,
-                &line_layouts,
-                cx,
-            )
-        } else {
-            false
-        };
+            let mut max_visible_line_width = Pixels::ZERO;
+            let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
+            for line_with_invisibles in &line_layouts {
+                if line_with_invisibles.line.width > max_visible_line_width {
+                    max_visible_line_width = line_with_invisibles.line.width;
+                }
+            }
 
-        if clamped || autoscrolled {
-            snapshot = editor.snapshot(cx);
-        }
+            let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
+                .unwrap()
+                .width;
+            let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
 
-        let mut context_menu = None;
-        let mut code_actions_indicator = None;
-        if let Some(newest_selection_head) = newest_selection_head {
-            if (start_row..end_row).contains(&newest_selection_head.row()) {
-                if editor.context_menu_visible() {
-                    context_menu =
-                        editor.render_context_menu(newest_selection_head, &self.style, cx);
-                }
+            let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
+                self.layout_blocks(
+                    start_row..end_row,
+                    &snapshot,
+                    bounds.size.width,
+                    scroll_width,
+                    gutter_padding,
+                    gutter_width,
+                    em_width,
+                    gutter_width + gutter_margin,
+                    line_height,
+                    &style,
+                    &line_layouts,
+                    editor,
+                    cx,
+                )
+            });
 
-                let active = matches!(
-                    editor.context_menu.read().as_ref(),
-                    Some(crate::ContextMenu::CodeActions(_))
-                );
+            let scroll_max = point(
+                f32::from((scroll_width - text_size.width) / em_width).max(0.0),
+                max_row as f32,
+            );
 
-                code_actions_indicator = editor
-                    .render_code_actions_indicator(&style, active, cx)
-                    .map(|element| CodeActionsIndicator {
-                        row: newest_selection_head.row(),
-                        button: element,
-                    });
+            let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
+
+            let autoscrolled = if autoscroll_horizontally {
+                editor.autoscroll_horizontally(
+                    start_row,
+                    text_size.width,
+                    scroll_width,
+                    em_width,
+                    &line_layouts,
+                    cx,
+                )
+            } else {
+                false
+            };
+
+            if clamped || autoscrolled {
+                snapshot = editor.snapshot(cx);
             }
-        }
 
-        let visible_rows = start_row..start_row + line_layouts.len() as u32;
-        // todo!("hover")
-        // let mut hover = editor.hover_state.render(
-        //     &snapshot,
-        //     &style,
-        //     visible_rows,
-        //     editor.workspace.as_ref().map(|(w, _)| w.clone()),
-        //     cx,
-        // );
-        // let mode = editor.mode;
-
-        let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
-            editor.render_fold_indicators(
-                fold_statuses,
-                &style,
-                editor.gutter_hovered,
-                line_height,
-                gutter_margin,
-                cx,
-            )
-        });
+            let mut context_menu = None;
+            let mut code_actions_indicator = None;
+            if let Some(newest_selection_head) = newest_selection_head {
+                if (start_row..end_row).contains(&newest_selection_head.row()) {
+                    if editor.context_menu_visible() {
+                        context_menu =
+                            editor.render_context_menu(newest_selection_head, &self.style, cx);
+                    }
 
-        // todo!("context_menu")
-        // if let Some((_, context_menu)) = context_menu.as_mut() {
-        //     context_menu.layout(
-        //         SizeConstraint {
-        //             min: gpui::Point::<Pixels>::zero(),
-        //             max: point(
-        //                 cx.window_size().x * 0.7,
-        //                 (12. * line_height).min((size.y - line_height) / 2.),
-        //             ),
-        //         },
-        //         editor,
-        //         cx,
-        //     );
-        // }
+                    let active = matches!(
+                        editor.context_menu.read().as_ref(),
+                        Some(crate::ContextMenu::CodeActions(_))
+                    );
 
-        // todo!("hover popovers")
-        // if let Some((_, hover_popovers)) = hover.as_mut() {
-        //     for hover_popover in hover_popovers.iter_mut() {
-        //         hover_popover.layout(
-        //             SizeConstraint {
-        //                 min: gpui::Point::<Pixels>::zero(),
-        //                 max: point(
-        //                     (120. * em_width) // Default size
-        //                         .min(size.x / 2.) // Shrink to half of the editor width
-        //                         .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
-        //                     (16. * line_height) // Default size
-        //                         .min(size.y / 2.) // Shrink to half of the editor height
-        //                         .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
-        //                 ),
-        //             },
-        //             editor,
-        //             cx,
-        //         );
-        //     }
-        // }
+                    code_actions_indicator = editor
+                        .render_code_actions_indicator(&style, active, cx)
+                        .map(|element| CodeActionsIndicator {
+                            row: newest_selection_head.row(),
+                            button: element,
+                        });
+                }
+            }
 
-        let invisible_symbol_font_size = font_size / 2.;
-        let tab_invisible = cx
-            .text_system()
-            .shape_line(
-                "→".into(),
-                invisible_symbol_font_size,
-                &[TextRun {
-                    len: "→".len(),
-                    font: self.style.text.font(),
-                    color: cx.theme().colors().editor_invisible,
-                    background_color: None,
-                    underline: None,
-                }],
-            )
-            .unwrap();
-        let space_invisible = cx
-            .text_system()
-            .shape_line(
-                "•".into(),
-                invisible_symbol_font_size,
-                &[TextRun {
-                    len: "•".len(),
-                    font: self.style.text.font(),
-                    color: cx.theme().colors().editor_invisible,
-                    background_color: None,
-                    underline: None,
-                }],
-            )
-            .unwrap();
+            let visible_rows = start_row..start_row + line_layouts.len() as u32;
+            // todo!("hover")
+            // let mut hover = editor.hover_state.render(
+            //     &snapshot,
+            //     &style,
+            //     visible_rows,
+            //     editor.workspace.as_ref().map(|(w, _)| w.clone()),
+            //     cx,
+            // );
+            // let mode = editor.mode;
+
+            let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
+                editor.render_fold_indicators(
+                    fold_statuses,
+                    &style,
+                    editor.gutter_hovered,
+                    line_height,
+                    gutter_margin,
+                    cx,
+                )
+            });
 
-        LayoutState {
-            mode: editor_mode,
-            position_map: Arc::new(PositionMap {
-                size: bounds.size,
-                scroll_position: point(
-                    scroll_position.x * em_width,
-                    scroll_position.y * line_height,
-                ),
-                scroll_max,
-                line_layouts,
-                line_height,
-                em_width,
-                em_advance,
-                snapshot,
-            }),
-            visible_anchor_range: start_anchor..end_anchor,
-            visible_display_row_range: start_row..end_row,
-            wrap_guides,
-            gutter_size,
-            gutter_padding,
-            text_size,
-            scrollbar_row_range,
-            show_scrollbars,
-            is_singleton,
-            max_row,
-            gutter_margin,
-            active_rows,
-            highlighted_rows,
-            highlighted_ranges,
-            line_numbers,
-            display_hunks,
-            blocks,
-            selections,
-            context_menu,
-            code_actions_indicator,
-            fold_indicators,
-            tab_invisible,
-            space_invisible,
-            // hover_popovers: hover,
-        }
+            // todo!("hover popovers")
+            // if let Some((_, hover_popovers)) = hover.as_mut() {
+            //     for hover_popover in hover_popovers.iter_mut() {
+            //         hover_popover.layout(
+            //             SizeConstraint {
+            //                 min: gpui::Point::<Pixels>::zero(),
+            //                 max: point(
+            //                     (120. * em_width) // Default size
+            //                         .min(size.x / 2.) // Shrink to half of the editor width
+            //                         .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
+            //                     (16. * line_height) // Default size
+            //                         .min(size.y / 2.) // Shrink to half of the editor height
+            //                         .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
+            //                 ),
+            //             },
+            //             editor,
+            //             cx,
+            //         );
+            //     }
+            // }
+
+            let invisible_symbol_font_size = font_size / 2.;
+            let tab_invisible = cx
+                .text_system()
+                .shape_line(
+                    "→".into(),
+                    invisible_symbol_font_size,
+                    &[TextRun {
+                        len: "→".len(),
+                        font: self.style.text.font(),
+                        color: cx.theme().colors().editor_invisible,
+                        background_color: None,
+                        underline: None,
+                    }],
+                )
+                .unwrap();
+            let space_invisible = cx
+                .text_system()
+                .shape_line(
+                    "•".into(),
+                    invisible_symbol_font_size,
+                    &[TextRun {
+                        len: "•".len(),
+                        font: self.style.text.font(),
+                        color: cx.theme().colors().editor_invisible,
+                        background_color: None,
+                        underline: None,
+                    }],
+                )
+                .unwrap();
+
+            LayoutState {
+                mode: editor_mode,
+                position_map: Arc::new(PositionMap {
+                    size: bounds.size,
+                    scroll_position: point(
+                        scroll_position.x * em_width,
+                        scroll_position.y * line_height,
+                    ),
+                    scroll_max,
+                    line_layouts,
+                    line_height,
+                    em_width,
+                    em_advance,
+                    snapshot,
+                }),
+                visible_anchor_range: start_anchor..end_anchor,
+                visible_display_row_range: start_row..end_row,
+                wrap_guides,
+                gutter_size,
+                gutter_padding,
+                text_size,
+                scrollbar_row_range,
+                show_scrollbars,
+                is_singleton,
+                max_row,
+                gutter_margin,
+                active_rows,
+                highlighted_rows,
+                highlighted_ranges,
+                line_numbers,
+                display_hunks,
+                blocks,
+                selections,
+                context_menu,
+                code_actions_indicator,
+                fold_indicators,
+                tab_invisible,
+                space_invisible,
+                // hover_popovers: hover,
+            }
+        })
     }
 
     #[allow(clippy::too_many_arguments)]
     fn layout_blocks(
-        &mut self,
+        &self,
         rows: Range<u32>,
         snapshot: &EditorSnapshot,
         editor_width: Pixels,

crates/editor2/src/hover_popover.rs 🔗

@@ -422,7 +422,7 @@ impl HoverState {
         visible_rows: Range<u32>,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
+    ) -> Option<(DisplayPoint, Vec<AnyElement>)> {
         todo!("old version below")
     }
     //     // If there is a diagnostic, position the popovers based on that.
@@ -504,7 +504,7 @@ pub struct DiagnosticPopover {
 }
 
 impl DiagnosticPopover {
-    pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
+    pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement {
         todo!()
         // enum PrimaryDiagnostic {}
 

crates/editor2/src/items.rs 🔗

@@ -10,7 +10,7 @@ use futures::future::try_join_all;
 use gpui::{
     div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
     FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
-    ViewContext, VisualContext, WeakView,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use language::{
     proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -30,7 +30,7 @@ use std::{
 };
 use text::Selection;
 use theme::{ActiveTheme, Theme};
-use ui::{Label, TextColor};
+use ui::{Color, Label};
 use util::{paths::PathExt, ResultExt, TryFutureExt};
 use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
 use workspace::{
@@ -584,7 +584,7 @@ impl Item for Editor {
         Some(path.to_string_lossy().to_string().into())
     }
 
-    fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
+    fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
         let theme = cx.theme();
 
         AnyElement::new(
@@ -604,7 +604,7 @@ impl Item for Editor {
                                 &description,
                                 MAX_TAB_TITLE_LEN,
                             ))
-                            .color(TextColor::Muted),
+                            .color(Color::Muted),
                         ),
                     )
                 })),
@@ -761,7 +761,7 @@ impl Item for Editor {
     }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft { flex: None }
+        ToolbarItemLocation::PrimaryLeft
     }
 
     fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -907,17 +907,15 @@ impl SearchableItem for Editor {
     type Match = Range<Anchor>;
 
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        todo!()
-        // self.clear_background_highlights::<BufferSearchHighlights>(cx);
+        self.clear_background_highlights::<BufferSearchHighlights>(cx);
     }
 
     fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
-        todo!()
-        // self.highlight_background::<BufferSearchHighlights>(
-        //     matches,
-        //     |theme| theme.search.match_background,
-        //     cx,
-        // );
+        self.highlight_background::<BufferSearchHighlights>(
+            matches,
+            |theme| theme.title_bar_background, // todo: update theme
+            cx,
+        );
     }
 
     fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
@@ -952,22 +950,20 @@ impl SearchableItem for Editor {
         matches: Vec<Range<Anchor>>,
         cx: &mut ViewContext<Self>,
     ) {
-        todo!()
-        // self.unfold_ranges([matches[index].clone()], false, true, cx);
-        // let range = self.range_for_match(&matches[index]);
-        // self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-        //     s.select_ranges([range]);
-        // })
+        self.unfold_ranges([matches[index].clone()], false, true, cx);
+        let range = self.range_for_match(&matches[index]);
+        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+            s.select_ranges([range]);
+        })
     }
 
     fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
-        todo!()
-        // self.unfold_ranges(matches.clone(), false, false, cx);
-        // let mut ranges = Vec::new();
-        // for m in &matches {
-        //     ranges.push(self.range_for_match(&m))
-        // }
-        // self.change_selections(None, cx, |s| s.select_ranges(ranges));
+        self.unfold_ranges(matches.clone(), false, false, cx);
+        let mut ranges = Vec::new();
+        for m in &matches {
+            ranges.push(self.range_for_match(&m))
+        }
+        self.change_selections(None, cx, |s| s.select_ranges(ranges));
     }
     fn replace(
         &mut self,

crates/file_finder2/src/file_finder.rs 🔗

@@ -117,8 +117,8 @@ impl FocusableView for FileFinder {
         self.picker.focus_handle(cx)
     }
 }
-impl Render<Self> for FileFinder {
-    type Element = Div<Self>;
+impl Render for FileFinder {
+    type Element = Div;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
         v_stack().w_96().child(self.picker.clone())
@@ -530,7 +530,7 @@ impl FileFinderDelegate {
 }
 
 impl PickerDelegate for FileFinderDelegate {
-    type ListItem = Div<Picker<Self>>;
+    type ListItem = Div;
 
     fn placeholder_text(&self) -> Arc<str> {
         "Search project files...".into()

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
 };
 use text::{Bias, Point};
 use theme::ActiveTheme;
-use ui::{h_stack, v_stack, Label, StyledExt, TextColor};
+use ui::{h_stack, v_stack, Color, Label, StyledExt};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
 use workspace::Workspace;
 
@@ -144,15 +144,15 @@ impl GoToLine {
     }
 }
 
-impl Render<Self> for GoToLine {
-    type Element = Div<Self>;
+impl Render for GoToLine {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
             .elevation_2(cx)
             .key_context("GoToLine")
-            .on_action(Self::cancel)
-            .on_action(Self::confirm)
+            .on_action(cx.listener(Self::cancel))
+            .on_action(cx.listener(Self::confirm))
             .w_96()
             .child(
                 v_stack()
@@ -176,7 +176,7 @@ impl Render<Self> for GoToLine {
                             .justify_between()
                             .px_2()
                             .py_1()
-                            .child(Label::new(self.current_text.clone()).color(TextColor::Muted)),
+                            .child(Label::new(self.current_text.clone()).color(Color::Muted)),
                     ),
             )
     }

crates/gpui2/src/app.rs 🔗

@@ -424,7 +424,7 @@ impl AppContext {
     /// Opens a new window with the given option and the root view returned by the given function.
     /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
     /// functionality.
-    pub fn open_window<V: 'static + Render<V>>(
+    pub fn open_window<V: 'static + Render>(
         &mut self,
         options: crate::WindowOptions,
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,

crates/gpui2/src/app/async_context.rs 🔗

@@ -115,7 +115,7 @@ impl AsyncAppContext {
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
     ) -> Result<WindowHandle<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         let app = self
             .app
@@ -286,7 +286,7 @@ impl VisualContext for AsyncWindowContext {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         self.window
             .update(self, |_, cx| cx.build_view(build_view_state))
@@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         self.window
             .update(self, |_, cx| cx.replace_root_view(build_view))

crates/gpui2/src/app/test_context.rs 🔗

@@ -1,8 +1,9 @@
 use crate::{
     div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
-    BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent,
-    Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow,
-    View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
+    BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
+    KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
+    TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
+    WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
@@ -126,7 +127,7 @@ impl TestAppContext {
     pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         let mut cx = self.app.borrow_mut();
         cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
@@ -143,7 +144,7 @@ impl TestAppContext {
     pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         let mut cx = self.app.borrow_mut();
         let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
@@ -296,21 +297,19 @@ impl TestAppContext {
             .unwrap()
     }
 
-    pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
+    pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
         let (tx, rx) = futures::channel::mpsc::unbounded();
-
-        entity.update(self, move |_, cx: &mut ModelContext<T>| {
+        self.update(|cx| {
             cx.observe(entity, {
                 let tx = tx.clone();
-                move |_, _, _| {
+                move |_, _| {
                     let _ = tx.unbounded_send(());
                 }
             })
             .detach();
-
-            cx.on_release(move |_, _| tx.close_channel()).detach();
+            cx.observe_release(entity, move |_, _| tx.close_channel())
+                .detach()
         });
-
         rx
     }
 
@@ -569,7 +568,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         self.window
             .update(self.cx, |_, cx| cx.build_view(build_view))
@@ -591,7 +590,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         self.window
             .update(self.cx, |_, cx| cx.replace_root_view(build_view))
@@ -619,7 +618,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
 }
 
 impl AnyWindowHandle {
-    pub fn build_view<V: Render<V> + 'static>(
+    pub fn build_view<V: Render + 'static>(
         &self,
         cx: &mut TestAppContext,
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
@@ -630,8 +629,8 @@ impl AnyWindowHandle {
 
 pub struct EmptyView {}
 
-impl Render<Self> for EmptyView {
-    type Element = Div<Self>;
+impl Render for EmptyView {
+    type Element = Div;
 
     fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
         div()

crates/gpui2/src/element.rs 🔗

@@ -1,24 +1,25 @@
 use crate::{
     AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext,
+    WindowContext,
 };
 use derive_more::{Deref, DerefMut};
 pub(crate) use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug, marker::PhantomData};
+use std::{any::Any, fmt::Debug};
 
-pub trait Render<V: 'static>: 'static + Sized {
-    type Element: Element<V> + 'static;
+pub trait Render: 'static + Sized {
+    type Element: Element + 'static;
 
-    fn render(&mut self, cx: &mut ViewContext<V>) -> Self::Element;
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
 }
 
-pub trait RenderOnce<V: 'static>: Sized {
-    type Element: Element<V> + 'static;
+pub trait RenderOnce: Sized {
+    type Element: Element + 'static;
 
     fn element_id(&self) -> Option<ElementId>;
 
     fn render_once(self) -> Self::Element;
 
-    fn render_into_any(self) -> AnyElement<V> {
+    fn render_into_any(self) -> AnyElement {
         self.render_once().into_any()
     }
 
@@ -26,9 +27,8 @@ pub trait RenderOnce<V: 'static>: Sized {
         self,
         origin: Point<Pixels>,
         available_space: Size<T>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(&mut <Self::Element as Element<V>>::State, &mut ViewContext<V>) -> R,
+        cx: &mut WindowContext,
+        f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
     ) -> R
     where
         T: Clone + Default + Debug + Into<AvailableSpace>,
@@ -39,13 +39,9 @@ pub trait RenderOnce<V: 'static>: Sized {
             element: Some(element),
             phase: ElementDrawPhase::Start,
         };
-        let frame_state = DrawableElement::draw(
-            element,
-            origin,
-            available_space.map(Into::into),
-            view_state,
-            cx,
-        );
+
+        let frame_state =
+            DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
 
         if let Some(mut frame_state) = frame_state {
             f(&mut frame_state, cx)
@@ -61,7 +57,7 @@ pub trait RenderOnce<V: 'static>: Sized {
     fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
     where
         Self: Sized,
-        U: RenderOnce<V>,
+        U: RenderOnce,
     {
         f(self)
     }
@@ -87,70 +83,55 @@ pub trait RenderOnce<V: 'static>: Sized {
     }
 }
 
-pub trait Element<V: 'static>: 'static + RenderOnce<V> {
+pub trait Element: 'static + RenderOnce {
     type State: 'static;
 
     fn layout(
         &mut self,
-        view_state: &mut V,
-        element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        state: Option<Self::State>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State);
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    );
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
 
-    fn into_any(self) -> AnyElement<V> {
+    fn into_any(self) -> AnyElement {
         AnyElement::new(self)
     }
 }
 
-pub trait Component<V: 'static>: 'static {
-    type Rendered: RenderOnce<V>;
+pub trait Component: 'static {
+    type Rendered: RenderOnce;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered;
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered;
 }
 
-pub struct CompositeElement<V, C> {
+pub struct CompositeElement<C> {
     component: Option<C>,
-    view_type: PhantomData<V>,
 }
 
-pub struct CompositeElementState<V: 'static, C: Component<V>> {
-    rendered_element: Option<<C::Rendered as RenderOnce<V>>::Element>,
-    rendered_element_state: <<C::Rendered as RenderOnce<V>>::Element as Element<V>>::State,
+pub struct CompositeElementState<C: Component> {
+    rendered_element: Option<<C::Rendered as RenderOnce>::Element>,
+    rendered_element_state: <<C::Rendered as RenderOnce>::Element as Element>::State,
 }
 
-impl<V, C> CompositeElement<V, C> {
+impl<C> CompositeElement<C> {
     pub fn new(component: C) -> Self {
         CompositeElement {
             component: Some(component),
-            view_type: PhantomData,
         }
     }
 }
 
-impl<V: 'static, C: Component<V>> Element<V> for CompositeElement<V, C> {
-    type State = CompositeElementState<V, C>;
+impl<C: Component> Element for CompositeElement<C> {
+    type State = CompositeElementState<C>;
 
     fn layout(
         &mut self,
-        view: &mut V,
         state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
-        let mut element = self
-            .component
-            .take()
-            .unwrap()
-            .render(view, cx)
-            .render_once();
-        let (layout_id, state) = element.layout(view, state.map(|s| s.rendered_element_state), cx);
+        let mut element = self.component.take().unwrap().render(cx).render_once();
+        let (layout_id, state) = element.layout(state.map(|s| s.rendered_element_state), cx);
         let state = CompositeElementState {
             rendered_element: Some(element),
             rendered_element_state: state,
@@ -158,23 +139,16 @@ impl<V: 'static, C: Component<V>> Element<V> for CompositeElement<V, C> {
         (layout_id, state)
     }
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        view: &mut V,
-        state: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) {
-        state.rendered_element.take().unwrap().paint(
-            bounds,
-            view,
-            &mut state.rendered_element_state,
-            cx,
-        );
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        state
+            .rendered_element
+            .take()
+            .unwrap()
+            .paint(bounds, &mut state.rendered_element_state, cx);
     }
 }
 
-impl<V: 'static, C: Component<V>> RenderOnce<V> for CompositeElement<V, C> {
+impl<C: Component> RenderOnce for CompositeElement<C> {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -189,10 +163,10 @@ impl<V: 'static, C: Component<V>> RenderOnce<V> for CompositeElement<V, C> {
 #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
 
-pub trait ParentElement<V: 'static> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
+pub trait ParentElement {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>;
 
-    fn child(mut self, child: impl RenderOnce<V>) -> Self
+    fn child(mut self, child: impl RenderOnce) -> Self
     where
         Self: Sized,
     {
@@ -200,7 +174,7 @@ pub trait ParentElement<V: 'static> {
         self
     }
 
-    fn children(mut self, children: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
+    fn children(mut self, children: impl IntoIterator<Item = impl RenderOnce>) -> Self
     where
         Self: Sized,
     {
@@ -213,26 +187,28 @@ pub trait ParentElement<V: 'static> {
     }
 }
 
-trait ElementObject<V> {
+trait ElementObject {
     fn element_id(&self) -> Option<ElementId>;
-    fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
-    fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
+
+    fn layout(&mut self, cx: &mut WindowContext) -> LayoutId;
+
+    fn paint(&mut self, cx: &mut WindowContext);
+
     fn measure(
         &mut self,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> Size<Pixels>;
+
     fn draw(
         &mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     );
 }
 
-pub struct DrawableElement<V: 'static, E: Element<V>> {
+pub struct DrawableElement<E: Element> {
     element: Option<E>,
     phase: ElementDrawPhase<E::State>,
 }
@@ -253,7 +229,7 @@ enum ElementDrawPhase<S> {
 }
 
 /// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
-impl<V, E: Element<V>> DrawableElement<V, E> {
+impl<E: Element> DrawableElement<E> {
     fn new(element: E) -> Self {
         DrawableElement {
             element: Some(element),
@@ -265,18 +241,15 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
         self.element.as_ref()?.element_id()
     }
 
-    fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
+    fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
         let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
         {
             let layout_id = cx.with_element_state(id, |element_state, cx| {
-                self.element
-                    .as_mut()
-                    .unwrap()
-                    .layout(state, element_state, cx)
+                self.element.as_mut().unwrap().layout(element_state, cx)
             });
             (layout_id, None)
         } else {
-            let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(state, None, cx);
+            let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx);
             (layout_id, Some(frame_state))
         };
 
@@ -287,7 +260,7 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
         layout_id
     }
 
-    fn paint(mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> Option<E::State> {
+    fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> {
         match self.phase {
             ElementDrawPhase::LayoutRequested {
                 layout_id,
@@ -304,7 +277,7 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
                     self.element
                         .take()
                         .unwrap()
-                        .paint(bounds, view_state, &mut frame_state, cx);
+                        .paint(bounds, &mut frame_state, cx);
                     Some(frame_state)
                 } else {
                     let element_id = self
@@ -315,12 +288,10 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
                         .expect("if we don't have frame state, we should have element state");
                     cx.with_element_state(element_id, |element_state, cx| {
                         let mut element_state = element_state.unwrap();
-                        self.element.take().unwrap().paint(
-                            bounds,
-                            view_state,
-                            &mut element_state,
-                            cx,
-                        );
+                        self.element
+                            .take()
+                            .unwrap()
+                            .paint(bounds, &mut element_state, cx);
                         ((), element_state)
                     });
                     None
@@ -334,11 +305,10 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
     fn measure(
         &mut self,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> Size<Pixels> {
         if matches!(&self.phase, ElementDrawPhase::Start) {
-            self.layout(view_state, cx);
+            self.layout(cx);
         }
 
         let layout_id = match &mut self.phase {
@@ -376,22 +346,20 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
         mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> Option<E::State> {
-        self.measure(available_space, view_state, cx);
-        cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx))
+        self.measure(available_space, cx);
+        cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
     }
 }
 
-// impl<V: 'static, E: Element<V>> Element<V> for DrawableElement<V, E> {
-//     type State = <E::Element as Element<V>>::State;
+// impl<V: 'static, E: Element> Element for DrawableElement<V, E> {
+//     type State = <E::Element as Element>::State;
 
 //     fn layout(
 //         &mut self,
-//         view_state: &mut V,
 //         element_state: Option<Self::State>,
-//         cx: &mut ViewContext<V>,
+//         cx: &mut WindowContext,
 //     ) -> (LayoutId, Self::State) {
 
 //     }
@@ -399,15 +367,14 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
 //     fn paint(
 //         self,
 //         bounds: Bounds<Pixels>,
-//         view_state: &mut V,
 //         element_state: &mut Self::State,
-//         cx: &mut ViewContext<V>,
+//         cx: &mut WindowContext,
 //     ) {
 //         todo!()
 //     }
 // }
 
-// impl<V: 'static, E: 'static + Element<V>> RenderOnce<V> for DrawableElement<V, E> {
+// impl<V: 'static, E: 'static + Element> RenderOnce for DrawableElement<V, E> {
 //     type Element = Self;
 
 //     fn element_id(&self) -> Option<ElementId> {
@@ -419,81 +386,71 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
 //     }
 // }
 
-impl<V, E> ElementObject<V> for Option<DrawableElement<V, E>>
+impl<E> ElementObject for Option<DrawableElement<E>>
 where
-    E: Element<V>,
+    E: Element,
     E::State: 'static,
 {
     fn element_id(&self) -> Option<ElementId> {
         self.as_ref().unwrap().element_id()
     }
 
-    fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
-        DrawableElement::layout(self.as_mut().unwrap(), view_state, cx)
+    fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
+        DrawableElement::layout(self.as_mut().unwrap(), cx)
     }
 
-    fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
-        DrawableElement::paint(self.take().unwrap(), view_state, cx);
+    fn paint(&mut self, cx: &mut WindowContext) {
+        DrawableElement::paint(self.take().unwrap(), cx);
     }
 
     fn measure(
         &mut self,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> Size<Pixels> {
-        DrawableElement::measure(self.as_mut().unwrap(), available_space, view_state, cx)
+        DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
     }
 
     fn draw(
         &mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
-        DrawableElement::draw(
-            self.take().unwrap(),
-            origin,
-            available_space,
-            view_state,
-            cx,
-        );
+        DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
     }
 }
 
-pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
+pub struct AnyElement(Box<dyn ElementObject>);
 
-impl<V: 'static> AnyElement<V> {
+impl AnyElement {
     pub fn new<E>(element: E) -> Self
     where
-        V: 'static,
-        E: 'static + Element<V>,
+        E: 'static + Element,
         E::State: Any,
     {
-        AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject<V>>)
+        AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
     }
 
     pub fn element_id(&self) -> Option<ElementId> {
         self.0.element_id()
     }
 
-    pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
-        self.0.layout(view_state, cx)
+    pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
+        self.0.layout(cx)
     }
 
-    pub fn paint(mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
-        self.0.paint(view_state, cx)
+    pub fn paint(mut self, cx: &mut WindowContext) {
+        self.0.paint(cx)
     }
 
     /// Initializes this element and performs layout within the given available space to determine its size.
     pub fn measure(
         &mut self,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> Size<Pixels> {
-        self.0.measure(available_space, view_state, cx)
+        self.0.measure(available_space, cx)
     }
 
     /// Initializes this element and performs layout in the available space, then paints it at the given origin.
@@ -501,43 +458,35 @@ impl<V: 'static> AnyElement<V> {
         mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
-        view_state: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
-        self.0.draw(origin, available_space, view_state, cx)
+        self.0.draw(origin, available_space, cx)
     }
 
     /// Converts this `AnyElement` into a trait object that can be stored and manipulated.
-    pub fn into_any(self) -> AnyElement<V> {
+    pub fn into_any(self) -> AnyElement {
         AnyElement::new(self)
     }
 }
 
-impl<V: 'static> Element<V> for AnyElement<V> {
+impl Element for AnyElement {
     type State = ();
 
     fn layout(
         &mut self,
-        view_state: &mut V,
         _: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
-        let layout_id = self.layout(view_state, cx);
+        let layout_id = self.layout(cx);
         (layout_id, ())
     }
 
-    fn paint(
-        self,
-        _bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        _: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) {
-        self.paint(view_state, cx);
+    fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
+        self.paint(cx);
     }
 }
 
-impl<V: 'static> RenderOnce<V> for AnyElement<V> {
+impl RenderOnce for AnyElement {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -549,13 +498,13 @@ impl<V: 'static> RenderOnce<V> for AnyElement<V> {
     }
 }
 
-// impl<V, E, F> Element<V> for Option<F>
+// impl<V, E, F> Element for Option<F>
 // where
 //     V: 'static,
-//     E: Element<V>,
-//     F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
+//     E: Element,
+//     F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static,
 // {
-//     type State = Option<AnyElement<V>>;
+//     type State = Option<AnyElement>;
 
 //     fn element_id(&self) -> Option<ElementId> {
 //         None
@@ -563,9 +512,8 @@ impl<V: 'static> RenderOnce<V> for AnyElement<V> {
 
 //     fn layout(
 //         &mut self,
-//         view_state: &mut V,
 //         _: Option<Self::State>,
-//         cx: &mut ViewContext<V>,
+//         cx: &mut WindowContext,
 //     ) -> (LayoutId, Self::State) {
 //         let render = self.take().unwrap();
 //         let mut element = (render)(view_state, cx).into_any();
@@ -576,19 +524,18 @@ impl<V: 'static> RenderOnce<V> for AnyElement<V> {
 //     fn paint(
 //         self,
 //         _bounds: Bounds<Pixels>,
-//         view_state: &mut V,
 //         rendered_element: &mut Self::State,
-//         cx: &mut ViewContext<V>,
+//         cx: &mut WindowContext,
 //     ) {
 //         rendered_element.take().unwrap().paint(view_state, cx);
 //     }
 // }
 
-// impl<V, E, F> RenderOnce<V> for Option<F>
+// impl<V, E, F> RenderOnce for Option<F>
 // where
 //     V: 'static,
-//     E: Element<V>,
-//     F: FnOnce(&mut V, &mut ViewContext<V>) -> E + 'static,
+//     E: Element,
+//     F: FnOnce(&mut V, &mut WindowContext) -> E + 'static,
 // {
 //     type Element = Self;
 

crates/gpui2/src/elements/div.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
     KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
     MouseUpEvent, ParentElement, Pixels, Point, Render, RenderOnce, ScrollWheelEvent, SharedString,
-    Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility,
+    Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext,
 };
 use collections::HashMap;
 use refineable::Refineable;
@@ -12,7 +12,6 @@ use std::{
     any::{Any, TypeId},
     cell::RefCell,
     fmt::Debug,
-    marker::PhantomData,
     mem,
     rc::Rc,
     time::Duration,
@@ -28,30 +27,24 @@ pub struct GroupStyle {
     pub style: StyleRefinement,
 }
 
-pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
-    fn interactivity(&mut self) -> &mut Interactivity<V>;
+pub trait InteractiveElement: Sized + Element {
+    fn interactivity(&mut self) -> &mut Interactivity;
 
     fn group(mut self, group: impl Into<SharedString>) -> Self {
         self.interactivity().group = Some(group.into());
         self
     }
 
-    fn id(mut self, id: impl Into<ElementId>) -> Stateful<V, Self> {
+    fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
         self.interactivity().element_id = Some(id.into());
 
-        Stateful {
-            element: self,
-            view_type: PhantomData,
-        }
+        Stateful { element: self }
     }
 
-    fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<V, Self> {
+    fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
         self.interactivity().focusable = true;
         self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
-        Focusable {
-            element: self,
-            view_type: PhantomData,
-        }
+        Focusable { element: self }
     }
 
     fn key_context<C, E>(mut self, key_context: C) -> Self
@@ -85,15 +78,15 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
     fn on_mouse_down(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().mouse_down_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+            move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && event.button == button
                     && bounds.contains_point(&event.position)
                 {
-                    handler(view, event, cx)
+                    (listener)(event, cx)
                 }
             },
         ));
@@ -102,12 +95,12 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
 
     fn on_any_mouse_down(
         mut self,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().mouse_down_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+            move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx)
+                    (listener)(event, cx)
                 }
             },
         ));
@@ -117,43 +110,43 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
     fn on_mouse_up(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_up_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+        self.interactivity()
+            .mouse_up_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && event.button == button
                     && bounds.contains_point(&event.position)
                 {
-                    handler(view, event, cx)
+                    (listener)(event, cx)
                 }
-            },
-        ));
+            }));
         self
     }
 
     fn on_any_mouse_up(
         mut self,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_up_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+        self.interactivity()
+            .mouse_up_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx)
+                    (listener)(event, cx)
                 }
-            },
-        ));
+            }));
         self
     }
 
     fn on_mouse_down_out(
         mut self,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().mouse_down_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+            move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
-                    handler(view, event, cx)
+                    (listener)(event, cx)
                 }
             },
         ));
@@ -163,29 +156,29 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
     fn on_mouse_up_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
-        self.interactivity().mouse_up_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+        self.interactivity()
+            .mouse_up_listeners
+            .push(Box::new(move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
                     && event.button == button
                     && !bounds.contains_point(&event.position)
                 {
-                    handler(view, event, cx);
+                    (listener)(event, cx);
                 }
-            },
-        ));
+            }));
         self
     }
 
     fn on_mouse_move(
         mut self,
-        handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().mouse_move_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+            move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx);
+                    (listener)(event, cx);
                 }
             },
         ));
@@ -194,29 +187,29 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
 
     fn on_scroll_wheel(
         mut self,
-        handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().scroll_wheel_listeners.push(Box::new(
-            move |view, event, bounds, phase, cx| {
+            move |event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx);
+                    (listener)(event, cx);
                 }
             },
         ));
         self
     }
 
-    /// Capture the given action, fires during the capture phase
+    /// Capture the given action, before normal action dispatch can fire
     fn capture_action<A: Action>(
         mut self,
-        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&A, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().action_listeners.push((
             TypeId::of::<A>(),
-            Box::new(move |view, action, phase, cx| {
+            Box::new(move |action, phase, cx| {
                 let action = action.downcast_ref().unwrap();
                 if phase == DispatchPhase::Capture {
-                    listener(view, action, cx)
+                    (listener)(action, cx)
                 }
             }),
         ));
@@ -224,10 +217,7 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
     }
 
     /// Add a listener for the given action, fires during the bubble event phase
-    fn on_action<A: Action>(
-        mut self,
-        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
-    ) -> Self {
+    fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
         // NOTE: this debug assert has the side-effect of working around
         // a bug where a crate consisting only of action definitions does
         // not register the actions in debug builds:
@@ -244,10 +234,10 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
         // );
         self.interactivity().action_listeners.push((
             TypeId::of::<A>(),
-            Box::new(move |view, action, phase, cx| {
+            Box::new(move |action, phase, cx| {
                 let action = action.downcast_ref().unwrap();
                 if phase == DispatchPhase::Bubble {
-                    listener(view, action, cx)
+                    (listener)(action, cx)
                 }
             }),
         ));
@@ -256,24 +246,53 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
 
     fn on_key_down(
         mut self,
-        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity()
             .key_down_listeners
-            .push(Box::new(move |view, event, phase, cx| {
-                listener(view, event, phase, cx)
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    (listener)(event, cx)
+                }
             }));
         self
     }
 
-    fn on_key_up(
+    fn capture_key_down(
         mut self,
-        listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
+    ) -> Self {
+        self.interactivity()
+            .key_down_listeners
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    listener(event, cx)
+                }
+            }));
+        self
+    }
+
+    fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
+        self.interactivity()
+            .key_up_listeners
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    listener(event, cx)
+                }
+            }));
+        self
+    }
+
+    fn capture_key_up(
+        mut self,
+        listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity()
             .key_up_listeners
-            .push(Box::new(move |view, event, phase, cx| {
-                listener(view, event, phase, cx)
+            .push(Box::new(move |event, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    listener(event, cx)
+                }
             }));
         self
     }
@@ -302,25 +321,22 @@ pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
 
     fn on_drop<W: 'static>(
         mut self,
-        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
+        listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
     ) -> Self {
         self.interactivity().drop_listeners.push((
             TypeId::of::<W>(),
-            Box::new(move |view, dragged_view, cx| {
-                listener(view, dragged_view.downcast().unwrap(), cx);
+            Box::new(move |dragged_view, cx| {
+                listener(&dragged_view.downcast().unwrap(), cx);
             }),
         ));
         self
     }
 }
 
-pub trait StatefulInteractiveElement<V: 'static, E: Element<V>>: InteractiveElement<V> {
-    fn focusable(mut self) -> Focusable<V, Self> {
+pub trait StatefulInteractiveElement: InteractiveElement {
+    fn focusable(mut self) -> Focusable<Self> {
         self.interactivity().focusable = true;
-        Focusable {
-            element: self,
-            view_type: PhantomData,
-        }
+        Focusable { element: self }
     }
 
     fn overflow_scroll(mut self) -> Self {
@@ -362,40 +378,33 @@ pub trait StatefulInteractiveElement<V: 'static, E: Element<V>>: InteractiveElem
         self
     }
 
-    fn on_click(
-        mut self,
-        listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
+    fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
     where
         Self: Sized,
     {
         self.interactivity()
             .click_listeners
-            .push(Box::new(move |view, event, cx| listener(view, event, cx)));
+            .push(Box::new(move |event, cx| listener(event, cx)));
         self
     }
 
-    fn on_drag<W>(
-        mut self,
-        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
-    ) -> Self
+    fn on_drag<W>(mut self, listener: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self
     where
         Self: Sized,
-        W: 'static + Render<W>,
+        W: 'static + Render,
     {
         debug_assert!(
             self.interactivity().drag_listener.is_none(),
             "calling on_drag more than once on the same element is not supported"
         );
-        self.interactivity().drag_listener =
-            Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
-                view: listener(view_state, cx).into(),
-                cursor_offset,
-            }));
+        self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag {
+            view: listener(cx).into(),
+            cursor_offset,
+        }));
         self
     }
 
-    fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
+    fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
     where
         Self: Sized,
     {
@@ -407,10 +416,7 @@ pub trait StatefulInteractiveElement<V: 'static, E: Element<V>>: InteractiveElem
         self
     }
 
-    fn tooltip(
-        mut self,
-        build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
-    ) -> Self
+    fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
     where
         Self: Sized,
     {
@@ -418,14 +424,13 @@ pub trait StatefulInteractiveElement<V: 'static, E: Element<V>>: InteractiveElem
             self.interactivity().tooltip_builder.is_none(),
             "calling tooltip more than once on the same element is not supported"
         );
-        self.interactivity().tooltip_builder =
-            Some(Rc::new(move |view_state, cx| build_tooltip(view_state, cx)));
+        self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip));
 
         self
     }
 }
 
-pub trait FocusableElement<V: 'static>: InteractiveElement<V> {
+pub trait FocusableElement: InteractiveElement {
     fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
@@ -442,49 +447,41 @@ pub trait FocusableElement<V: 'static>: InteractiveElement<V> {
         self
     }
 
-    fn on_focus(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
+    fn on_focus(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
     where
         Self: Sized,
     {
-        self.interactivity().focus_listeners.push(Box::new(
-            move |view, focus_handle, event, cx| {
+        self.interactivity()
+            .focus_listeners
+            .push(Box::new(move |focus_handle, event, cx| {
                 if event.focused.as_ref() == Some(focus_handle) {
-                    listener(view, event, cx)
+                    listener(event, cx)
                 }
-            },
-        ));
+            }));
         self
     }
 
-    fn on_blur(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
+    fn on_blur(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
     where
         Self: Sized,
     {
-        self.interactivity().focus_listeners.push(Box::new(
-            move |view, focus_handle, event, cx| {
+        self.interactivity()
+            .focus_listeners
+            .push(Box::new(move |focus_handle, event, cx| {
                 if event.blurred.as_ref() == Some(focus_handle) {
-                    listener(view, event, cx)
+                    listener(event, cx)
                 }
-            },
-        ));
+            }));
         self
     }
 
-    fn on_focus_in(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
+    fn on_focus_in(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
     where
         Self: Sized,
     {
-        self.interactivity().focus_listeners.push(Box::new(
-            move |view, focus_handle, event, cx| {
+        self.interactivity()
+            .focus_listeners
+            .push(Box::new(move |focus_handle, event, cx| {
                 let descendant_blurred = event
                     .blurred
                     .as_ref()
@@ -495,22 +492,19 @@ pub trait FocusableElement<V: 'static>: InteractiveElement<V> {
                     .map_or(false, |focused| focus_handle.contains(focused, cx));
 
                 if !descendant_blurred && descendant_focused {
-                    listener(view, event, cx)
+                    listener(event, cx)
                 }
-            },
-        ));
+            }));
         self
     }
 
-    fn on_focus_out(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
+    fn on_focus_out(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
     where
         Self: Sized,
     {
-        self.interactivity().focus_listeners.push(Box::new(
-            move |view, focus_handle, event, cx| {
+        self.interactivity()
+            .focus_listeners
+            .push(Box::new(move |focus_handle, event, cx| {
                 let descendant_blurred = event
                     .blurred
                     .as_ref()
@@ -520,93 +514,79 @@ pub trait FocusableElement<V: 'static>: InteractiveElement<V> {
                     .as_ref()
                     .map_or(false, |focused| focus_handle.contains(focused, cx));
                 if descendant_blurred && !descendant_focused {
-                    listener(view, event, cx)
+                    listener(event, cx)
                 }
-            },
-        ));
+            }));
         self
     }
 }
 
-pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
+pub type FocusListeners = SmallVec<[FocusListener; 2]>;
 
-pub type FocusListener<V> =
-    Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
+pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
 
-pub type MouseDownListener<V> = Box<
-    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
->;
-pub type MouseUpListener<V> = Box<
-    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
->;
+pub type MouseDownListener =
+    Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
+pub type MouseUpListener =
+    Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
 
-pub type MouseMoveListener<V> = Box<
-    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
->;
+pub type MouseMoveListener =
+    Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
 
-pub type ScrollWheelListener<V> = Box<
-    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + 'static,
->;
+pub type ScrollWheelListener =
+    Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
 
-pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
+pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
 
-pub type DragListener<V> =
-    Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
+pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>;
 
-type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
+type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
 
-pub type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
+pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
 
-pub type TooltipBuilder<V> = Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
+pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
 
-pub type KeyDownListener<V> =
-    Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
 
-pub type KeyUpListener<V> =
-    Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
 
-pub type ActionListener<V> =
-    Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
-
-pub fn div<V: 'static>() -> Div<V> {
+pub fn div() -> Div {
     Div {
         interactivity: Interactivity::default(),
         children: SmallVec::default(),
     }
 }
 
-pub struct Div<V> {
-    interactivity: Interactivity<V>,
-    children: SmallVec<[AnyElement<V>; 2]>,
+pub struct Div {
+    interactivity: Interactivity,
+    children: SmallVec<[AnyElement; 2]>,
 }
 
-impl<V> Styled for Div<V> {
+impl Styled for Div {
     fn style(&mut self) -> &mut StyleRefinement {
         &mut self.interactivity.base_style
     }
 }
 
-impl<V: 'static> InteractiveElement<V> for Div<V> {
-    fn interactivity(&mut self) -> &mut Interactivity<V> {
+impl InteractiveElement for Div {
+    fn interactivity(&mut self) -> &mut Interactivity {
         &mut self.interactivity
     }
 }
 
-impl<V: 'static> ParentElement<V> for Div<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+impl ParentElement for Div {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
         &mut self.children
     }
 }
 
-impl<V: 'static> Element<V> for Div<V> {
+impl Element for Div {
     type State = DivState;
 
     fn layout(
         &mut self,
-        view_state: &mut V,
         element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         let mut child_layout_ids = SmallVec::new();
         let mut interactivity = mem::take(&mut self.interactivity);
@@ -618,7 +598,7 @@ impl<V: 'static> Element<V> for Div<V> {
                     child_layout_ids = self
                         .children
                         .iter_mut()
-                        .map(|child| child.layout(view_state, cx))
+                        .map(|child| child.layout(cx))
                         .collect::<SmallVec<_>>();
                     cx.request_layout(&style, child_layout_ids.iter().copied())
                 })
@@ -637,9 +617,8 @@ impl<V: 'static> Element<V> for Div<V> {
     fn paint(
         self,
         bounds: Bounds<Pixels>,
-        view_state: &mut V,
         element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
         let mut child_min = point(Pixels::MAX, Pixels::MAX);
         let mut child_max = Point::default();
@@ -675,7 +654,7 @@ impl<V: 'static> Element<V> for Div<V> {
                             cx.with_content_mask(style.overflow_mask(bounds), |cx| {
                                 cx.with_element_offset(scroll_offset, |cx| {
                                     for child in self.children {
-                                        child.paint(view_state, cx);
+                                        child.paint(cx);
                                     }
                                 })
                             })
@@ -687,7 +666,7 @@ impl<V: 'static> Element<V> for Div<V> {
     }
 }
 
-impl<V: 'static> RenderOnce<V> for Div<V> {
+impl RenderOnce for Div {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -710,12 +689,12 @@ impl DivState {
     }
 }
 
-pub struct Interactivity<V> {
+pub struct Interactivity {
     pub element_id: Option<ElementId>,
     pub key_context: KeyContext,
     pub focusable: bool,
     pub tracked_focus_handle: Option<FocusHandle>,
-    pub focus_listeners: FocusListeners<V>,
+    pub focus_listeners: FocusListeners,
     pub group: Option<SharedString>,
     pub base_style: StyleRefinement,
     pub focus_style: StyleRefinement,
@@ -726,29 +705,26 @@ pub struct Interactivity<V> {
     pub group_active_style: Option<GroupStyle>,
     pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
     pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
-    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
-    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
-    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
-    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
-    pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
-    pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
-    pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
-    pub drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
-    pub click_listeners: SmallVec<[ClickListener<V>; 2]>,
-    pub drag_listener: Option<DragListener<V>>,
-    pub hover_listener: Option<HoverListener<V>>,
-    pub tooltip_builder: Option<TooltipBuilder<V>>,
+    pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>,
+    pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>,
+    pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>,
+    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>,
+    pub key_down_listeners: SmallVec<[KeyDownListener; 2]>,
+    pub key_up_listeners: SmallVec<[KeyUpListener; 2]>,
+    pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>,
+    pub drop_listeners: SmallVec<[(TypeId, Box<DropListener>); 2]>,
+    pub click_listeners: SmallVec<[ClickListener; 2]>,
+    pub drag_listener: Option<DragListener>,
+    pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
+    pub tooltip_builder: Option<TooltipBuilder>,
 }
 
-impl<V> Interactivity<V>
-where
-    V: 'static,
-{
+impl Interactivity {
     pub fn layout(
         &mut self,
         element_state: Option<InteractiveElementState>,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(Style, &mut ViewContext<V>) -> LayoutId,
+        cx: &mut WindowContext,
+        f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
     ) -> (LayoutId, InteractiveElementState) {
         let mut element_state = element_state.unwrap_or_default();
 
@@ -774,8 +750,8 @@ where
         bounds: Bounds<Pixels>,
         content_size: Size<Pixels>,
         element_state: &mut InteractiveElementState,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(Style, Point<Pixels>, &mut ViewContext<V>),
+        cx: &mut WindowContext,
+        f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
     ) {
         let style = self.compute_style(Some(bounds), element_state, cx);
 
@@ -786,27 +762,27 @@ where
             }
         }
 
-        for listener in self.mouse_down_listeners {
-            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
+        for listener in self.mouse_down_listeners.drain(..) {
+            cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+                listener(event, &bounds, phase, cx);
             })
         }
 
-        for listener in self.mouse_up_listeners {
-            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
+        for listener in self.mouse_up_listeners.drain(..) {
+            cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
+                listener(event, &bounds, phase, cx);
             })
         }
 
-        for listener in self.mouse_move_listeners {
-            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
+        for listener in self.mouse_move_listeners.drain(..) {
+            cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
+                listener(event, &bounds, phase, cx);
             })
         }
 
-        for listener in self.scroll_wheel_listeners {
-            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
+        for listener in self.scroll_wheel_listeners.drain(..) {
+            cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
+                listener(event, &bounds, phase, cx);
             })
         }
 
@@ -817,7 +793,7 @@ where
 
         if let Some(group_bounds) = hover_group_bounds {
             let hovered = group_bounds.contains_point(&cx.mouse_position());
-            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+            cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                 if phase == DispatchPhase::Capture {
                     if group_bounds.contains_point(&event.position) != hovered {
                         cx.notify();
@@ -830,7 +806,7 @@ where
             || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
         {
             let hovered = bounds.contains_point(&cx.mouse_position());
-            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+            cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                 if phase == DispatchPhase::Capture {
                     if bounds.contains_point(&event.position) != hovered {
                         cx.notify();
@@ -840,8 +816,8 @@ where
         }
 
         if cx.active_drag.is_some() {
-            let drop_listeners = self.drop_listeners;
-            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
+            let drop_listeners = mem::take(&mut self.drop_listeners);
+            cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     if let Some(drag_state_type) =
                         cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
@@ -852,7 +828,7 @@ where
                                     .active_drag
                                     .take()
                                     .expect("checked for type drag state type above");
-                                listener(view, drag.view.clone(), cx);
+                                listener(drag.view.clone(), cx);
                                 cx.notify();
                                 cx.stop_propagation();
                             }
@@ -872,7 +848,7 @@ where
                 if let Some(drag_listener) = drag_listener {
                     let active_state = element_state.clicked_state.clone();
 
-                    cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
+                    cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                         if cx.active_drag.is_some() {
                             if phase == DispatchPhase::Capture {
                                 cx.notify();
@@ -883,7 +859,7 @@ where
                         {
                             *active_state.borrow_mut() = ElementClickedState::default();
                             let cursor_offset = event.position - bounds.origin;
-                            let drag = drag_listener(view_state, cursor_offset, cx);
+                            let drag = drag_listener(cursor_offset, cx);
                             cx.active_drag = Some(drag);
                             cx.notify();
                             cx.stop_propagation();
@@ -891,21 +867,21 @@ where
                     });
                 }
 
-                cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {
+                cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
                     if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                         let mouse_click = ClickEvent {
                             down: mouse_down.clone(),
                             up: event.clone(),
                         };
                         for listener in &click_listeners {
-                            listener(view_state, &mouse_click, cx);
+                            listener(&mouse_click, cx);
                         }
                     }
                     *pending_mouse_down.borrow_mut() = None;
                     cx.notify();
                 });
             } else {
-                cx.on_mouse_event(move |_view_state, event: &MouseDownEvent, phase, cx| {
+                cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
                     if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                         *pending_mouse_down.borrow_mut() = Some(event.clone());
                         cx.notify();
@@ -918,7 +894,7 @@ where
             let was_hovered = element_state.hover_state.clone();
             let has_mouse_down = element_state.pending_mouse_down.clone();
 
-            cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
+            cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                 if phase != DispatchPhase::Bubble {
                     return;
                 }
@@ -930,7 +906,7 @@ where
                     *was_hovered = is_hovered;
                     drop(was_hovered);
 
-                    hover_listener(view_state, is_hovered, cx);
+                    hover_listener(&is_hovered, cx);
                 }
             });
         }
@@ -939,7 +915,7 @@ where
             let active_tooltip = element_state.active_tooltip.clone();
             let pending_mouse_down = element_state.pending_mouse_down.clone();
 
-            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+            cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                 if phase != DispatchPhase::Bubble {
                     return;
                 }
@@ -956,12 +932,12 @@ where
                         let active_tooltip = active_tooltip.clone();
                         let tooltip_builder = tooltip_builder.clone();
 
-                        move |view, mut cx| async move {
+                        move |mut cx| async move {
                             cx.background_executor().timer(TOOLTIP_DELAY).await;
-                            view.update(&mut cx, move |view_state, cx| {
+                            cx.update(|_, cx| {
                                 active_tooltip.borrow_mut().replace(ActiveTooltip {
                                     tooltip: Some(AnyTooltip {
-                                        view: tooltip_builder(view_state, cx),
+                                        view: tooltip_builder(cx),
                                         cursor_offset: cx.mouse_position(),
                                     }),
                                     _task: None,
@@ -979,7 +955,7 @@ where
             });
 
             let active_tooltip = element_state.active_tooltip.clone();
-            cx.on_mouse_event(move |_, _: &MouseDownEvent, _, _| {
+            cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
                 active_tooltip.borrow_mut().take();
             });
 
@@ -992,7 +968,7 @@ where
 
         let active_state = element_state.clicked_state.clone();
         if !active_state.borrow().is_clicked() {
-            cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
+            cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
                 if phase == DispatchPhase::Capture {
                     *active_state.borrow_mut() = ElementClickedState::default();
                     cx.notify();
@@ -1003,7 +979,7 @@ where
                 .group_active_style
                 .as_ref()
                 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
-            cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
+            cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble {
                     let group = active_group_bounds
                         .map_or(false, |bounds| bounds.contains_point(&down.position));
@@ -1025,7 +1001,7 @@ where
             let line_height = cx.line_height();
             let scroll_max = (content_size - bounds.size).max(&Size::default());
 
-            cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
+            cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     let mut scroll_offset = scroll_offset.borrow_mut();
                     let old_scroll_offset = *scroll_offset;
@@ -1062,15 +1038,15 @@ where
             self.key_context.clone(),
             element_state.focus_handle.clone(),
             |_, cx| {
-                for listener in self.key_down_listeners {
-                    cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
-                        listener(state, event, phase, cx);
+                for listener in self.key_down_listeners.drain(..) {
+                    cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
+                        listener(event, phase, cx);
                     })
                 }
 
-                for listener in self.key_up_listeners {
-                    cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
-                        listener(state, event, phase, cx);
+                for listener in self.key_up_listeners.drain(..) {
+                    cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
+                        listener(event, phase, cx);
                     })
                 }
 
@@ -1081,9 +1057,7 @@ where
                 if let Some(focus_handle) = element_state.focus_handle.as_ref() {
                     for listener in self.focus_listeners {
                         let focus_handle = focus_handle.clone();
-                        cx.on_focus_changed(move |view, event, cx| {
-                            listener(view, &focus_handle, event, cx)
-                        });
+                        cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx));
                     }
                 }
 
@@ -1100,7 +1074,7 @@ where
         &self,
         bounds: Option<Bounds<Pixels>>,
         element_state: &mut InteractiveElementState,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> Style {
         let mut style = Style::default();
         style.refine(&self.base_style);
@@ -1171,7 +1145,7 @@ where
     }
 }
 
-impl<V: 'static> Default for Interactivity<V> {
+impl Default for Interactivity {
     fn default() -> Self {
         Self {
             element_id: None,
@@ -1259,31 +1233,25 @@ impl GroupBounds {
     }
 }
 
-pub struct Focusable<V, E> {
+pub struct Focusable<E> {
     element: E,
-    view_type: PhantomData<V>,
 }
 
-impl<V: 'static + Render<V>, E: InteractiveElement<V>> FocusableElement<V> for Focusable<V, E> {}
+impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
 
-impl<V, E> InteractiveElement<V> for Focusable<V, E>
+impl<E> InteractiveElement for Focusable<E>
 where
-    V: 'static + Render<V>,
-    E: InteractiveElement<V>,
+    E: InteractiveElement,
 {
-    fn interactivity(&mut self) -> &mut Interactivity<V> {
+    fn interactivity(&mut self) -> &mut Interactivity {
         self.element.interactivity()
     }
 }
 
-impl<V: 'static + Render<V>, E: StatefulInteractiveElement<V, E>> StatefulInteractiveElement<V, E>
-    for Focusable<V, E>
-{
-}
+impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
 
-impl<V, E> Styled for Focusable<V, E>
+impl<E> Styled for Focusable<E>
 where
-    V: 'static,
     E: Styled,
 {
     fn style(&mut self) -> &mut StyleRefinement {

crates/gpui2/src/elements/img.rs 🔗

@@ -1,17 +1,17 @@
 use crate::{
-    BorrowWindow, Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity,
-    LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext,
+    Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels,
+    RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
 };
 use futures::FutureExt;
 use util::ResultExt;
 
-pub struct Img<V: 'static> {
-    interactivity: Interactivity<V>,
+pub struct Img {
+    interactivity: Interactivity,
     uri: Option<SharedString>,
     grayscale: bool,
 }
 
-pub fn img<V: 'static>() -> Img<V> {
+pub fn img() -> Img {
     Img {
         interactivity: Interactivity::default(),
         uri: None,
@@ -19,10 +19,7 @@ pub fn img<V: 'static>() -> Img<V> {
     }
 }
 
-impl<V> Img<V>
-where
-    V: 'static,
-{
+impl Img {
     pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
         self.uri = Some(uri.into());
         self
@@ -34,14 +31,13 @@ where
     }
 }
 
-impl<V> Element<V> for Img<V> {
+impl Element for Img {
     type State = InteractiveElementState;
 
     fn layout(
         &mut self,
-        _view_state: &mut V,
         element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         self.interactivity.layout(element_state, cx, |style, cx| {
             cx.request_layout(&style, None)
@@ -51,9 +47,8 @@ impl<V> Element<V> for Img<V> {
     fn paint(
         self,
         bounds: Bounds<Pixels>,
-        _view_state: &mut V,
         element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
         self.interactivity.paint(
             bounds,
@@ -78,7 +73,7 @@ impl<V> Element<V> for Img<V> {
                                 .log_err()
                         });
                     } else {
-                        cx.spawn(|_, mut cx| async move {
+                        cx.spawn(|mut cx| async move {
                             if image_future.await.ok().is_some() {
                                 cx.on_next_frame(|cx| cx.notify());
                             }
@@ -91,7 +86,7 @@ impl<V> Element<V> for Img<V> {
     }
 }
 
-impl<V: 'static> RenderOnce<V> for Img<V> {
+impl RenderOnce for Img {
     type Element = Self;
 
     fn element_id(&self) -> Option<crate::ElementId> {
@@ -103,14 +98,14 @@ impl<V: 'static> RenderOnce<V> for Img<V> {
     }
 }
 
-impl<V> Styled for Img<V> {
+impl Styled for Img {
     fn style(&mut self) -> &mut StyleRefinement {
         &mut self.interactivity.base_style
     }
 }
 
-impl<V> InteractiveElement<V> for Img<V> {
-    fn interactivity(&mut self) -> &mut Interactivity<V> {
+impl InteractiveElement for Img {
+    fn interactivity(&mut self) -> &mut Interactivity {
         &mut self.interactivity
     }
 }

crates/gpui2/src/elements/overlay.rs 🔗

@@ -3,15 +3,15 @@ use taffy::style::{Display, Position};
 
 use crate::{
     point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentElement, Pixels, Point,
-    RenderOnce, Size, Style,
+    RenderOnce, Size, Style, WindowContext,
 };
 
 pub struct OverlayState {
     child_layout_ids: SmallVec<[LayoutId; 4]>,
 }
 
-pub struct Overlay<V> {
-    children: SmallVec<[AnyElement<V>; 2]>,
+pub struct Overlay {
+    children: SmallVec<[AnyElement; 2]>,
     anchor_corner: AnchorCorner,
     fit_mode: OverlayFitMode,
     // todo!();
@@ -21,7 +21,7 @@ pub struct Overlay<V> {
 
 /// overlay gives you a floating element that will avoid overflowing the window bounds.
 /// Its children should have no margin to avoid measurement issues.
-pub fn overlay<V: 'static>() -> Overlay<V> {
+pub fn overlay() -> Overlay {
     Overlay {
         children: SmallVec::new(),
         anchor_corner: AnchorCorner::TopLeft,
@@ -30,7 +30,7 @@ pub fn overlay<V: 'static>() -> Overlay<V> {
     }
 }
 
-impl<V> Overlay<V> {
+impl Overlay {
     /// Sets which corner of the overlay should be anchored to the current position.
     pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
         self.anchor_corner = anchor;
@@ -51,25 +51,24 @@ impl<V> Overlay<V> {
     }
 }
 
-impl<V: 'static> ParentElement<V> for Overlay<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+impl ParentElement for Overlay {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
         &mut self.children
     }
 }
 
-impl<V: 'static> Element<V> for Overlay<V> {
+impl Element for Overlay {
     type State = OverlayState;
 
     fn layout(
         &mut self,
-        view_state: &mut V,
         _: Option<Self::State>,
-        cx: &mut crate::ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (crate::LayoutId, Self::State) {
         let child_layout_ids = self
             .children
             .iter_mut()
-            .map(|child| child.layout(view_state, cx))
+            .map(|child| child.layout(cx))
             .collect::<SmallVec<_>>();
 
         let mut overlay_style = Style::default();
@@ -84,9 +83,8 @@ impl<V: 'static> Element<V> for Overlay<V> {
     fn paint(
         self,
         bounds: crate::Bounds<crate::Pixels>,
-        view_state: &mut V,
         element_state: &mut Self::State,
-        cx: &mut crate::ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
         if element_state.child_layout_ids.is_empty() {
             return;
@@ -147,13 +145,13 @@ impl<V: 'static> Element<V> for Overlay<V> {
 
         cx.with_element_offset(desired.origin - bounds.origin, |cx| {
             for child in self.children {
-                child.paint(view_state, cx);
+                child.paint(cx);
             }
         })
     }
 }
 
-impl<V: 'static> RenderOnce<V> for Overlay<V> {
+impl RenderOnce for Overlay {
     type Element = Self;
 
     fn element_id(&self) -> Option<crate::ElementId> {

crates/gpui2/src/elements/svg.rs 🔗

@@ -1,49 +1,43 @@
 use crate::{
     Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
-    LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext,
+    LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
 };
 use util::ResultExt;
 
-pub struct Svg<V: 'static> {
-    interactivity: Interactivity<V>,
+pub struct Svg {
+    interactivity: Interactivity,
     path: Option<SharedString>,
 }
 
-pub fn svg<V: 'static>() -> Svg<V> {
+pub fn svg() -> Svg {
     Svg {
         interactivity: Interactivity::default(),
         path: None,
     }
 }
 
-impl<V> Svg<V> {
+impl Svg {
     pub fn path(mut self, path: impl Into<SharedString>) -> Self {
         self.path = Some(path.into());
         self
     }
 }
 
-impl<V> Element<V> for Svg<V> {
+impl Element for Svg {
     type State = InteractiveElementState;
 
     fn layout(
         &mut self,
-        _view_state: &mut V,
         element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         self.interactivity.layout(element_state, cx, |style, cx| {
             cx.request_layout(&style, None)
         })
     }
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        _view_state: &mut V,
-        element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) where
+    fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
+    where
         Self: Sized,
     {
         self.interactivity
@@ -55,7 +49,7 @@ impl<V> Element<V> for Svg<V> {
     }
 }
 
-impl<V: 'static> RenderOnce<V> for Svg<V> {
+impl RenderOnce for Svg {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -67,14 +61,14 @@ impl<V: 'static> RenderOnce<V> for Svg<V> {
     }
 }
 
-impl<V> Styled for Svg<V> {
+impl Styled for Svg {
     fn style(&mut self) -> &mut StyleRefinement {
         &mut self.interactivity.base_style
     }
 }
 
-impl<V> InteractiveElement<V> for Svg<V> {
-    fn interactivity(&mut self) -> &mut Interactivity<V> {
+impl InteractiveElement for Svg {
+    fn interactivity(&mut self) -> &mut Interactivity {
         &mut self.interactivity
     }
 }

crates/gpui2/src/elements/text.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    BorrowWindow, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size,
-    TextRun, ViewContext, WindowContext, WrappedLine,
+    Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun,
+    WhiteSpace, WindowContext, WrappedLine,
 };
 use anyhow::anyhow;
 use parking_lot::{Mutex, MutexGuard};
@@ -8,32 +8,25 @@ use smallvec::SmallVec;
 use std::{cell::Cell, rc::Rc, sync::Arc};
 use util::ResultExt;
 
-impl<V: 'static> Element<V> for &'static str {
+impl Element for &'static str {
     type State = TextState;
 
     fn layout(
         &mut self,
-        _: &mut V,
         _: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         let mut state = TextState::default();
         let layout_id = state.layout(SharedString::from(*self), None, cx);
         (layout_id, state)
     }
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        _: &mut V,
-        state: &mut TextState,
-        cx: &mut ViewContext<V>,
-    ) {
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
         state.paint(bounds, self, cx)
     }
 }
 
-impl<V: 'static> RenderOnce<V> for &'static str {
+impl RenderOnce for &'static str {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -45,37 +38,30 @@ impl<V: 'static> RenderOnce<V> for &'static str {
     }
 }
 
-impl<V: 'static> Element<V> for SharedString {
+impl Element for SharedString {
     type State = TextState;
 
     fn layout(
         &mut self,
-        _: &mut V,
         _: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         let mut state = TextState::default();
         let layout_id = state.layout(self.clone(), None, cx);
         (layout_id, state)
     }
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        _: &mut V,
-        state: &mut TextState,
-        cx: &mut ViewContext<V>,
-    ) {
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
         let text_str: &str = self.as_ref();
         state.paint(bounds, text_str, cx)
     }
 }
 
-impl<V: 'static> RenderOnce<V> for SharedString {
+impl RenderOnce for SharedString {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
-        Some(self.clone().into())
+        None
     }
 
     fn render_once(self) -> Self::Element {
@@ -102,110 +88,25 @@ impl StyledText {
     }
 }
 
-impl<V: 'static> Element<V> for StyledText {
+impl Element for StyledText {
     type State = TextState;
 
     fn layout(
         &mut self,
-        _view: &mut V,
-        element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
-        let element_state = element_state.unwrap_or_default();
-        let text_system = cx.text_system().clone();
-        let text_style = cx.text_style();
-        let font_size = text_style.font_size.to_pixels(cx.rem_size());
-        let line_height = text_style
-            .line_height
-            .to_pixels(font_size.into(), cx.rem_size());
-        let text = self.text.clone();
-
-        let rem_size = cx.rem_size();
-
-        let runs = if let Some(runs) = self.runs.take() {
-            runs
-        } else {
-            vec![text_style.to_run(text.len())]
-        };
-
-        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
-            let element_state = element_state.clone();
-            move |known_dimensions, available_space| {
-                let wrap_width = known_dimensions.width.or(match available_space.width {
-                    crate::AvailableSpace::Definite(x) => Some(x),
-                    _ => None,
-                });
-
-                if let Some(text_state) = element_state.0.lock().as_ref() {
-                    if text_state.size.is_some()
-                        && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
-                    {
-                        return text_state.size.unwrap();
-                    }
-                }
-
-                let Some(lines) = text_system
-                    .shape_text(
-                        &text,
-                        font_size,
-                        &runs[..],
-                        wrap_width, // Wrap if we know the width.
-                    )
-                    .log_err()
-                else {
-                    element_state.lock().replace(TextStateInner {
-                        lines: Default::default(),
-                        line_height,
-                        wrap_width,
-                        size: Some(Size::default()),
-                    });
-                    return Size::default();
-                };
-
-                let mut size: Size<Pixels> = Size::default();
-                for line in &lines {
-                    let line_size = line.size(line_height);
-                    size.height += line_size.height;
-                    size.width = size.width.max(line_size.width);
-                }
-
-                element_state.lock().replace(TextStateInner {
-                    lines,
-                    line_height,
-                    wrap_width,
-                    size: Some(size),
-                });
-
-                size
-            }
-        });
-
-        (layout_id, element_state)
+        let mut state = TextState::default();
+        let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
+        (layout_id, state)
     }
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        _: &mut V,
-        element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) {
-        let element_state = element_state.lock();
-        let element_state = element_state
-            .as_ref()
-            .ok_or_else(|| anyhow!("measurement has not been performed on {}", &self.text))
-            .unwrap();
-
-        let line_height = element_state.line_height;
-        let mut line_origin = bounds.origin;
-        for line in &element_state.lines {
-            line.paint(line_origin, line_height, cx).log_err();
-            line_origin.y += line.size(line_height).height;
-        }
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        state.paint(bounds, &self.text, cx)
     }
 }
 
-impl<V: 'static> RenderOnce<V> for StyledText {
+impl RenderOnce for StyledText {
     type Element = Self;
 
     fn element_id(&self) -> Option<crate::ElementId> {
@@ -258,10 +159,14 @@ impl TextState {
             let element_state = self.clone();
 
             move |known_dimensions, available_space| {
-                let wrap_width = known_dimensions.width.or(match available_space.width {
-                    crate::AvailableSpace::Definite(x) => Some(x),
-                    _ => None,
-                });
+                let wrap_width = if text_style.white_space == WhiteSpace::Normal {
+                    known_dimensions.width.or(match available_space.width {
+                        crate::AvailableSpace::Definite(x) => Some(x),
+                        _ => None,
+                    })
+                } else {
+                    None
+                };
 
                 if let Some(text_state) = element_state.0.lock().as_ref() {
                     if text_state.size.is_some()
@@ -273,10 +178,7 @@ impl TextState {
 
                 let Some(lines) = text_system
                     .shape_text(
-                        &text,
-                        font_size,
-                        &runs[..],
-                        wrap_width, // Wrap if we know the width.
+                        &text, font_size, &runs, wrap_width, // Wrap if we know the width.
                     )
                     .log_err()
                 else {
@@ -293,7 +195,7 @@ impl TextState {
                 for line in &lines {
                     let line_size = line.size(line_height);
                     size.height += line_size.height;
-                    size.width = size.width.max(line_size.width);
+                    size.width = size.width.max(line_size.width).ceil();
                 }
 
                 element_state.lock().replace(TextStateInner {
@@ -327,7 +229,7 @@ impl TextState {
 }
 
 struct InteractiveText {
-    id: ElementId,
+    element_id: ElementId,
     text: StyledText,
 }
 
@@ -336,28 +238,27 @@ struct InteractiveTextState {
     clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
 }
 
-impl<V: 'static> Element<V> for InteractiveText {
+impl Element for InteractiveText {
     type State = InteractiveTextState;
 
     fn layout(
         &mut self,
-        view_state: &mut V,
-        element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        state: Option<Self::State>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         if let Some(InteractiveTextState {
             text_state,
             clicked_range_ixs,
-        }) = element_state
+        }) = state
         {
-            let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx);
+            let (layout_id, text_state) = self.text.layout(Some(text_state), cx);
             let element_state = InteractiveTextState {
                 text_state,
                 clicked_range_ixs,
             };
             (layout_id, element_state)
         } else {
-            let (layout_id, text_state) = self.text.layout(view_state, None, cx);
+            let (layout_id, text_state) = self.text.layout(None, cx);
             let element_state = InteractiveTextState {
                 text_state,
                 clicked_range_ixs: Rc::default(),
@@ -366,23 +267,16 @@ impl<V: 'static> Element<V> for InteractiveText {
         }
     }
 
-    fn paint(
-        self,
-        bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) {
-        self.text
-            .paint(bounds, view_state, &mut element_state.text_state, cx)
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        self.text.paint(bounds, &mut state.text_state, cx)
     }
 }
 
-impl<V: 'static> RenderOnce<V> for InteractiveText {
+impl RenderOnce for InteractiveText {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
-        Some(self.id.clone())
+        Some(self.element_id.clone())
     }
 
     fn render_once(self) -> Self::Element {

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Element, ElementId,
-    InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, Point,
-    RenderOnce, Size, StyleRefinement, Styled, ViewContext,
+    point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement,
+    InteractiveElementState, Interactivity, LayoutId, Pixels, Point, Render, RenderOnce, Size,
+    StyleRefinement, Styled, View, ViewContext, WindowContext,
 };
 use smallvec::SmallVec;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -10,52 +10,51 @@ use taffy::style::Overflow;
 /// uniform_list provides lazy rendering for a set of items that are of uniform height.
 /// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
 /// uniform_list will only render the visibile subset of items.
-pub fn uniform_list<I, V, E>(
+pub fn uniform_list<I, R, V>(
+    view: View<V>,
     id: I,
     item_count: usize,
-    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<E>,
-) -> UniformList<V>
+    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
+) -> UniformList
 where
     I: Into<ElementId>,
-    V: 'static,
-    E: Element<V>,
+    R: RenderOnce,
+    V: Render,
 {
     let id = id.into();
-    let mut style = StyleRefinement::default();
-    style.overflow.y = Some(Overflow::Hidden);
+    let mut base_style = StyleRefinement::default();
+    base_style.overflow.y = Some(Overflow::Scroll);
+
+    let render_range = move |range, cx: &mut WindowContext| {
+        view.update(cx, |this, cx| {
+            f(this, range, cx)
+                .into_iter()
+                .map(|component| component.render_into_any())
+                .collect()
+        })
+    };
 
     UniformList {
         id: id.clone(),
-        style,
         item_count,
         item_to_measure_index: 0,
-        render_items: Box::new(move |view, visible_range, cx| {
-            f(view, visible_range, cx)
-                .into_iter()
-                .map(|component| component.into_any())
-                .collect()
-        }),
+        render_items: Box::new(render_range),
         interactivity: Interactivity {
             element_id: Some(id.into()),
+            base_style,
             ..Default::default()
         },
         scroll_handle: None,
     }
 }
 
-pub struct UniformList<V: 'static> {
+pub struct UniformList {
     id: ElementId,
-    style: StyleRefinement,
     item_count: usize,
     item_to_measure_index: usize,
-    render_items: Box<
-        dyn for<'a> Fn(
-            &'a mut V,
-            Range<usize>,
-            &'a mut ViewContext<V>,
-        ) -> SmallVec<[AnyElement<V>; 64]>,
-    >,
-    interactivity: Interactivity<V>,
+    render_items:
+        Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
+    interactivity: Interactivity,
     scroll_handle: Option<UniformListScrollHandle>,
 }
 
@@ -89,9 +88,9 @@ impl UniformListScrollHandle {
     }
 }
 
-impl<V: 'static> Styled for UniformList<V> {
+impl Styled for UniformList {
     fn style(&mut self) -> &mut StyleRefinement {
-        &mut self.style
+        &mut self.interactivity.base_style
     }
 }
 
@@ -101,25 +100,24 @@ pub struct UniformListState {
     item_size: Size<Pixels>,
 }
 
-impl<V: 'static> Element<V> for UniformList<V> {
+impl Element for UniformList {
     type State = UniformListState;
 
     fn layout(
         &mut self,
-        view_state: &mut V,
-        element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
+        state: Option<Self::State>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         let max_items = self.item_count;
         let rem_size = cx.rem_size();
-        let item_size = element_state
+        let item_size = state
             .as_ref()
             .map(|s| s.item_size)
-            .unwrap_or_else(|| self.measure_item(view_state, None, cx));
+            .unwrap_or_else(|| self.measure_item(None, cx));
 
         let (layout_id, interactive) =
             self.interactivity
-                .layout(element_state.map(|s| s.interactive), cx, |style, cx| {
+                .layout(state.map(|s| s.interactive), cx, |style, cx| {
                     cx.request_measured_layout(
                         style,
                         rem_size,
@@ -157,9 +155,8 @@ impl<V: 'static> Element<V> for UniformList<V> {
     fn paint(
         self,
         bounds: Bounds<crate::Pixels>,
-        view_state: &mut V,
         element_state: &mut Self::State,
-        cx: &mut ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
         let style =
             self.interactivity
@@ -185,9 +182,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
             .get_or_insert_with(Rc::default)
             .clone();
 
-        let item_height = self
-            .measure_item(view_state, Some(padded_bounds.size.width), cx)
-            .height;
+        let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
 
         self.interactivity.paint(
             bounds,
@@ -229,7 +224,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
                                 self.item_count,
                             );
 
-                        let items = (self.render_items)(view_state, visible_range.clone(), cx);
+                        let items = (self.render_items)(visible_range.clone(), cx);
                         cx.with_z_index(1, |cx| {
                             for (item, ix) in items.into_iter().zip(visible_range) {
                                 let item_origin = padded_bounds.origin
@@ -238,7 +233,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
                                     AvailableSpace::Definite(padded_bounds.size.width),
                                     AvailableSpace::Definite(item_height),
                                 );
-                                item.draw(item_origin, available_space, view_state, cx);
+                                item.draw(item_origin, available_space, cx);
                             }
                         });
                     }
@@ -248,7 +243,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
     }
 }
 
-impl<V> RenderOnce<V> for UniformList<V> {
+impl RenderOnce for UniformList {
     type Element = Self;
 
     fn element_id(&self) -> Option<crate::ElementId> {
@@ -260,24 +255,19 @@ impl<V> RenderOnce<V> for UniformList<V> {
     }
 }
 
-impl<V> UniformList<V> {
+impl UniformList {
     pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
         self.item_to_measure_index = item_index.unwrap_or(0);
         self
     }
 
-    fn measure_item(
-        &self,
-        view_state: &mut V,
-        list_width: Option<Pixels>,
-        cx: &mut ViewContext<V>,
-    ) -> Size<Pixels> {
+    fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
         if self.item_count == 0 {
             return Size::default();
         }
 
         let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
-        let mut items = (self.render_items)(view_state, item_ix..item_ix + 1, cx);
+        let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
         let mut item_to_measure = items.pop().unwrap();
         let available_space = size(
             list_width.map_or(AvailableSpace::MinContent, |width| {
@@ -285,7 +275,7 @@ impl<V> UniformList<V> {
             }),
             AvailableSpace::MinContent,
         );
-        item_to_measure.measure(available_space, view_state, cx)
+        item_to_measure.measure(available_space, cx)
     }
 
     pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
@@ -294,8 +284,8 @@ impl<V> UniformList<V> {
     }
 }
 
-impl<V> InteractiveElement<V> for UniformList<V> {
-    fn interactivity(&mut self) -> &mut crate::Interactivity<V> {
+impl InteractiveElement for UniformList {
+    fn interactivity(&mut self) -> &mut crate::Interactivity {
         &mut self.interactivity
     }
 }

crates/gpui2/src/gpui2.rs 🔗

@@ -121,7 +121,7 @@ pub trait VisualContext: Context {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>;
+        V: 'static + Render;
 
     fn update_view<V: 'static, R>(
         &mut self,
@@ -134,7 +134,7 @@ pub trait VisualContext: Context {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>;
+        V: 'static + Render;
 
     fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
     where

crates/gpui2/src/input.rs 🔗

@@ -1,4 +1,6 @@
-use crate::{AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext};
+use crate::{
+    AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext,
+};
 use std::ops::Range;
 
 /// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
@@ -43,9 +45,9 @@ pub struct ElementInputHandler<V> {
 impl<V: 'static> ElementInputHandler<V> {
     /// Used in [Element::paint] with the element's bounds and a view context for its
     /// containing view.
-    pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {
+    pub fn new(element_bounds: Bounds<Pixels>, view: View<V>, cx: &mut WindowContext) -> Self {
         ElementInputHandler {
-            view: cx.view().clone(),
+            view,
             element_bounds,
             cx: cx.to_async(),
         }

crates/gpui2/src/interactive.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{
-    div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
+    div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, RenderOnce,
+    ViewContext,
 };
 use smallvec::SmallVec;
 use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
@@ -63,24 +64,24 @@ pub struct Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Element<()>,
+    E: RenderOnce,
 {
     pub state: S,
     pub render_drag_handle: R,
-    view_type: PhantomData<V>,
+    view_element_types: PhantomData<(V, E)>,
 }
 
 impl<S, R, V, E> Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Element<()>,
+    E: Element,
 {
     pub fn new(state: S, render_drag_handle: R) -> Self {
         Drag {
             state,
             render_drag_handle,
-            view_type: PhantomData,
+            view_element_types: Default::default(),
         }
     }
 }
@@ -192,8 +193,8 @@ impl Deref for MouseExitEvent {
 #[derive(Debug, Clone, Default)]
 pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
 
-impl Render<Self> for ExternalPaths {
-    type Element = Div<Self>;
+impl Render for ExternalPaths {
+    type Element = Div;
 
     fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
         div() // Intentionally left empty because the platform will render icons for the dragged files
@@ -286,7 +287,7 @@ pub struct FocusEvent {
 mod test {
     use crate::{
         self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke,
-        ParentElement, Render, Stateful, TestAppContext, VisualContext,
+        ParentElement, Render, RenderOnce, Stateful, TestAppContext, VisualContext,
     };
 
     struct TestView {
@@ -297,16 +298,25 @@ mod test {
 
     actions!(TestAction);
 
-    impl Render<Self> for TestView {
-        type Element = Stateful<Self, Div<Self>>;
+    impl Render for TestView {
+        type Element = Stateful<Div>;
 
-        fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
+        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
             div().id("testview").child(
                 div()
                     .key_context("parent")
-                    .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
-                    .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
-                    .child(div().key_context("nested").track_focus(&self.focus_handle)),
+                    .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
+                    .on_action(
+                        cx.listener(|this: &mut TestView, _: &TestAction, _| {
+                            this.saw_action = true
+                        }),
+                    )
+                    .child(
+                        div()
+                            .key_context("nested")
+                            .track_focus(&self.focus_handle)
+                            .render_once(),
+                    ),
             )
         }
     }

crates/gpui2/src/style.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
     Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
     FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
-    SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext,
+    SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
 };
 use refineable::{Cascade, Refineable};
 use smallvec::SmallVec;
@@ -128,6 +128,13 @@ pub struct BoxShadow {
     pub spread_radius: Pixels,
 }
 
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub enum WhiteSpace {
+    #[default]
+    Normal,
+    Nowrap,
+}
+
 #[derive(Refineable, Clone, Debug)]
 #[refineable(Debug)]
 pub struct TextStyle {
@@ -139,6 +146,7 @@ pub struct TextStyle {
     pub font_weight: FontWeight,
     pub font_style: FontStyle,
     pub underline: Option<UnderlineStyle>,
+    pub white_space: WhiteSpace,
 }
 
 impl Default for TextStyle {
@@ -152,6 +160,7 @@ impl Default for TextStyle {
             font_weight: FontWeight::default(),
             font_style: FontStyle::default(),
             underline: None,
+            white_space: WhiteSpace::Normal,
         }
     }
 }
@@ -313,7 +322,7 @@ impl Style {
     }
 
     /// Paints the background of an element styled with this style.
-    pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
+    pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
         let rem_size = cx.rem_size();
 
         cx.with_z_index(0, |cx| {

crates/gpui2/src/styled.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
     DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
-    SharedString, StyleRefinement, Visibility,
+    SharedString, StyleRefinement, Visibility, WhiteSpace,
 };
 use crate::{BoxShadow, TextStyleRefinement};
 use smallvec::{smallvec, SmallVec};
@@ -101,6 +101,24 @@ pub trait Styled: Sized {
         self
     }
 
+    /// Sets the whitespace of the element to `normal`.
+    /// [Docs](https://tailwindcss.com/docs/whitespace#normal)
+    fn whitespace_normal(mut self) -> Self {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .white_space = Some(WhiteSpace::Normal);
+        self
+    }
+
+    /// Sets the whitespace of the element to `nowrap`.
+    /// [Docs](https://tailwindcss.com/docs/whitespace#nowrap)
+    fn whitespace_nowrap(mut self) -> Self {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .white_space = Some(WhiteSpace::Nowrap);
+        self
+    }
+
     /// Sets the flex direction of the element to `column`.
     /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
     fn flex_col(mut self) -> Self {

crates/gpui2/src/view.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
 };
 use anyhow::{Context, Result};
 use std::{
-    any::{Any, TypeId},
+    any::TypeId,
     hash::{Hash, Hasher},
 };
 
@@ -59,15 +59,15 @@ impl<V: 'static> View<V> {
         self.model.read(cx)
     }
 
-    pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
-    where
-        E: 'static + Element<V>,
-    {
-        RenderViewWith {
-            view: self.clone(),
-            element: Some(component),
-        }
-    }
+    // pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
+    // where
+    //     E: 'static + Element,
+    // {
+    //     RenderViewWith {
+    //         view: self.clone(),
+    //         element: Some(component),
+    //     }
+    // }
 
     pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
     where
@@ -77,6 +77,24 @@ impl<V: 'static> View<V> {
     }
 }
 
+impl<V: Render> Element for View<V> {
+    type State = Option<AnyElement>;
+
+    fn layout(
+        &mut self,
+        _state: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (LayoutId, Self::State) {
+        let mut element = self.update(cx, |view, cx| view.render(cx).into_any());
+        let layout_id = element.layout(cx);
+        (layout_id, Some(element))
+    }
+
+    fn paint(self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
+        element.take().unwrap().paint(cx);
+    }
+}
+
 impl<V> Clone for View<V> {
     fn clone(&self) -> Self {
         Self {
@@ -151,8 +169,8 @@ impl<V> Eq for WeakView<V> {}
 #[derive(Clone, Debug)]
 pub struct AnyView {
     model: AnyModel,
-    layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
-    paint: fn(&AnyView, Box<dyn Any>, &mut WindowContext),
+    layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
+    paint: fn(&AnyView, AnyElement, &mut WindowContext),
 }
 
 impl AnyView {
@@ -199,7 +217,7 @@ impl AnyView {
     }
 }
 
-impl<V: 'static + Render<V>> From<View<V>> for AnyView {
+impl<V: Render> From<View<V>> for AnyView {
     fn from(value: View<V>) -> Self {
         AnyView {
             model: value.model.into_any(),
@@ -209,36 +227,24 @@ impl<V: 'static + Render<V>> From<View<V>> for AnyView {
     }
 }
 
-impl<V: 'static + Render<V>, ParentV: 'static> Element<ParentV> for View<V> {
-    type State = Option<AnyElement<V>>;
+impl Element for AnyView {
+    type State = Option<AnyElement>;
 
     fn layout(
         &mut self,
-        _parent_view: &mut ParentV,
         _state: Option<Self::State>,
-        cx: &mut ViewContext<ParentV>,
+        cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
-        self.update(cx, |view, cx| {
-            let mut element = view.render(cx).into_any();
-            let layout_id = element.layout(view, cx);
-            (layout_id, Some(element))
-        })
+        let (layout_id, state) = (self.layout)(self, cx);
+        (layout_id, Some(state))
     }
 
-    fn paint(
-        self,
-        _: Bounds<Pixels>,
-        _parent: &mut ParentV,
-        element: &mut Self::State,
-        cx: &mut ViewContext<ParentV>,
-    ) {
-        self.update(cx, |view, cx| {
-            element.take().unwrap().paint(view, cx);
-        });
+    fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        (self.paint)(&self, state.take().unwrap(), cx)
     }
 }
 
-impl<V: 'static + Render<V>, ParentV: 'static> RenderOnce<ParentV> for View<V> {
+impl<V: 'static + Render> RenderOnce for View<V> {
     type Element = View<V>;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -250,31 +256,7 @@ impl<V: 'static + Render<V>, ParentV: 'static> RenderOnce<ParentV> for View<V> {
     }
 }
 
-impl<V: 'static> Element<V> for AnyView {
-    type State = Option<Box<dyn Any>>;
-
-    fn layout(
-        &mut self,
-        _view_state: &mut V,
-        _element_state: Option<Self::State>,
-        cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::State) {
-        let (layout_id, rendered_element) = (self.layout)(self, cx);
-        (layout_id, Some(rendered_element))
-    }
-
-    fn paint(
-        mut self,
-        _bounds: Bounds<Pixels>,
-        _view_state: &mut V,
-        rendered_element: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) {
-        (self.paint)(&mut self, rendered_element.take().unwrap(), cx)
-    }
-}
-
-impl<ParentV: 'static> RenderOnce<ParentV> for AnyView {
+impl RenderOnce for AnyView {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -288,8 +270,8 @@ impl<ParentV: 'static> RenderOnce<ParentV> for AnyView {
 
 pub struct AnyWeakView {
     model: AnyWeakModel,
-    layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
-    paint: fn(&AnyView, Box<dyn Any>, &mut WindowContext),
+    layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
+    paint: fn(&AnyView, AnyElement, &mut WindowContext),
 }
 
 impl AnyWeakView {
@@ -303,7 +285,7 @@ impl AnyWeakView {
     }
 }
 
-impl<V: 'static + Render<V>> From<WeakView<V>> for AnyWeakView {
+impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
     fn from(view: WeakView<V>) -> Self {
         Self {
             model: view.model.into(),
@@ -313,88 +295,40 @@ impl<V: 'static + Render<V>> From<WeakView<V>> for AnyWeakView {
     }
 }
 
-pub struct RenderViewWith<E, V> {
-    view: View<V>,
-    element: Option<E>,
-}
-
-impl<E, ParentV, V> Element<ParentV> for RenderViewWith<E, V>
+impl<T, E> Render for T
 where
-    E: 'static + Element<V>,
-    ParentV: 'static,
-    V: 'static,
+    T: 'static + FnMut(&mut WindowContext) -> E,
+    E: 'static + Send + Element,
 {
-    type State = Option<AnyElement<V>>;
+    type Element = E;
 
-    fn layout(
-        &mut self,
-        _: &mut ParentV,
-        _: Option<Self::State>,
-        cx: &mut ViewContext<ParentV>,
-    ) -> (LayoutId, Self::State) {
-        self.view.update(cx, |view, cx| {
-            let mut element = self.element.take().unwrap().into_any();
-            let layout_id = element.layout(view, cx);
-            (layout_id, Some(element))
-        })
-    }
-
-    fn paint(
-        self,
-        _: Bounds<Pixels>,
-        _: &mut ParentV,
-        element: &mut Self::State,
-        cx: &mut ViewContext<ParentV>,
-    ) {
-        self.view
-            .update(cx, |view, cx| element.take().unwrap().paint(view, cx))
-    }
-}
-
-impl<E, V, ParentV> RenderOnce<ParentV> for RenderViewWith<E, V>
-where
-    E: 'static + Element<V>,
-    V: 'static,
-    ParentV: 'static,
-{
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.element.as_ref().unwrap().element_id()
-    }
-
-    fn render_once(self) -> Self::Element {
-        self
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        (self)(cx)
     }
 }
 
 mod any_view {
-    use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext};
-    use std::any::Any;
+    use crate::{AnyElement, AnyView, BorrowWindow, Element, LayoutId, Render, WindowContext};
 
-    pub(crate) fn layout<V: 'static + Render<V>>(
+    pub(crate) fn layout<V: 'static + Render>(
         view: &AnyView,
         cx: &mut WindowContext,
-    ) -> (LayoutId, Box<dyn Any>) {
+    ) -> (LayoutId, AnyElement) {
         cx.with_element_id(Some(view.model.entity_id), |cx| {
             let view = view.clone().downcast::<V>().unwrap();
-            view.update(cx, |view, cx| {
-                let mut element = AnyElement::new(view.render(cx));
-                let layout_id = element.layout(view, cx);
-                (layout_id, Box::new(element) as Box<dyn Any>)
-            })
+            let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
+            let layout_id = element.layout(cx);
+            (layout_id, element)
         })
     }
 
-    pub(crate) fn paint<V: 'static + Render<V>>(
+    pub(crate) fn paint<V: 'static + Render>(
         view: &AnyView,
-        element: Box<dyn Any>,
+        element: AnyElement,
         cx: &mut WindowContext,
     ) {
         cx.with_element_id(Some(view.model.entity_id), |cx| {
-            let view = view.clone().downcast::<V>().unwrap();
-            let element = element.downcast::<AnyElement<V>>().unwrap();
-            view.update(cx, |view, cx| element.paint(view, cx))
+            element.paint(cx);
         })
     }
 }

crates/gpui2/src/window.rs 🔗

@@ -187,7 +187,7 @@ impl Drop for FocusHandle {
 
 /// FocusableView allows users of your view to easily
 /// focus it (using cx.focus_view(view))
-pub trait FocusableView: 'static + Render<Self> {
+pub trait FocusableView: 'static + Render {
     fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
 }
 
@@ -1436,6 +1436,82 @@ impl<'a> WindowContext<'a> {
             .dispatch_tree
             .bindings_for_action(action)
     }
+
+    pub fn listener_for<V: Render, E>(
+        &self,
+        view: &View<V>,
+        f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
+    ) -> impl Fn(&E, &mut WindowContext) + 'static {
+        let view = view.downgrade();
+        move |e: &E, cx: &mut WindowContext| {
+            view.update(cx, |view, cx| f(view, e, cx)).ok();
+        }
+    }
+
+    pub fn constructor_for<V: Render, R>(
+        &self,
+        view: &View<V>,
+        f: impl Fn(&mut V, &mut ViewContext<V>) -> R + 'static,
+    ) -> impl Fn(&mut WindowContext) -> R + 'static {
+        let view = view.clone();
+        move |cx: &mut WindowContext| view.update(cx, |view, cx| f(view, cx))
+    }
+
+    //========== ELEMENT RELATED FUNCTIONS ===========
+    pub fn with_key_dispatch<R>(
+        &mut self,
+        context: KeyContext,
+        focus_handle: Option<FocusHandle>,
+        f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
+    ) -> R {
+        let window = &mut self.window;
+        window
+            .current_frame
+            .dispatch_tree
+            .push_node(context.clone());
+        if let Some(focus_handle) = focus_handle.as_ref() {
+            window
+                .current_frame
+                .dispatch_tree
+                .make_focusable(focus_handle.id);
+        }
+        let result = f(focus_handle, self);
+
+        self.window.current_frame.dispatch_tree.pop_node();
+
+        result
+    }
+
+    /// Register a focus listener for the current frame only. It will be cleared
+    /// on the next frame render. You should use this method only from within elements,
+    /// and we may want to enforce that better via a different context type.
+    // todo!() Move this to `FrameContext` to emphasize its individuality?
+    pub fn on_focus_changed(
+        &mut self,
+        listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static,
+    ) {
+        self.window
+            .current_frame
+            .focus_listeners
+            .push(Box::new(move |event, cx| {
+                listener(event, cx);
+            }));
+    }
+
+    /// Set an input handler, such as [ElementInputHandler], which interfaces with the
+    /// platform to receive textual input with proper integration with concerns such
+    /// as IME interactions.
+    pub fn handle_input(
+        &mut self,
+        focus_handle: &FocusHandle,
+        input_handler: impl PlatformInputHandler,
+    ) {
+        if focus_handle.is_focused(self) {
+            self.window
+                .platform_window
+                .set_input_handler(Box::new(input_handler));
+        }
+    }
 }
 
 impl Context for WindowContext<'_> {
@@ -1520,7 +1596,7 @@ impl VisualContext for WindowContext<'_> {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -1559,7 +1635,7 @@ impl VisualContext for WindowContext<'_> {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render<V>,
+        V: 'static + Render,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -1617,6 +1693,10 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         self.borrow_mut()
     }
 
+    fn app(&self) -> &AppContext {
+        self.borrow()
+    }
+
     fn window(&self) -> &Window {
         self.borrow()
     }
@@ -2124,49 +2204,6 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         )
     }
 
-    /// Register a focus listener for the current frame only. It will be cleared
-    /// on the next frame render. You should use this method only from within elements,
-    /// and we may want to enforce that better via a different context type.
-    // todo!() Move this to `FrameContext` to emphasize its individuality?
-    pub fn on_focus_changed(
-        &mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) {
-        let handle = self.view().downgrade();
-        self.window
-            .current_frame
-            .focus_listeners
-            .push(Box::new(move |event, cx| {
-                handle
-                    .update(cx, |view, cx| listener(view, event, cx))
-                    .log_err();
-            }));
-    }
-
-    pub fn with_key_dispatch<R>(
-        &mut self,
-        context: KeyContext,
-        focus_handle: Option<FocusHandle>,
-        f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
-    ) -> R {
-        let window = &mut self.window;
-        window
-            .current_frame
-            .dispatch_tree
-            .push_node(context.clone());
-        if let Some(focus_handle) = focus_handle.as_ref() {
-            window
-                .current_frame
-                .dispatch_tree
-                .make_focusable(focus_handle.id);
-        }
-        let result = f(focus_handle, self);
-
-        self.window.current_frame.dispatch_tree.pop_node();
-
-        result
-    }
-
     pub fn spawn<Fut, R>(
         &mut self,
         f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2243,21 +2280,6 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             });
     }
 
-    /// Set an input handler, such as [ElementInputHandler], which interfaces with the
-    /// platform to receive textual input with proper integration with concerns such
-    /// as IME interactions.
-    pub fn handle_input(
-        &mut self,
-        focus_handle: &FocusHandle,
-        input_handler: impl PlatformInputHandler,
-    ) {
-        if focus_handle.is_focused(self) {
-            self.window
-                .platform_window
-                .set_input_handler(Box::new(input_handler));
-        }
-    }
-
     pub fn emit<Evt>(&mut self, event: Evt)
     where
         Evt: 'static,
@@ -2284,6 +2306,16 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     {
         self.defer(|_, cx| cx.emit(Manager::Dismiss))
     }
+
+    pub fn listener<E>(
+        &self,
+        f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
+    ) -> impl Fn(&E, &mut WindowContext) + 'static {
+        let view = self.view().downgrade();
+        move |e: &E, cx: &mut WindowContext| {
+            view.update(cx, |view, cx| f(view, e, cx)).ok();
+        }
+    }
 }
 
 impl<V> Context for ViewContext<'_, V> {
@@ -2335,7 +2367,7 @@ impl<V> Context for ViewContext<'_, V> {
 }
 
 impl<V: 'static> VisualContext for ViewContext<'_, V> {
-    fn build_view<W: Render<W> + 'static>(
+    fn build_view<W: Render + 'static>(
         &mut self,
         build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
     ) -> Self::Result<View<W>> {
@@ -2355,7 +2387,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> {
         build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
     ) -> Self::Result<View<W>>
     where
-        W: 'static + Render<W>,
+        W: 'static + Render,
     {
         self.window_cx.replace_root_view(build_view)
     }
@@ -2400,7 +2432,7 @@ pub struct WindowHandle<V> {
     state_type: PhantomData<V>,
 }
 
-impl<V: 'static + Render<V>> WindowHandle<V> {
+impl<V: 'static + Render> WindowHandle<V> {
     pub fn new(id: WindowId) -> Self {
         WindowHandle {
             any_handle: AnyWindowHandle {

crates/gpui2_macros/src/derive_render_once.rs 🔗

@@ -1,37 +1,17 @@
 use proc_macro::TokenStream;
 use quote::quote;
-use syn::{parse_macro_input, parse_quote, DeriveInput};
+use syn::{parse_macro_input, DeriveInput};
 
 pub fn derive_render_once(input: TokenStream) -> TokenStream {
     let ast = parse_macro_input!(input as DeriveInput);
     let type_name = &ast.ident;
-
-    let mut trait_generics = ast.generics.clone();
-    let view_type = if let Some(view_type) = specified_view_type(&ast) {
-        quote! { #view_type }
-    } else {
-        if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| {
-            if let syn::GenericParam::Type(type_param) = param {
-                Some(type_param.ident.clone())
-            } else {
-                None
-            }
-        }) {
-            quote! { #first_type_param }
-        } else {
-            trait_generics.params.push(parse_quote! { V: 'static });
-            quote! { V }
-        }
-    };
-
-    let (impl_generics, _, where_clause) = trait_generics.split_for_impl();
-    let (_, type_generics, _) = ast.generics.split_for_impl();
+    let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
 
     let gen = quote! {
-        impl #impl_generics gpui::RenderOnce<#view_type> for #type_name #type_generics
+        impl #impl_generics gpui::RenderOnce for #type_name #type_generics
         #where_clause
         {
-            type Element = gpui::CompositeElement<#view_type, Self>;
+            type Element = gpui::CompositeElement<Self>;
 
             fn element_id(&self) -> Option<ElementId> {
                 None
@@ -45,20 +25,3 @@ pub fn derive_render_once(input: TokenStream) -> TokenStream {
 
     gen.into()
 }
-
-fn specified_view_type(ast: &DeriveInput) -> Option<proc_macro2::Ident> {
-    ast.attrs.iter().find_map(|attr| {
-        if attr.path.is_ident("view") {
-            if let Ok(syn::Meta::NameValue(meta_name_value)) = attr.parse_meta() {
-                if let syn::Lit::Str(lit_str) = meta_name_value.lit {
-                    return Some(
-                        lit_str
-                            .parse::<syn::Ident>()
-                            .expect("Failed to parse view_type"),
-                    );
-                }
-            }
-        }
-        None
-    })
-}

crates/language2/src/buffer.rs 🔗

@@ -7,6 +7,7 @@ pub use crate::{
 use crate::{
     diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
     language_settings::{language_settings, LanguageSettings},
+    markdown::parse_markdown,
     outline::OutlineItem,
     syntax_map::{
         SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
@@ -155,12 +156,52 @@ pub struct Diagnostic {
     pub is_unnecessary: bool,
 }
 
+pub async fn prepare_completion_documentation(
+    documentation: &lsp::Documentation,
+    language_registry: &Arc<LanguageRegistry>,
+    language: Option<Arc<Language>>,
+) -> Documentation {
+    match documentation {
+        lsp::Documentation::String(text) => {
+            if text.lines().count() <= 1 {
+                Documentation::SingleLine(text.clone())
+            } else {
+                Documentation::MultiLinePlainText(text.clone())
+            }
+        }
+
+        lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
+            lsp::MarkupKind::PlainText => {
+                if value.lines().count() <= 1 {
+                    Documentation::SingleLine(value.clone())
+                } else {
+                    Documentation::MultiLinePlainText(value.clone())
+                }
+            }
+
+            lsp::MarkupKind::Markdown => {
+                let parsed = parse_markdown(value, language_registry, language).await;
+                Documentation::MultiLineMarkdown(parsed)
+            }
+        },
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum Documentation {
+    Undocumented,
+    SingleLine(String),
+    MultiLinePlainText(String),
+    MultiLineMarkdown(ParsedMarkdown),
+}
+
 #[derive(Clone, Debug)]
 pub struct Completion {
     pub old_range: Range<Anchor>,
     pub new_text: String,
     pub label: CodeLabel,
     pub server_id: LanguageServerId,
+    pub documentation: Option<Documentation>,
     pub lsp_completion: lsp::CompletionItem,
 }
 

crates/language2/src/proto.rs 🔗

@@ -482,6 +482,7 @@ pub async fn deserialize_completion(
                 lsp_completion.filter_text.as_deref(),
             )
         }),
+        documentation: None,
         server_id: LanguageServerId(completion.server_id as usize),
         lsp_completion,
     })

crates/picker2/src/picker2.rs 🔗

@@ -1,10 +1,10 @@
 use editor::Editor;
 use gpui::{
     div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton,
-    Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
+    MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
 };
 use std::{cmp, sync::Arc};
-use ui::{prelude::*, v_stack, Divider, Label, TextColor};
+use ui::{prelude::*, v_stack, Color, Divider, Label};
 
 pub struct Picker<D: PickerDelegate> {
     pub delegate: D,
@@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> {
 }
 
 pub trait PickerDelegate: Sized + 'static {
-    type ListItem: RenderOnce<Picker<Self>>;
+    type ListItem: RenderOnce;
 
     fn match_count(&self) -> usize;
     fn selected_index(&self) -> usize;
@@ -180,21 +180,21 @@ impl<D: PickerDelegate> Picker<D> {
     }
 }
 
-impl<D: PickerDelegate> Render<Self> for Picker<D> {
-    type Element = Div<Self>;
+impl<D: PickerDelegate> Render for Picker<D> {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
             .key_context("picker")
             .size_full()
             .elevation_2(cx)
-            .on_action(Self::select_next)
-            .on_action(Self::select_prev)
-            .on_action(Self::select_first)
-            .on_action(Self::select_last)
-            .on_action(Self::cancel)
-            .on_action(Self::confirm)
-            .on_action(Self::secondary_confirm)
+            .on_action(cx.listener(Self::select_next))
+            .on_action(cx.listener(Self::select_prev))
+            .on_action(cx.listener(Self::select_first))
+            .on_action(cx.listener(Self::select_last))
+            .on_action(cx.listener(Self::cancel))
+            .on_action(cx.listener(Self::confirm))
+            .on_action(cx.listener(Self::secondary_confirm))
             .child(
                 v_stack()
                     .py_0p5()
@@ -208,31 +208,37 @@ impl<D: PickerDelegate> Render<Self> for Picker<D> {
                         .p_1()
                         .grow()
                         .child(
-                            uniform_list("candidates", self.delegate.match_count(), {
-                                move |this: &mut Self, visible_range, cx| {
-                                    let selected_ix = this.delegate.selected_index();
-                                    visible_range
-                                        .map(|ix| {
-                                            div()
-                                                .on_mouse_down(
-                                                    MouseButton::Left,
-                                                    move |this: &mut Self, event, cx| {
-                                                        this.handle_click(
-                                                            ix,
-                                                            event.modifiers.command,
-                                                            cx,
-                                                        )
-                                                    },
-                                                )
-                                                .child(this.delegate.render_match(
-                                                    ix,
-                                                    ix == selected_ix,
-                                                    cx,
-                                                ))
-                                        })
-                                        .collect()
-                                }
-                            })
+                            uniform_list(
+                                cx.view().clone(),
+                                "candidates",
+                                self.delegate.match_count(),
+                                {
+                                    let selected_index = self.delegate.selected_index();
+
+                                    move |picker, visible_range, cx| {
+                                        visible_range
+                                            .map(|ix| {
+                                                div()
+                                                    .on_mouse_down(
+                                                        MouseButton::Left,
+                                                        cx.listener(move |this, event: &MouseDownEvent, cx| {
+                                                            this.handle_click(
+                                                                ix,
+                                                                event.modifiers.command,
+                                                                cx,
+                                                            )
+                                                        }),
+                                                    )
+                                                    .child(picker.delegate.render_match(
+                                                        ix,
+                                                        ix == selected_index,
+                                                        cx,
+                                                    ))
+                                            })
+                                            .collect()
+                                    }
+                                },
+                            )
                             .track_scroll(self.scroll_handle.clone()),
                         )
                         .max_h_72()
@@ -244,7 +250,7 @@ impl<D: PickerDelegate> Render<Self> for Picker<D> {
                     v_stack().p_1().grow().child(
                         div()
                             .px_1()
-                            .child(Label::new("No matches").color(TextColor::Muted)),
+                            .child(Label::new("No matches").color(Color::Muted)),
                     ),
                 )
             })

crates/project2/src/lsp_command.rs 🔗

@@ -10,7 +10,7 @@ use futures::future;
 use gpui::{AppContext, AsyncAppContext, Model};
 use language::{
     language_settings::{language_settings, InlayHintKind},
-    point_from_lsp, point_to_lsp,
+    point_from_lsp, point_to_lsp, prepare_completion_documentation,
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
     CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
@@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions {
     async fn response_from_lsp(
         self,
         completions: Option<lsp::CompletionResponse>,
-        _: Model<Project>,
+        project: Model<Project>,
         buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
@@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions {
             Default::default()
         };
 
-        let completions = buffer.update(&mut cx, |buffer, _| {
+        let completions = buffer.update(&mut cx, |buffer, cx| {
+            let language_registry = project.read(cx).languages().clone();
             let language = buffer.language().cloned();
             let snapshot = buffer.snapshot();
             let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
@@ -1443,14 +1444,29 @@ impl LspCommand for GetCompletions {
                         }
                     };
 
+                    let language_registry = language_registry.clone();
                     let language = language.clone();
                     LineEnding::normalize(&mut new_text);
                     Some(async move {
                         let mut label = None;
-                        if let Some(language) = language {
+                        if let Some(language) = language.as_ref() {
                             language.process_completion(&mut lsp_completion).await;
                             label = language.label_for_completion(&lsp_completion).await;
                         }
+
+                        let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
+                            Some(
+                                prepare_completion_documentation(
+                                    lsp_docs,
+                                    &language_registry,
+                                    language.clone(),
+                                )
+                                .await,
+                            )
+                        } else {
+                            None
+                        };
+
                         Completion {
                             old_range,
                             new_text,
@@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions {
                                     lsp_completion.filter_text.as_deref(),
                                 )
                             }),
+                            documentation,
                             server_id,
                             lsp_completion,
                         }

crates/project_panel2/src/project_panel.rs 🔗

@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
 use gpui::{
     actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
     ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
-    Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, Stateful,
-    StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext,
-    VisualContext as _, WeakView, WindowContext,
+    Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render,
+    RenderOnce, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View,
+    ViewContext, VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
@@ -371,7 +371,7 @@ impl ProjectPanel {
         _entry_id: ProjectEntryId,
         _cx: &mut ViewContext<Self>,
     ) {
-        todo!()
+        // todo!()
         //     let project = self.project.read(cx);
 
         //     let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
@@ -1339,7 +1339,7 @@ impl ProjectPanel {
         editor: Option<&View<Editor>>,
         padding: Pixels,
         cx: &mut ViewContext<Self>,
-    ) -> Div<Self> {
+    ) -> Div {
         let show_editor = details.is_editing && !details.is_processing;
 
         let theme = cx.theme();
@@ -1378,7 +1378,7 @@ impl ProjectPanel {
         details: EntryDetails,
         // dragged_entry_destination: &mut Option<Arc<Path>>,
         cx: &mut ViewContext<Self>,
-    ) -> Stateful<Self, Div<Self>> {
+    ) -> Stateful<Div> {
         let kind = details.kind;
         let settings = ProjectPanelSettings::get_global(cx);
         const INDENT_SIZE: Pixels = px(16.0);
@@ -1396,7 +1396,7 @@ impl ProjectPanel {
                 this.bg(cx.theme().colors().element_selected)
             })
             .hover(|style| style.bg(cx.theme().colors().element_hover))
-            .on_click(move |this, event, cx| {
+            .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
                 if !show_editor {
                     if kind.is_dir() {
                         this.toggle_expanded(entry_id, cx);
@@ -1408,10 +1408,13 @@ impl ProjectPanel {
                         }
                     }
                 }
-            })
-            .on_mouse_down(MouseButton::Right, move |this, event, cx| {
-                this.deploy_context_menu(event.position, entry_id, cx);
-            })
+            }))
+            .on_mouse_down(
+                MouseButton::Right,
+                cx.listener(move |this, event: &MouseDownEvent, cx| {
+                    this.deploy_context_menu(event.position, entry_id, cx);
+                }),
+            )
         // .on_drop::<ProjectEntryId>(|this, event, cx| {
         //     this.move_entry(
         //         *dragged_entry,
@@ -1423,10 +1426,10 @@ impl ProjectPanel {
     }
 }
 
-impl Render<Self> for ProjectPanel {
-    type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
+impl Render for ProjectPanel {
+    type Element = Focusable<Stateful<Div>>;
 
-    fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let has_worktree = self.visible_entries.len() != 0;
 
         if has_worktree {
@@ -1434,40 +1437,43 @@ impl Render<Self> for ProjectPanel {
                 .id("project-panel")
                 .size_full()
                 .key_context("ProjectPanel")
-                .on_action(Self::select_next)
-                .on_action(Self::select_prev)
-                .on_action(Self::expand_selected_entry)
-                .on_action(Self::collapse_selected_entry)
-                .on_action(Self::collapse_all_entries)
-                .on_action(Self::new_file)
-                .on_action(Self::new_directory)
-                .on_action(Self::rename)
-                .on_action(Self::delete)
-                .on_action(Self::confirm)
-                .on_action(Self::open_file)
-                .on_action(Self::cancel)
-                .on_action(Self::cut)
-                .on_action(Self::copy)
-                .on_action(Self::copy_path)
-                .on_action(Self::copy_relative_path)
-                .on_action(Self::paste)
-                .on_action(Self::reveal_in_finder)
-                .on_action(Self::open_in_terminal)
-                .on_action(Self::new_search_in_directory)
+                .on_action(cx.listener(Self::select_next))
+                .on_action(cx.listener(Self::select_prev))
+                .on_action(cx.listener(Self::expand_selected_entry))
+                .on_action(cx.listener(Self::collapse_selected_entry))
+                .on_action(cx.listener(Self::collapse_all_entries))
+                .on_action(cx.listener(Self::new_file))
+                .on_action(cx.listener(Self::new_directory))
+                .on_action(cx.listener(Self::rename))
+                .on_action(cx.listener(Self::delete))
+                .on_action(cx.listener(Self::confirm))
+                .on_action(cx.listener(Self::open_file))
+                .on_action(cx.listener(Self::cancel))
+                .on_action(cx.listener(Self::cut))
+                .on_action(cx.listener(Self::copy))
+                .on_action(cx.listener(Self::copy_path))
+                .on_action(cx.listener(Self::copy_relative_path))
+                .on_action(cx.listener(Self::paste))
+                .on_action(cx.listener(Self::reveal_in_finder))
+                .on_action(cx.listener(Self::open_in_terminal))
+                .on_action(cx.listener(Self::new_search_in_directory))
                 .track_focus(&self.focus_handle)
                 .child(
                     uniform_list(
+                        cx.view().clone(),
                         "entries",
                         self.visible_entries
                             .iter()
                             .map(|(_, worktree_entries)| worktree_entries.len())
                             .sum(),
-                        |this: &mut Self, range, cx| {
-                            let mut items = Vec::new();
-                            this.for_each_visible_entry(range, cx, |id, details, cx| {
-                                items.push(this.render_entry(id, details, cx));
-                            });
-                            items
+                        {
+                            |this, range, cx| {
+                                let mut items = Vec::new();
+                                this.for_each_visible_entry(range, cx, |id, details, cx| {
+                                    items.push(this.render_entry(id, details, cx));
+                                });
+                                items
+                            }
                         },
                     )
                     .size_full()

crates/rich_text2/src/rich_text.rs 🔗

@@ -56,12 +56,12 @@ pub struct Mention {
 }
 
 impl RichText {
-    pub fn element<V: 'static>(
+    pub fn element(
         &self,
         // syntax: Arc<SyntaxTheme>,
         //  style: RichTextStyle,
         // cx: &mut ViewContext<V>,
-    ) -> AnyElement<V> {
+    ) -> AnyElement {
         todo!();
 
         // let mut region_id = 0;

crates/search2/Cargo.toml 🔗

@@ -0,0 +1,40 @@
+[package]
+name = "search2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/search.rs"
+doctest = false
+
+[dependencies]
+bitflags = "1"
+collections = { path = "../collections" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+language = { package = "language2", path = "../language2" }
+menu = { package = "menu2", path = "../menu2" }
+project = { package = "project2", path = "../project2" }
+settings = { package = "settings2", path = "../settings2" }
+theme = { package = "theme2", path = "../theme2" }
+util = { path = "../util" }
+ui = {package = "ui2", path = "../ui2"}
+workspace = { package = "workspace2", path = "../workspace2" }
+#semantic_index = { path = "../semantic_index" }
+anyhow.workspace = true
+futures.workspace = true
+log.workspace = true
+postage.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+serde_json.workspace = true
+[dev-dependencies]
+client = { package = "client2", path = "../client2", features = ["test-support"] }
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+unindent.workspace = true

crates/search2/src/buffer_search.rs 🔗

@@ -0,0 +1,1704 @@
+use crate::{
+    history::SearchHistory,
+    mode::{next_mode, SearchMode},
+    search_bar::{render_nav_button, render_search_mode_button},
+    ActivateRegexMode, ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery,
+    ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,
+    ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
+};
+use collections::HashMap;
+use editor::Editor;
+use futures::channel::oneshot;
+use gpui::{
+    actions, div, red, Action, AppContext, Div, EventEmitter, InteractiveElement as _,
+    ParentElement as _, Render, RenderOnce, Styled, Subscription, Task, View, ViewContext,
+    VisualContext as _, WindowContext,
+};
+use project::search::SearchQuery;
+use serde::Deserialize;
+use std::{any::Any, sync::Arc};
+
+use ui::{h_stack, ButtonGroup, Icon, IconButton, IconElement};
+use util::ResultExt;
+use workspace::{
+    item::ItemHandle,
+    searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
+    ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+#[derive(PartialEq, Clone, Deserialize, Default, Action)]
+pub struct Deploy {
+    pub focus: bool,
+}
+
+actions!(Dismiss, FocusEditor);
+
+pub enum Event {
+    UpdateLocation,
+}
+
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))
+        .detach();
+}
+
+pub struct BufferSearchBar {
+    query_editor: View<Editor>,
+    replacement_editor: View<Editor>,
+    active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
+    active_match_index: Option<usize>,
+    active_searchable_item_subscription: Option<Subscription>,
+    active_search: Option<Arc<SearchQuery>>,
+    searchable_items_with_matches:
+        HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
+    pending_search: Option<Task<()>>,
+    search_options: SearchOptions,
+    default_options: SearchOptions,
+    query_contains_error: bool,
+    dismissed: bool,
+    search_history: SearchHistory,
+    current_mode: SearchMode,
+    replace_enabled: bool,
+}
+
+impl EventEmitter<Event> for BufferSearchBar {}
+impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}
+impl Render for BufferSearchBar {
+    type Element = Div;
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        // let query_container_style = if self.query_contains_error {
+        //     theme.search.invalid_editor
+        // } else {
+        //     theme.search.editor.input.container
+        // };
+        if self.dismissed {
+            return div();
+        }
+        let supported_options = self.supported_options();
+
+        let previous_query_keystrokes = cx
+            .bindings_for_action(&PreviousHistoryQuery {})
+            .into_iter()
+            .next()
+            .map(|binding| {
+                binding
+                    .keystrokes()
+                    .iter()
+                    .map(|k| k.to_string())
+                    .collect::<Vec<_>>()
+            });
+        let next_query_keystrokes = cx
+            .bindings_for_action(&NextHistoryQuery {})
+            .into_iter()
+            .next()
+            .map(|binding| {
+                binding
+                    .keystrokes()
+                    .iter()
+                    .map(|k| k.to_string())
+                    .collect::<Vec<_>>()
+            });
+        let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
+            (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
+                format!(
+                    "Search ({}/{} for previous/next query)",
+                    previous_query_keystrokes.join(" "),
+                    next_query_keystrokes.join(" ")
+                )
+            }
+            (None, Some(next_query_keystrokes)) => {
+                format!(
+                    "Search ({} for next query)",
+                    next_query_keystrokes.join(" ")
+                )
+            }
+            (Some(previous_query_keystrokes), None) => {
+                format!(
+                    "Search ({} for previous query)",
+                    previous_query_keystrokes.join(" ")
+                )
+            }
+            (None, None) => String::new(),
+        };
+        let new_placeholder_text = Arc::from(new_placeholder_text);
+        self.query_editor.update(cx, |editor, cx| {
+            editor.set_placeholder_text(new_placeholder_text, cx);
+        });
+        self.replacement_editor.update(cx, |editor, cx| {
+            editor.set_placeholder_text("Replace with...", cx);
+        });
+
+        let search_button_for_mode = |mode| {
+            let is_active = self.current_mode == mode;
+
+            render_search_mode_button(
+                mode,
+                is_active,
+                cx.listener(move |this, _, cx| {
+                    this.activate_search_mode(mode, cx);
+                }),
+            )
+        };
+        let search_option_button = |option| {
+            let is_active = self.search_options.contains(option);
+            option.as_button(is_active)
+        };
+        let match_count = self
+            .active_searchable_item
+            .as_ref()
+            .and_then(|searchable_item| {
+                if self.query(cx).is_empty() {
+                    return None;
+                }
+                let matches = self
+                    .searchable_items_with_matches
+                    .get(&searchable_item.downgrade())?;
+                let message = if let Some(match_ix) = self.active_match_index {
+                    format!("{}/{}", match_ix + 1, matches.len())
+                } else {
+                    "No matches".to_string()
+                };
+
+                Some(ui::Label::new(message))
+            });
+        let nav_button_for_direction = |icon, direction| {
+            render_nav_button(
+                icon,
+                self.active_match_index.is_some(),
+                cx.listener(move |this, _, cx| match direction {
+                    Direction::Prev => this.select_prev_match(&Default::default(), cx),
+                    Direction::Next => this.select_next_match(&Default::default(), cx),
+                }),
+            )
+        };
+        let should_show_replace_input = self.replace_enabled && supported_options.replacement;
+        let replace_all = should_show_replace_input
+            .then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll));
+        let replace_next = should_show_replace_input
+            .then(|| super::render_replace_button(ReplaceNext, ui::Icon::Replace));
+        let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
+
+        h_stack()
+            .key_context("BufferSearchBar")
+            .when(in_replace, |this| {
+                this.key_context("in_replace")
+                    .on_action(cx.listener(Self::replace_next))
+                    .on_action(cx.listener(Self::replace_all))
+            })
+            .on_action(cx.listener(Self::previous_history_query))
+            .on_action(cx.listener(Self::next_history_query))
+            .w_full()
+            .p_1()
+            .child(
+                div()
+                    .flex()
+                    .flex_1()
+                    .border_1()
+                    .border_color(red())
+                    .rounded_md()
+                    .items_center()
+                    .child(IconElement::new(Icon::MagnifyingGlass))
+                    .child(self.query_editor.clone())
+                    .children(
+                        supported_options
+                            .case
+                            .then(|| search_option_button(SearchOptions::CASE_SENSITIVE)),
+                    )
+                    .children(
+                        supported_options
+                            .word
+                            .then(|| search_option_button(SearchOptions::WHOLE_WORD)),
+                    ),
+            )
+            .child(
+                h_stack()
+                    .flex_none()
+                    .child(ButtonGroup::new(vec![
+                        search_button_for_mode(SearchMode::Text),
+                        search_button_for_mode(SearchMode::Regex),
+                    ]))
+                    .when(supported_options.replacement, |this| {
+                        this.child(super::toggle_replace_button(self.replace_enabled))
+                    }),
+            )
+            .child(
+                h_stack()
+                    .gap_0p5()
+                    .flex_1()
+                    .when(self.replace_enabled, |this| {
+                        this.child(self.replacement_editor.clone())
+                            .children(replace_next)
+                            .children(replace_all)
+                    }),
+            )
+            .child(
+                h_stack()
+                    .gap_0p5()
+                    .flex_none()
+                    .child(self.render_action_button())
+                    .children(match_count)
+                    .child(nav_button_for_direction(
+                        ui::Icon::ChevronLeft,
+                        Direction::Prev,
+                    ))
+                    .child(nav_button_for_direction(
+                        ui::Icon::ChevronRight,
+                        Direction::Next,
+                    )),
+            )
+    }
+}
+
+impl ToolbarItemView for BufferSearchBar {
+    fn set_active_pane_item(
+        &mut self,
+        item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) -> ToolbarItemLocation {
+        cx.notify();
+        self.active_searchable_item_subscription.take();
+        self.active_searchable_item.take();
+
+        self.pending_search.take();
+
+        if let Some(searchable_item_handle) =
+            item.and_then(|item| item.to_searchable_item_handle(cx))
+        {
+            let this = cx.view().downgrade();
+
+            searchable_item_handle
+                .subscribe_to_search_events(
+                    cx,
+                    Box::new(move |search_event, cx| {
+                        if let Some(this) = this.upgrade() {
+                            this.update(cx, |this, cx| {
+                                this.on_active_searchable_item_event(search_event, cx)
+                            });
+                        }
+                    }),
+                )
+                .detach();
+
+            self.active_searchable_item = Some(searchable_item_handle);
+            let _ = self.update_matches(cx);
+            if !self.dismissed {
+                return ToolbarItemLocation::Secondary;
+            }
+        }
+        ToolbarItemLocation::Hidden
+    }
+
+    fn row_count(&self, _: &WindowContext<'_>) -> usize {
+        1
+    }
+}
+
+impl BufferSearchBar {
+    pub fn register(workspace: &mut Workspace) {
+        workspace.register_action(|workspace, a: &Deploy, cx| {
+            workspace.active_pane().update(cx, |this, cx| {
+                this.toolbar().update(cx, |this, cx| {
+                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+                        search_bar.update(cx, |this, cx| {
+                            if this.is_dismissed() {
+                                this.show(cx);
+                            } else {
+                                this.dismiss(&Dismiss, cx);
+                            }
+                        });
+                        return;
+                    }
+                    let view = cx.build_view(|cx| BufferSearchBar::new(cx));
+                    this.add_item(view.clone(), cx);
+                    view.update(cx, |this, cx| this.deploy(a, cx));
+                    cx.notify();
+                })
+            });
+        });
+        fn register_action<A: Action>(
+            workspace: &mut Workspace,
+            update: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+        ) {
+            workspace.register_action(move |workspace, action: &A, cx| {
+                workspace.active_pane().update(cx, move |this, cx| {
+                    this.toolbar().update(cx, move |this, cx| {
+                        if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+                            search_bar.update(cx, move |this, cx| update(this, action, cx));
+                            cx.notify();
+                        }
+                    })
+                });
+            });
+        }
+
+        register_action(workspace, |this, action: &ToggleCaseSensitive, cx| {
+            if this.supported_options().case {
+                this.toggle_case_sensitive(action, cx);
+            }
+        });
+        register_action(workspace, |this, action: &ToggleWholeWord, cx| {
+            if this.supported_options().word {
+                this.toggle_whole_word(action, cx);
+            }
+        });
+        register_action(workspace, |this, action: &ToggleReplace, cx| {
+            if this.supported_options().replacement {
+                this.toggle_replace(action, cx);
+            }
+        });
+        register_action(workspace, |this, _: &ActivateRegexMode, cx| {
+            if this.supported_options().regex {
+                this.activate_search_mode(SearchMode::Regex, cx);
+            }
+        });
+        register_action(workspace, |this, _: &ActivateTextMode, cx| {
+            this.activate_search_mode(SearchMode::Text, cx);
+        });
+        register_action(workspace, |this, action: &CycleMode, cx| {
+            if this.supported_options().regex {
+                // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
+                // cycling.
+                this.cycle_mode(action, cx)
+            }
+        });
+        register_action(workspace, |this, action: &SelectNextMatch, cx| {
+            this.select_next_match(action, cx);
+        });
+        register_action(workspace, |this, action: &SelectPrevMatch, cx| {
+            this.select_prev_match(action, cx);
+        });
+        register_action(workspace, |this, action: &SelectAllMatches, cx| {
+            this.select_all_matches(action, cx);
+        });
+        register_action(workspace, |this, _: &editor::Cancel, cx| {
+            if !this.dismissed {
+                this.dismiss(&Dismiss, cx);
+            }
+        });
+    }
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        let query_editor = cx.build_view(|cx| Editor::single_line(cx));
+        cx.subscribe(&query_editor, Self::on_query_editor_event)
+            .detach();
+        let replacement_editor = cx.build_view(|cx| Editor::single_line(cx));
+        cx.subscribe(&replacement_editor, Self::on_query_editor_event)
+            .detach();
+        Self {
+            query_editor,
+            replacement_editor,
+            active_searchable_item: None,
+            active_searchable_item_subscription: None,
+            active_match_index: None,
+            searchable_items_with_matches: Default::default(),
+            default_options: SearchOptions::NONE,
+            search_options: SearchOptions::NONE,
+            pending_search: None,
+            query_contains_error: false,
+            dismissed: true,
+            search_history: SearchHistory::default(),
+            current_mode: SearchMode::default(),
+            active_search: None,
+            replace_enabled: false,
+        }
+    }
+
+    pub fn is_dismissed(&self) -> bool {
+        self.dismissed
+    }
+
+    pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
+        self.dismissed = true;
+        for searchable_item in self.searchable_items_with_matches.keys() {
+            if let Some(searchable_item) =
+                WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
+            {
+                searchable_item.clear_matches(cx);
+            }
+        }
+        if let Some(active_editor) = self.active_searchable_item.as_ref() {
+            let handle = active_editor.focus_handle(cx);
+            cx.focus(&handle);
+        }
+        cx.emit(Event::UpdateLocation);
+        cx.notify();
+    }
+
+    pub fn deploy(&mut self, deploy: &Deploy, cx: &mut ViewContext<Self>) -> bool {
+        if self.show(cx) {
+            self.search_suggested(cx);
+            if deploy.focus {
+                self.select_query(cx);
+                let handle = cx.focus_handle();
+                cx.focus(&handle);
+            }
+            return true;
+        }
+
+        false
+    }
+
+    pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
+        if self.active_searchable_item.is_none() {
+            return false;
+        }
+        self.dismissed = false;
+        cx.notify();
+        cx.emit(Event::UpdateLocation);
+        true
+    }
+
+    fn supported_options(&self) -> workspace::searchable::SearchOptions {
+        self.active_searchable_item
+            .as_deref()
+            .map(SearchableItemHandle::supported_options)
+            .unwrap_or_default()
+    }
+    pub fn search_suggested(&mut self, cx: &mut ViewContext<Self>) {
+        let search = self
+            .query_suggestion(cx)
+            .map(|suggestion| self.search(&suggestion, Some(self.default_options), cx));
+
+        if let Some(search) = search {
+            cx.spawn(|this, mut cx| async move {
+                search.await?;
+                this.update(&mut cx, |this, cx| this.activate_current_match(cx))
+            })
+            .detach_and_log_err(cx);
+        }
+    }
+
+    pub fn activate_current_match(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(match_ix) = self.active_match_index {
+            if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
+                if let Some(matches) = self
+                    .searchable_items_with_matches
+                    .get(&active_searchable_item.downgrade())
+                {
+                    active_searchable_item.activate_match(match_ix, matches, cx)
+                }
+            }
+        }
+    }
+
+    pub fn select_query(&mut self, cx: &mut ViewContext<Self>) {
+        self.query_editor.update(cx, |query_editor, cx| {
+            query_editor.select_all(&Default::default(), cx);
+        });
+    }
+
+    pub fn query(&self, cx: &WindowContext) -> String {
+        self.query_editor.read(cx).text(cx)
+    }
+    pub fn replacement(&self, cx: &WindowContext) -> String {
+        self.replacement_editor.read(cx).text(cx)
+    }
+    pub fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<String> {
+        self.active_searchable_item
+            .as_ref()
+            .map(|searchable_item| searchable_item.query_suggestion(cx))
+            .filter(|suggestion| !suggestion.is_empty())
+    }
+
+    pub fn set_replacement(&mut self, replacement: Option<&str>, cx: &mut ViewContext<Self>) {
+        if replacement.is_none() {
+            self.replace_enabled = false;
+            return;
+        }
+        self.replace_enabled = true;
+        self.replacement_editor
+            .update(cx, |replacement_editor, cx| {
+                replacement_editor
+                    .buffer()
+                    .update(cx, |replacement_buffer, cx| {
+                        let len = replacement_buffer.len(cx);
+                        replacement_buffer.edit([(0..len, replacement.unwrap())], None, cx);
+                    });
+            });
+    }
+
+    pub fn search(
+        &mut self,
+        query: &str,
+        options: Option<SearchOptions>,
+        cx: &mut ViewContext<Self>,
+    ) -> oneshot::Receiver<()> {
+        let options = options.unwrap_or(self.default_options);
+        if query != self.query(cx) || self.search_options != options {
+            self.query_editor.update(cx, |query_editor, cx| {
+                query_editor.buffer().update(cx, |query_buffer, cx| {
+                    let len = query_buffer.len(cx);
+                    query_buffer.edit([(0..len, query)], None, cx);
+                });
+            });
+            self.search_options = options;
+            self.query_contains_error = false;
+            self.clear_matches(cx);
+            cx.notify();
+        }
+        self.update_matches(cx)
+    }
+
+    fn render_action_button(&self) -> impl RenderOnce {
+        // let tooltip_style = theme.tooltip.clone();
+
+        // let style = theme.search.action_button.clone();
+
+        IconButton::new(0, ui::Icon::SelectAll)
+            .on_click(|_, cx| cx.dispatch_action(Box::new(SelectAllMatches)))
+    }
+
+    pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
+        assert_ne!(
+            mode,
+            SearchMode::Semantic,
+            "Semantic search is not supported in buffer search"
+        );
+        if mode == self.current_mode {
+            return;
+        }
+        self.current_mode = mode;
+        let _ = self.update_matches(cx);
+        cx.notify();
+    }
+
+    pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
+        if let Some(active_editor) = self.active_searchable_item.as_ref() {
+            let handle = active_editor.focus_handle(cx);
+            cx.focus(&handle);
+        }
+    }
+
+    fn toggle_search_option(&mut self, search_option: SearchOptions, cx: &mut ViewContext<Self>) {
+        self.search_options.toggle(search_option);
+        self.default_options = self.search_options;
+        let _ = self.update_matches(cx);
+        cx.notify();
+    }
+
+    pub fn set_search_options(
+        &mut self,
+        search_options: SearchOptions,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.search_options = search_options;
+        cx.notify();
+    }
+
+    fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext<Self>) {
+        self.select_match(Direction::Next, 1, cx);
+    }
+
+    fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext<Self>) {
+        self.select_match(Direction::Prev, 1, cx);
+    }
+
+    fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext<Self>) {
+        if !self.dismissed && self.active_match_index.is_some() {
+            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
+                if let Some(matches) = self
+                    .searchable_items_with_matches
+                    .get(&searchable_item.downgrade())
+                {
+                    searchable_item.select_matches(matches, cx);
+                    self.focus_editor(&FocusEditor, cx);
+                }
+            }
+        }
+    }
+
+    pub fn select_match(&mut self, direction: Direction, count: usize, cx: &mut ViewContext<Self>) {
+        if let Some(index) = self.active_match_index {
+            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
+                if let Some(matches) = self
+                    .searchable_items_with_matches
+                    .get(&searchable_item.downgrade())
+                {
+                    let new_match_index = searchable_item
+                        .match_index_for_direction(matches, index, direction, count, cx);
+
+                    searchable_item.update_matches(matches, cx);
+                    searchable_item.activate_match(new_match_index, matches, cx);
+                }
+            }
+        }
+    }
+
+    pub fn select_last_match(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(searchable_item) = self.active_searchable_item.as_ref() {
+            if let Some(matches) = self
+                .searchable_items_with_matches
+                .get(&searchable_item.downgrade())
+            {
+                if matches.len() == 0 {
+                    return;
+                }
+                let new_match_index = matches.len() - 1;
+                searchable_item.update_matches(matches, cx);
+                searchable_item.activate_match(new_match_index, matches, cx);
+            }
+        }
+    }
+
+    fn on_query_editor_event(
+        &mut self,
+        _: View<Editor>,
+        event: &editor::EditorEvent,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if let editor::EditorEvent::Edited { .. } = event {
+            self.query_contains_error = false;
+            self.clear_matches(cx);
+            let search = self.update_matches(cx);
+            cx.spawn(|this, mut cx| async move {
+                search.await?;
+                this.update(&mut cx, |this, cx| this.activate_current_match(cx))
+            })
+            .detach_and_log_err(cx);
+        }
+    }
+
+    fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) {
+        match event {
+            SearchEvent::MatchesInvalidated => {
+                let _ = self.update_matches(cx);
+            }
+            SearchEvent::ActiveMatchChanged => self.update_match_index(cx),
+        }
+    }
+
+    fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
+        self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
+    }
+    fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
+        self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
+    }
+    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
+        let mut active_item_matches = None;
+        for (searchable_item, matches) in self.searchable_items_with_matches.drain() {
+            if let Some(searchable_item) =
+                WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
+            {
+                if Some(&searchable_item) == self.active_searchable_item.as_ref() {
+                    active_item_matches = Some((searchable_item.downgrade(), matches));
+                } else {
+                    searchable_item.clear_matches(cx);
+                }
+            }
+        }
+
+        self.searchable_items_with_matches
+            .extend(active_item_matches);
+    }
+
+    fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> {
+        let (done_tx, done_rx) = oneshot::channel();
+        let query = self.query(cx);
+        self.pending_search.take();
+
+        if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
+            if query.is_empty() {
+                self.active_match_index.take();
+                active_searchable_item.clear_matches(cx);
+                let _ = done_tx.send(());
+                cx.notify();
+            } else {
+                let query: Arc<_> = if self.current_mode == SearchMode::Regex {
+                    match SearchQuery::regex(
+                        query,
+                        self.search_options.contains(SearchOptions::WHOLE_WORD),
+                        self.search_options.contains(SearchOptions::CASE_SENSITIVE),
+                        false,
+                        Vec::new(),
+                        Vec::new(),
+                    ) {
+                        Ok(query) => query.with_replacement(self.replacement(cx)),
+                        Err(_) => {
+                            self.query_contains_error = true;
+                            cx.notify();
+                            return done_rx;
+                        }
+                    }
+                } else {
+                    match SearchQuery::text(
+                        query,
+                        self.search_options.contains(SearchOptions::WHOLE_WORD),
+                        self.search_options.contains(SearchOptions::CASE_SENSITIVE),
+                        false,
+                        Vec::new(),
+                        Vec::new(),
+                    ) {
+                        Ok(query) => query.with_replacement(self.replacement(cx)),
+                        Err(_) => {
+                            self.query_contains_error = true;
+                            cx.notify();
+                            return done_rx;
+                        }
+                    }
+                }
+                .into();
+                self.active_search = Some(query.clone());
+                let query_text = query.as_str().to_string();
+
+                let matches = active_searchable_item.find_matches(query, cx);
+
+                let active_searchable_item = active_searchable_item.downgrade();
+                self.pending_search = Some(cx.spawn(|this, mut cx| async move {
+                    let matches = matches.await;
+
+                    this.update(&mut cx, |this, cx| {
+                        if let Some(active_searchable_item) =
+                            WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
+                        {
+                            this.searchable_items_with_matches
+                                .insert(active_searchable_item.downgrade(), matches);
+
+                            this.update_match_index(cx);
+                            this.search_history.add(query_text);
+                            if !this.dismissed {
+                                let matches = this
+                                    .searchable_items_with_matches
+                                    .get(&active_searchable_item.downgrade())
+                                    .unwrap();
+                                active_searchable_item.update_matches(matches, cx);
+                                let _ = done_tx.send(());
+                            }
+                            cx.notify();
+                        }
+                    })
+                    .log_err();
+                }));
+            }
+        }
+        done_rx
+    }
+
+    fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
+        let new_index = self
+            .active_searchable_item
+            .as_ref()
+            .and_then(|searchable_item| {
+                let matches = self
+                    .searchable_items_with_matches
+                    .get(&searchable_item.downgrade())?;
+                searchable_item.active_match_index(matches, cx)
+            });
+        if new_index != self.active_match_index {
+            self.active_match_index = new_index;
+            cx.notify();
+        }
+    }
+
+    fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
+        if let Some(new_query) = self.search_history.next().map(str::to_string) {
+            let _ = self.search(&new_query, Some(self.search_options), cx);
+        } else {
+            self.search_history.reset_selection();
+            let _ = self.search("", Some(self.search_options), cx);
+        }
+    }
+
+    fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
+        if self.query(cx).is_empty() {
+            if let Some(new_query) = self.search_history.current().map(str::to_string) {
+                let _ = self.search(&new_query, Some(self.search_options), cx);
+                return;
+            }
+        }
+
+        if let Some(new_query) = self.search_history.previous().map(str::to_string) {
+            let _ = self.search(&new_query, Some(self.search_options), cx);
+        }
+    }
+    fn cycle_mode(&mut self, _: &CycleMode, cx: &mut ViewContext<Self>) {
+        self.activate_search_mode(next_mode(&self.current_mode, false), cx);
+    }
+    fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
+        if let Some(_) = &self.active_searchable_item {
+            self.replace_enabled = !self.replace_enabled;
+            if !self.replace_enabled {
+                let handle = self.query_editor.focus_handle(cx);
+                cx.focus(&handle);
+            }
+            cx.notify();
+        }
+    }
+    fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
+        let mut should_propagate = true;
+        if !self.dismissed && self.active_search.is_some() {
+            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
+                if let Some(query) = self.active_search.as_ref() {
+                    if let Some(matches) = self
+                        .searchable_items_with_matches
+                        .get(&searchable_item.downgrade())
+                    {
+                        if let Some(active_index) = self.active_match_index {
+                            let query = query
+                                .as_ref()
+                                .clone()
+                                .with_replacement(self.replacement(cx));
+                            searchable_item.replace(&matches[active_index], &query, cx);
+                            self.select_next_match(&SelectNextMatch, cx);
+                        }
+                        should_propagate = false;
+                        self.focus_editor(&FocusEditor, cx);
+                    }
+                }
+            }
+        }
+        if !should_propagate {
+            cx.stop_propagation();
+        }
+    }
+    pub fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
+        if !self.dismissed && self.active_search.is_some() {
+            if let Some(searchable_item) = self.active_searchable_item.as_ref() {
+                if let Some(query) = self.active_search.as_ref() {
+                    if let Some(matches) = self
+                        .searchable_items_with_matches
+                        .get(&searchable_item.downgrade())
+                    {
+                        let query = query
+                            .as_ref()
+                            .clone()
+                            .with_replacement(self.replacement(cx));
+                        for m in matches {
+                            searchable_item.replace(m, &query, cx);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::ops::Range;
+
+    use super::*;
+    use editor::{DisplayPoint, Editor};
+    use gpui::{Context, EmptyView, Hsla, TestAppContext, VisualTestContext};
+    use language::Buffer;
+    use smol::stream::StreamExt as _;
+    use unindent::Unindent as _;
+
+    fn init_globals(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let store = settings::SettingsStore::test(cx);
+            cx.set_global(store);
+            editor::init(cx);
+
+            language::init(cx);
+            theme::init(theme::LoadThemes::JustBase, cx);
+        });
+    }
+    fn init_test(
+        cx: &mut TestAppContext,
+    ) -> (
+        View<Editor>,
+        View<BufferSearchBar>,
+        &mut VisualTestContext<'_>,
+    ) {
+        init_globals(cx);
+        let buffer = cx.build_model(|cx| {
+            Buffer::new(
+                0,
+                cx.entity_id().as_u64(),
+                r#"
+                A regular expression (shortened as regex or regexp;[1] also referred to as
+                rational expression[2][3]) is a sequence of characters that specifies a search
+                pattern in text. Usually such patterns are used by string-searching algorithms
+                for "find" or "find and replace" operations on strings, or for input validation.
+                "#
+                .unindent(),
+            )
+        });
+        let (_, cx) = cx.add_window_view(|_| EmptyView {});
+        let editor = cx.build_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
+
+        let search_bar = cx.build_view(|cx| {
+            let mut search_bar = BufferSearchBar::new(cx);
+            search_bar.set_active_pane_item(Some(&editor), cx);
+            search_bar.show(cx);
+            search_bar
+        });
+
+        (editor, search_bar, cx)
+    }
+
+    #[gpui::test]
+    async fn test_search_simple(cx: &mut TestAppContext) {
+        let (editor, search_bar, cx) = init_test(cx);
+        // todo! osiewicz: these tests asserted on background color as well, that should be brought back.
+        let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| {
+            background_highlights
+                .into_iter()
+                .map(|(range, _)| range)
+                .collect::<Vec<_>>()
+        };
+        // Search for a string that appears with different casing.
+        // By default, search is case-insensitive.
+        search_bar
+            .update(cx, |search_bar, cx| search_bar.search("us", None, cx))
+            .await
+            .unwrap();
+        editor.update(cx, |editor, cx| {
+            assert_eq!(
+                display_points_of(editor.all_text_background_highlights(cx)),
+                &[
+                    DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19),
+                    DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
+                ]
+            );
+        });
+
+        // Switch to a case sensitive search.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
+        });
+        let mut editor_notifications = cx.notifications(&editor);
+        editor_notifications.next().await;
+        editor.update(cx, |editor, cx| {
+            assert_eq!(
+                display_points_of(editor.all_text_background_highlights(cx)),
+                &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),]
+            );
+        });
+
+        // Search for a string that appears both as a whole word and
+        // within other words. By default, all results are found.
+        search_bar
+            .update(cx, |search_bar, cx| search_bar.search("or", None, cx))
+            .await
+            .unwrap();
+        editor.update(cx, |editor, cx| {
+            assert_eq!(
+                display_points_of(editor.all_text_background_highlights(cx)),
+                &[
+                    DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26),
+                    DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
+                    DisplayPoint::new(2, 71)..DisplayPoint::new(2, 73),
+                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 3),
+                    DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13),
+                    DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58),
+                    DisplayPoint::new(3, 60)..DisplayPoint::new(3, 62),
+                ]
+            );
+        });
+
+        // Switch to a whole word search.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
+        });
+        let mut editor_notifications = cx.notifications(&editor);
+        editor_notifications.next().await;
+        editor.update(cx, |editor, cx| {
+            assert_eq!(
+                display_points_of(editor.all_text_background_highlights(cx)),
+                &[
+                    DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
+                    DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13),
+                    DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58),
+                ]
+            );
+        });
+
+        editor.update(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
+            });
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+            search_bar.select_next_match(&SelectNextMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+        });
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.select_next_match(&SelectNextMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(1));
+        });
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.select_next_match(&SelectNextMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(2));
+        });
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.select_next_match(&SelectNextMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+        });
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.select_prev_match(&SelectPrevMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(2));
+        });
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.select_prev_match(&SelectPrevMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(1));
+        });
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.select_prev_match(&SelectPrevMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+        });
+
+        // Park the cursor in between matches and ensure that going to the previous match selects
+        // the closest match to the left.
+        editor.update(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+            });
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.active_match_index, Some(1));
+            search_bar.select_prev_match(&SelectPrevMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+        });
+
+        // Park the cursor in between matches and ensure that going to the next match selects the
+        // closest match to the right.
+        editor.update(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+            });
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.active_match_index, Some(1));
+            search_bar.select_next_match(&SelectNextMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(1));
+        });
+
+        // Park the cursor after the last match and ensure that going to the previous match selects
+        // the last match.
+        editor.update(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
+            });
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.active_match_index, Some(2));
+            search_bar.select_prev_match(&SelectPrevMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(2));
+        });
+
+        // Park the cursor after the last match and ensure that going to the next match selects the
+        // first match.
+        editor.update(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
+            });
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.active_match_index, Some(2));
+            search_bar.select_next_match(&SelectNextMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+        });
+
+        // Park the cursor before the first match and ensure that going to the previous match
+        // selects the last match.
+        editor.update(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
+            });
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.active_match_index, Some(0));
+            search_bar.select_prev_match(&SelectPrevMatch, cx);
+            assert_eq!(
+                editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+            );
+        });
+        search_bar.update(cx, |search_bar, _| {
+            assert_eq!(search_bar.active_match_index, Some(2));
+        });
+    }
+
+    #[gpui::test]
+    async fn test_search_option_handling(cx: &mut TestAppContext) {
+        let (editor, search_bar, cx) = init_test(cx);
+
+        // show with options should make current search case sensitive
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.show(cx);
+                search_bar.search("us", Some(SearchOptions::CASE_SENSITIVE), cx)
+            })
+            .await
+            .unwrap();
+        // todo! osiewicz: these tests previously asserted on background color highlights; that should be introduced back.
+        let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| {
+            background_highlights
+                .into_iter()
+                .map(|(range, _)| range)
+                .collect::<Vec<_>>()
+        };
+        editor.update(cx, |editor, cx| {
+            assert_eq!(
+                display_points_of(editor.all_text_background_highlights(cx)),
+                &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),]
+            );
+        });
+
+        // search_suggested should restore default options
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.search_suggested(cx);
+            assert_eq!(search_bar.search_options, SearchOptions::NONE)
+        });
+
+        // toggling a search option should update the defaults
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.search("regex", Some(SearchOptions::CASE_SENSITIVE), cx)
+            })
+            .await
+            .unwrap();
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
+        });
+        let mut editor_notifications = cx.notifications(&editor);
+        editor_notifications.next().await;
+        editor.update(cx, |editor, cx| {
+            assert_eq!(
+                display_points_of(editor.all_text_background_highlights(cx)),
+                &[DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40),]
+            );
+        });
+
+        // defaults should still include whole word
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.search_suggested(cx);
+            assert_eq!(
+                search_bar.search_options,
+                SearchOptions::CASE_SENSITIVE | SearchOptions::WHOLE_WORD
+            )
+        });
+    }
+
+    #[gpui::test]
+    async fn test_search_select_all_matches(cx: &mut TestAppContext) {
+        init_globals(cx);
+        let buffer_text = r#"
+        A regular expression (shortened as regex or regexp;[1] also referred to as
+        rational expression[2][3]) is a sequence of characters that specifies a search
+        pattern in text. Usually such patterns are used by string-searching algorithms
+        for "find" or "find and replace" operations on strings, or for input validation.
+        "#
+        .unindent();
+        let expected_query_matches_count = buffer_text
+            .chars()
+            .filter(|c| c.to_ascii_lowercase() == 'a')
+            .count();
+        assert!(
+            expected_query_matches_count > 1,
+            "Should pick a query with multiple results"
+        );
+        let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
+        let window = cx.add_window(|_| EmptyView {});
+
+        let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
+
+        let search_bar = window.build_view(cx, |cx| {
+            let mut search_bar = BufferSearchBar::new(cx);
+            search_bar.set_active_pane_item(Some(&editor), cx);
+            search_bar.show(cx);
+            search_bar
+        });
+
+        window
+            .update(cx, |_, cx| {
+                search_bar.update(cx, |search_bar, cx| search_bar.search("a", None, cx))
+            })
+            .unwrap()
+            .await
+            .unwrap();
+        let initial_selections = window
+            .update(cx, |_, cx| {
+                search_bar.update(cx, |search_bar, cx| {
+                    let handle = search_bar.query_editor.focus_handle(cx);
+                    cx.focus(&handle);
+                    search_bar.activate_current_match(cx);
+                });
+                assert!(
+                    !editor.read(cx).is_focused(cx),
+                    "Initially, the editor should not be focused"
+                );
+                let initial_selections = editor.update(cx, |editor, cx| {
+                    let initial_selections = editor.selections.display_ranges(cx);
+                    assert_eq!(
+                        initial_selections.len(), 1,
+                        "Expected to have only one selection before adding carets to all matches, but got: {initial_selections:?}",
+                    );
+                    initial_selections
+                });
+                search_bar.update(cx, |search_bar, cx| {
+                    assert_eq!(search_bar.active_match_index, Some(0));
+                    let handle = search_bar.query_editor.focus_handle(cx);
+                    cx.focus(&handle);
+                    search_bar.select_all_matches(&SelectAllMatches, cx);
+                });
+                assert!(
+                    editor.read(cx).is_focused(cx),
+                    "Should focus editor after successful SelectAllMatches"
+                );
+                search_bar.update(cx, |search_bar, cx| {
+                    let all_selections =
+                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+                    assert_eq!(
+                        all_selections.len(),
+                        expected_query_matches_count,
+                        "Should select all `a` characters in the buffer, but got: {all_selections:?}"
+                    );
+                    assert_eq!(
+                        search_bar.active_match_index,
+                        Some(0),
+                        "Match index should not change after selecting all matches"
+                    );
+                });
+
+                search_bar.update(cx, |this, cx| this.select_next_match(&SelectNextMatch, cx));
+                initial_selections
+            }).unwrap();
+
+        window
+            .update(cx, |_, cx| {
+                assert!(
+                    editor.read(cx).is_focused(cx),
+                    "Should still have editor focused after SelectNextMatch"
+                );
+                search_bar.update(cx, |search_bar, cx| {
+                    let all_selections =
+                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+                    assert_eq!(
+                        all_selections.len(),
+                        1,
+                        "On next match, should deselect items and select the next match"
+                    );
+                    assert_ne!(
+                        all_selections, initial_selections,
+                        "Next match should be different from the first selection"
+                    );
+                    assert_eq!(
+                        search_bar.active_match_index,
+                        Some(1),
+                        "Match index should be updated to the next one"
+                    );
+                    let handle = search_bar.query_editor.focus_handle(cx);
+                    cx.focus(&handle);
+                    search_bar.select_all_matches(&SelectAllMatches, cx);
+                });
+            })
+            .unwrap();
+        window
+            .update(cx, |_, cx| {
+                assert!(
+                    editor.read(cx).is_focused(cx),
+                    "Should focus editor after successful SelectAllMatches"
+                );
+                search_bar.update(cx, |search_bar, cx| {
+                    let all_selections =
+                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+                    assert_eq!(
+                    all_selections.len(),
+                    expected_query_matches_count,
+                    "Should select all `a` characters in the buffer, but got: {all_selections:?}"
+                );
+                    assert_eq!(
+                        search_bar.active_match_index,
+                        Some(1),
+                        "Match index should not change after selecting all matches"
+                    );
+                });
+                search_bar.update(cx, |search_bar, cx| {
+                    search_bar.select_prev_match(&SelectPrevMatch, cx);
+                });
+            })
+            .unwrap();
+        let last_match_selections = window
+            .update(cx, |_, cx| {
+                assert!(
+                    editor.read(cx).is_focused(&cx),
+                    "Should still have editor focused after SelectPrevMatch"
+                );
+
+                search_bar.update(cx, |search_bar, cx| {
+                    let all_selections =
+                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+                    assert_eq!(
+                        all_selections.len(),
+                        1,
+                        "On previous match, should deselect items and select the previous item"
+                    );
+                    assert_eq!(
+                        all_selections, initial_selections,
+                        "Previous match should be the same as the first selection"
+                    );
+                    assert_eq!(
+                        search_bar.active_match_index,
+                        Some(0),
+                        "Match index should be updated to the previous one"
+                    );
+                    all_selections
+                })
+            })
+            .unwrap();
+
+        window
+            .update(cx, |_, cx| {
+                search_bar.update(cx, |search_bar, cx| {
+                    let handle = search_bar.query_editor.focus_handle(cx);
+                    cx.focus(&handle);
+                    search_bar.search("abas_nonexistent_match", None, cx)
+                })
+            })
+            .unwrap()
+            .await
+            .unwrap();
+        window
+            .update(cx, |_, cx| {
+                search_bar.update(cx, |search_bar, cx| {
+                    search_bar.select_all_matches(&SelectAllMatches, cx);
+                });
+                assert!(
+                editor.update(cx, |this, cx| !this.is_focused(cx.window_context())),
+                "Should not switch focus to editor if SelectAllMatches does not find any matches"
+            );
+                search_bar.update(cx, |search_bar, cx| {
+                    let all_selections =
+                        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx));
+                    assert_eq!(
+                        all_selections, last_match_selections,
+                        "Should not select anything new if there are no matches"
+                    );
+                    assert!(
+                        search_bar.active_match_index.is_none(),
+                        "For no matches, there should be no active match index"
+                    );
+                });
+            })
+            .unwrap();
+    }
+
+    #[gpui::test]
+    async fn test_search_query_history(cx: &mut TestAppContext) {
+        //crate::project_search::tests::init_test(cx);
+        init_globals(cx);
+        let buffer_text = r#"
+        A regular expression (shortened as regex or regexp;[1] also referred to as
+        rational expression[2][3]) is a sequence of characters that specifies a search
+        pattern in text. Usually such patterns are used by string-searching algorithms
+        for "find" or "find and replace" operations on strings, or for input validation.
+        "#
+        .unindent();
+        let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
+        let (_, cx) = cx.add_window_view(|_| EmptyView {});
+
+        let editor = cx.build_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
+
+        let search_bar = cx.build_view(|cx| {
+            let mut search_bar = BufferSearchBar::new(cx);
+            search_bar.set_active_pane_item(Some(&editor), cx);
+            search_bar.show(cx);
+            search_bar
+        });
+
+        // Add 3 search items into the history.
+        search_bar
+            .update(cx, |search_bar, cx| search_bar.search("a", None, cx))
+            .await
+            .unwrap();
+        search_bar
+            .update(cx, |search_bar, cx| search_bar.search("b", None, cx))
+            .await
+            .unwrap();
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.search("c", Some(SearchOptions::CASE_SENSITIVE), cx)
+            })
+            .await
+            .unwrap();
+        // Ensure that the latest search is active.
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "c");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Next history query after the latest should set the query to the empty string.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // First previous query for empty current query should set the query to the latest.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "c");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Further previous items should go over the history in reverse order.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "b");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Previous items should never go behind the first history item.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "a");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "a");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Next items should go over the history in the original order.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "b");
+            assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        search_bar
+            .update(cx, |search_bar, cx| search_bar.search("ba", None, cx))
+            .await
+            .unwrap();
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "ba");
+            assert_eq!(search_bar.search_options, SearchOptions::NONE);
+        });
+
+        // New search input should add another entry to history and move the selection to the end of the history.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "c");
+            assert_eq!(search_bar.search_options, SearchOptions::NONE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "b");
+            assert_eq!(search_bar.search_options, SearchOptions::NONE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "c");
+            assert_eq!(search_bar.search_options, SearchOptions::NONE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "ba");
+            assert_eq!(search_bar.search_options, SearchOptions::NONE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            assert_eq!(search_bar.query(cx), "");
+            assert_eq!(search_bar.search_options, SearchOptions::NONE);
+        });
+    }
+    #[gpui::test]
+    async fn test_replace_simple(cx: &mut TestAppContext) {
+        let (editor, search_bar, cx) = init_test(cx);
+
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.search("expression", None, cx)
+            })
+            .await
+            .unwrap();
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.replacement_editor.update(cx, |editor, cx| {
+                // We use $1 here as initially we should be in Text mode, where `$1` should be treated literally.
+                editor.set_text("expr$1", cx);
+            });
+            search_bar.replace_all(&ReplaceAll, cx)
+        });
+        assert_eq!(
+            editor.update(cx, |this, cx| { this.text(cx) }),
+            r#"
+        A regular expr$1 (shortened as regex or regexp;[1] also referred to as
+        rational expr$1[2][3]) is a sequence of characters that specifies a search
+        pattern in text. Usually such patterns are used by string-searching algorithms
+        for "find" or "find and replace" operations on strings, or for input validation.
+        "#
+            .unindent()
+        );
+
+        // Search for word boundaries and replace just a single one.
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.search("or", Some(SearchOptions::WHOLE_WORD), cx)
+            })
+            .await
+            .unwrap();
+
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.replacement_editor.update(cx, |editor, cx| {
+                editor.set_text("banana", cx);
+            });
+            search_bar.replace_next(&ReplaceNext, cx)
+        });
+        // Notice how the first or in the text (shORtened) is not replaced. Neither are the remaining hits of `or` in the text.
+        assert_eq!(
+            editor.update(cx, |this, cx| { this.text(cx) }),
+            r#"
+        A regular expr$1 (shortened as regex banana regexp;[1] also referred to as
+        rational expr$1[2][3]) is a sequence of characters that specifies a search
+        pattern in text. Usually such patterns are used by string-searching algorithms
+        for "find" or "find and replace" operations on strings, or for input validation.
+        "#
+            .unindent()
+        );
+        // Let's turn on regex mode.
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.activate_search_mode(SearchMode::Regex, cx);
+                search_bar.search("\\[([^\\]]+)\\]", None, cx)
+            })
+            .await
+            .unwrap();
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.replacement_editor.update(cx, |editor, cx| {
+                editor.set_text("${1}number", cx);
+            });
+            search_bar.replace_all(&ReplaceAll, cx)
+        });
+        assert_eq!(
+            editor.update(cx, |this, cx| { this.text(cx) }),
+            r#"
+        A regular expr$1 (shortened as regex banana regexp;1number also referred to as
+        rational expr$12number3number) is a sequence of characters that specifies a search
+        pattern in text. Usually such patterns are used by string-searching algorithms
+        for "find" or "find and replace" operations on strings, or for input validation.
+        "#
+            .unindent()
+        );
+        // Now with a whole-word twist.
+        search_bar
+            .update(cx, |search_bar, cx| {
+                search_bar.activate_search_mode(SearchMode::Regex, cx);
+                search_bar.search("a\\w+s", Some(SearchOptions::WHOLE_WORD), cx)
+            })
+            .await
+            .unwrap();
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.replacement_editor.update(cx, |editor, cx| {
+                editor.set_text("things", cx);
+            });
+            search_bar.replace_all(&ReplaceAll, cx)
+        });
+        // The only word affected by this edit should be `algorithms`, even though there's a bunch
+        // of words in this text that would match this regex if not for WHOLE_WORD.
+        assert_eq!(
+            editor.update(cx, |this, cx| { this.text(cx) }),
+            r#"
+        A regular expr$1 (shortened as regex banana regexp;1number also referred to as
+        rational expr$12number3number) is a sequence of characters that specifies a search
+        pattern in text. Usually such patterns are used by string-searching things
+        for "find" or "find and replace" operations on strings, or for input validation.
+        "#
+            .unindent()
+        );
+    }
+}

crates/search2/src/history.rs 🔗

@@ -0,0 +1,184 @@
+use smallvec::SmallVec;
+const SEARCH_HISTORY_LIMIT: usize = 20;
+
+#[derive(Default, Debug, Clone)]
+pub struct SearchHistory {
+    history: SmallVec<[String; SEARCH_HISTORY_LIMIT]>,
+    selected: Option<usize>,
+}
+
+impl SearchHistory {
+    pub fn add(&mut self, search_string: String) {
+        if let Some(i) = self.selected {
+            if search_string == self.history[i] {
+                return;
+            }
+        }
+
+        if let Some(previously_searched) = self.history.last_mut() {
+            if search_string.find(previously_searched.as_str()).is_some() {
+                *previously_searched = search_string;
+                self.selected = Some(self.history.len() - 1);
+                return;
+            }
+        }
+
+        self.history.push(search_string);
+        if self.history.len() > SEARCH_HISTORY_LIMIT {
+            self.history.remove(0);
+        }
+        self.selected = Some(self.history.len() - 1);
+    }
+
+    pub fn next(&mut self) -> Option<&str> {
+        let history_size = self.history.len();
+        if history_size == 0 {
+            return None;
+        }
+
+        let selected = self.selected?;
+        if selected == history_size - 1 {
+            return None;
+        }
+        let next_index = selected + 1;
+        self.selected = Some(next_index);
+        Some(&self.history[next_index])
+    }
+
+    pub fn current(&self) -> Option<&str> {
+        Some(&self.history[self.selected?])
+    }
+
+    pub fn previous(&mut self) -> Option<&str> {
+        let history_size = self.history.len();
+        if history_size == 0 {
+            return None;
+        }
+
+        let prev_index = match self.selected {
+            Some(selected_index) => {
+                if selected_index == 0 {
+                    return None;
+                } else {
+                    selected_index - 1
+                }
+            }
+            None => history_size - 1,
+        };
+
+        self.selected = Some(prev_index);
+        Some(&self.history[prev_index])
+    }
+
+    pub fn reset_selection(&mut self) {
+        self.selected = None;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_add() {
+        let mut search_history = SearchHistory::default();
+        assert_eq!(
+            search_history.current(),
+            None,
+            "No current selection should be set fo the default search history"
+        );
+
+        search_history.add("rust".to_string());
+        assert_eq!(
+            search_history.current(),
+            Some("rust"),
+            "Newly added item should be selected"
+        );
+
+        // check if duplicates are not added
+        search_history.add("rust".to_string());
+        assert_eq!(
+            search_history.history.len(),
+            1,
+            "Should not add a duplicate"
+        );
+        assert_eq!(search_history.current(), Some("rust"));
+
+        // check if new string containing the previous string replaces it
+        search_history.add("rustlang".to_string());
+        assert_eq!(
+            search_history.history.len(),
+            1,
+            "Should replace previous item if it's a substring"
+        );
+        assert_eq!(search_history.current(), Some("rustlang"));
+
+        // push enough items to test SEARCH_HISTORY_LIMIT
+        for i in 0..SEARCH_HISTORY_LIMIT * 2 {
+            search_history.add(format!("item{i}"));
+        }
+        assert!(search_history.history.len() <= SEARCH_HISTORY_LIMIT);
+    }
+
+    #[test]
+    fn test_next_and_previous() {
+        let mut search_history = SearchHistory::default();
+        assert_eq!(
+            search_history.next(),
+            None,
+            "Default search history should not have a next item"
+        );
+
+        search_history.add("Rust".to_string());
+        assert_eq!(search_history.next(), None);
+        search_history.add("JavaScript".to_string());
+        assert_eq!(search_history.next(), None);
+        search_history.add("TypeScript".to_string());
+        assert_eq!(search_history.next(), None);
+
+        assert_eq!(search_history.current(), Some("TypeScript"));
+
+        assert_eq!(search_history.previous(), Some("JavaScript"));
+        assert_eq!(search_history.current(), Some("JavaScript"));
+
+        assert_eq!(search_history.previous(), Some("Rust"));
+        assert_eq!(search_history.current(), Some("Rust"));
+
+        assert_eq!(search_history.previous(), None);
+        assert_eq!(search_history.current(), Some("Rust"));
+
+        assert_eq!(search_history.next(), Some("JavaScript"));
+        assert_eq!(search_history.current(), Some("JavaScript"));
+
+        assert_eq!(search_history.next(), Some("TypeScript"));
+        assert_eq!(search_history.current(), Some("TypeScript"));
+
+        assert_eq!(search_history.next(), None);
+        assert_eq!(search_history.current(), Some("TypeScript"));
+    }
+
+    #[test]
+    fn test_reset_selection() {
+        let mut search_history = SearchHistory::default();
+        search_history.add("Rust".to_string());
+        search_history.add("JavaScript".to_string());
+        search_history.add("TypeScript".to_string());
+
+        assert_eq!(search_history.current(), Some("TypeScript"));
+        search_history.reset_selection();
+        assert_eq!(search_history.current(), None);
+        assert_eq!(
+            search_history.previous(),
+            Some("TypeScript"),
+            "Should start from the end after reset on previous item query"
+        );
+
+        search_history.previous();
+        assert_eq!(search_history.current(), Some("JavaScript"));
+        search_history.previous();
+        assert_eq!(search_history.current(), Some("Rust"));
+
+        search_history.reset_selection();
+        assert_eq!(search_history.current(), None);
+    }
+}

crates/search2/src/mode.rs 🔗

@@ -0,0 +1,32 @@
+// TODO: Update the default search mode to get from config
+#[derive(Copy, Clone, Debug, Default, PartialEq)]
+pub enum SearchMode {
+    #[default]
+    Text,
+    Semantic,
+    Regex,
+}
+
+impl SearchMode {
+    pub(crate) fn label(&self) -> &'static str {
+        match self {
+            SearchMode::Text => "Text",
+            SearchMode::Semantic => "Semantic",
+            SearchMode::Regex => "Regex",
+        }
+    }
+}
+
+pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
+    match mode {
+        SearchMode::Text => SearchMode::Regex,
+        SearchMode::Regex => {
+            if semantic_enabled {
+                SearchMode::Semantic
+            } else {
+                SearchMode::Text
+            }
+        }
+        SearchMode::Semantic => SearchMode::Text,
+    }
+}

crates/search2/src/project_search.rs 🔗

@@ -0,0 +1,2661 @@
+use crate::{
+    history::SearchHistory,
+    mode::{SearchMode, Side},
+    search_bar::{render_nav_button, render_option_button_icon, render_search_mode_button},
+    ActivateRegexMode, ActivateSemanticMode, ActivateTextMode, CycleMode, NextHistoryQuery,
+    PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectNextMatch, SelectPrevMatch,
+    ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
+};
+use anyhow::{Context, Result};
+use collections::HashMap;
+use editor::{
+    items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
+    SelectAll, MAX_TAB_TITLE_LEN,
+};
+use futures::StreamExt;
+use gpui::{
+    actions,
+    elements::*,
+    platform::{MouseButton, PromptLevel},
+    Action, AnyElement, AnyViewHandle, AppContext, Entity, ModelContext, ModelHandle, Subscription,
+    Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
+};
+use menu::Confirm;
+use project::{
+    search::{SearchInputs, SearchQuery},
+    Entry, Project,
+};
+use semantic_index::{SemanticIndex, SemanticIndexStatus};
+use smallvec::SmallVec;
+use std::{
+    any::{Any, TypeId},
+    borrow::Cow,
+    collections::HashSet,
+    mem,
+    ops::{Not, Range},
+    path::PathBuf,
+    sync::Arc,
+    time::{Duration, Instant},
+};
+use util::{paths::PathMatcher, ResultExt as _};
+use workspace::{
+    item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
+    searchable::{Direction, SearchableItem, SearchableItemHandle},
+    ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
+};
+
+actions!(
+    project_search,
+    [SearchInNew, ToggleFocus, NextField, ToggleFilters,]
+);
+
+#[derive(Default)]
+struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSearchView>>);
+
+#[derive(Default)]
+struct ActiveSettings(HashMap<WeakModelHandle<Project>, ProjectSearchSettings>);
+
+pub fn init(cx: &mut AppContext) {
+    cx.set_global(ActiveSearches::default());
+    cx.set_global(ActiveSettings::default());
+    cx.add_action(ProjectSearchView::deploy);
+    cx.add_action(ProjectSearchView::move_focus_to_results);
+    cx.add_action(ProjectSearchBar::confirm);
+    cx.add_action(ProjectSearchBar::search_in_new);
+    cx.add_action(ProjectSearchBar::select_next_match);
+    cx.add_action(ProjectSearchBar::select_prev_match);
+    cx.add_action(ProjectSearchBar::replace_next);
+    cx.add_action(ProjectSearchBar::replace_all);
+    cx.add_action(ProjectSearchBar::cycle_mode);
+    cx.add_action(ProjectSearchBar::next_history_query);
+    cx.add_action(ProjectSearchBar::previous_history_query);
+    cx.add_action(ProjectSearchBar::activate_regex_mode);
+    cx.add_action(ProjectSearchBar::toggle_replace);
+    cx.add_action(ProjectSearchBar::toggle_replace_on_a_pane);
+    cx.add_action(ProjectSearchBar::activate_text_mode);
+
+    // This action should only be registered if the semantic index is enabled
+    // We are registering it all the time, as I dont want to introduce a dependency
+    // for Semantic Index Settings globally whenever search is tested.
+    cx.add_action(ProjectSearchBar::activate_semantic_mode);
+
+    cx.capture_action(ProjectSearchBar::tab);
+    cx.capture_action(ProjectSearchBar::tab_previous);
+    cx.capture_action(ProjectSearchView::replace_all);
+    cx.capture_action(ProjectSearchView::replace_next);
+    add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
+    add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
+    add_toggle_filters_action::<ToggleFilters>(cx);
+}
+
+fn add_toggle_filters_action<A: Action>(cx: &mut AppContext) {
+    cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
+        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
+            if search_bar.update(cx, |search_bar, cx| search_bar.toggle_filters(cx)) {
+                return;
+            }
+        }
+        cx.propagate_action();
+    });
+}
+
+fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {
+    cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
+        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
+            if search_bar.update(cx, |search_bar, cx| {
+                search_bar.toggle_search_option(option, cx)
+            }) {
+                return;
+            }
+        }
+        cx.propagate_action();
+    });
+}
+
+struct ProjectSearch {
+    project: ModelHandle<Project>,
+    excerpts: ModelHandle<MultiBuffer>,
+    pending_search: Option<Task<Option<()>>>,
+    match_ranges: Vec<Range<Anchor>>,
+    active_query: Option<SearchQuery>,
+    search_id: usize,
+    search_history: SearchHistory,
+    no_results: Option<bool>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum InputPanel {
+    Query,
+    Exclude,
+    Include,
+}
+
+pub struct ProjectSearchView {
+    model: ModelHandle<ProjectSearch>,
+    query_editor: ViewHandle<Editor>,
+    replacement_editor: ViewHandle<Editor>,
+    results_editor: ViewHandle<Editor>,
+    semantic_state: Option<SemanticState>,
+    semantic_permissioned: Option<bool>,
+    search_options: SearchOptions,
+    panels_with_errors: HashSet<InputPanel>,
+    active_match_index: Option<usize>,
+    search_id: usize,
+    query_editor_was_focused: bool,
+    included_files_editor: ViewHandle<Editor>,
+    excluded_files_editor: ViewHandle<Editor>,
+    filters_enabled: bool,
+    replace_enabled: bool,
+    current_mode: SearchMode,
+}
+
+struct SemanticState {
+    index_status: SemanticIndexStatus,
+    maintain_rate_limit: Option<Task<()>>,
+    _subscription: Subscription,
+}
+
+#[derive(Debug, Clone)]
+struct ProjectSearchSettings {
+    search_options: SearchOptions,
+    filters_enabled: bool,
+    current_mode: SearchMode,
+}
+
+pub struct ProjectSearchBar {
+    active_project_search: Option<ViewHandle<ProjectSearchView>>,
+    subscription: Option<Subscription>,
+}
+
+impl Entity for ProjectSearch {
+    type Event = ();
+}
+
+impl ProjectSearch {
+    fn new(project: ModelHandle<Project>, cx: &mut ModelContext<Self>) -> Self {
+        let replica_id = project.read(cx).replica_id();
+        Self {
+            project,
+            excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
+            pending_search: Default::default(),
+            match_ranges: Default::default(),
+            active_query: None,
+            search_id: 0,
+            search_history: SearchHistory::default(),
+            no_results: None,
+        }
+    }
+
+    fn clone(&self, cx: &mut ModelContext<Self>) -> ModelHandle<Self> {
+        cx.add_model(|cx| Self {
+            project: self.project.clone(),
+            excerpts: self
+                .excerpts
+                .update(cx, |excerpts, cx| cx.add_model(|cx| excerpts.clone(cx))),
+            pending_search: Default::default(),
+            match_ranges: self.match_ranges.clone(),
+            active_query: self.active_query.clone(),
+            search_id: self.search_id,
+            search_history: self.search_history.clone(),
+            no_results: self.no_results.clone(),
+        })
+    }
+
+    fn search(&mut self, query: SearchQuery, cx: &mut ModelContext<Self>) {
+        let search = self
+            .project
+            .update(cx, |project, cx| project.search(query.clone(), cx));
+        self.search_id += 1;
+        self.search_history.add(query.as_str().to_string());
+        self.active_query = Some(query);
+        self.match_ranges.clear();
+        self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
+            let mut matches = search;
+            let this = this.upgrade(&cx)?;
+            this.update(&mut cx, |this, cx| {
+                this.match_ranges.clear();
+                this.excerpts.update(cx, |this, cx| this.clear(cx));
+                this.no_results = Some(true);
+            });
+
+            while let Some((buffer, anchors)) = matches.next().await {
+                let mut ranges = this.update(&mut cx, |this, cx| {
+                    this.no_results = Some(false);
+                    this.excerpts.update(cx, |excerpts, cx| {
+                        excerpts.stream_excerpts_with_context_lines(buffer, anchors, 1, cx)
+                    })
+                });
+
+                while let Some(range) = ranges.next().await {
+                    this.update(&mut cx, |this, _| this.match_ranges.push(range));
+                }
+                this.update(&mut cx, |_, cx| cx.notify());
+            }
+
+            this.update(&mut cx, |this, cx| {
+                this.pending_search.take();
+                cx.notify();
+            });
+
+            None
+        }));
+        cx.notify();
+    }
+
+    fn semantic_search(&mut self, inputs: &SearchInputs, cx: &mut ModelContext<Self>) {
+        let search = SemanticIndex::global(cx).map(|index| {
+            index.update(cx, |semantic_index, cx| {
+                semantic_index.search_project(
+                    self.project.clone(),
+                    inputs.as_str().to_owned(),
+                    10,
+                    inputs.files_to_include().to_vec(),
+                    inputs.files_to_exclude().to_vec(),
+                    cx,
+                )
+            })
+        });
+        self.search_id += 1;
+        self.match_ranges.clear();
+        self.search_history.add(inputs.as_str().to_string());
+        self.no_results = None;
+        self.pending_search = Some(cx.spawn(|this, mut cx| async move {
+            let results = search?.await.log_err()?;
+            let matches = results
+                .into_iter()
+                .map(|result| (result.buffer, vec![result.range.start..result.range.start]));
+
+            this.update(&mut cx, |this, cx| {
+                this.no_results = Some(true);
+                this.excerpts.update(cx, |excerpts, cx| {
+                    excerpts.clear(cx);
+                });
+            });
+            for (buffer, ranges) in matches {
+                let mut match_ranges = this.update(&mut cx, |this, cx| {
+                    this.no_results = Some(false);
+                    this.excerpts.update(cx, |excerpts, cx| {
+                        excerpts.stream_excerpts_with_context_lines(buffer, ranges, 3, cx)
+                    })
+                });
+                while let Some(match_range) = match_ranges.next().await {
+                    this.update(&mut cx, |this, cx| {
+                        this.match_ranges.push(match_range);
+                        while let Ok(Some(match_range)) = match_ranges.try_next() {
+                            this.match_ranges.push(match_range);
+                        }
+                        cx.notify();
+                    });
+                }
+            }
+
+            this.update(&mut cx, |this, cx| {
+                this.pending_search.take();
+                cx.notify();
+            });
+
+            None
+        }));
+        cx.notify();
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ViewEvent {
+    UpdateTab,
+    Activate,
+    EditorEvent(editor::Event),
+    Dismiss,
+}
+
+impl Entity for ProjectSearchView {
+    type Event = ViewEvent;
+}
+
+impl View for ProjectSearchView {
+    fn ui_name() -> &'static str {
+        "ProjectSearchView"
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        let model = &self.model.read(cx);
+        if model.match_ranges.is_empty() {
+            enum Status {}
+
+            let theme = theme::current(cx).clone();
+
+            // If Search is Active -> Major: Searching..., Minor: None
+            // If Semantic -> Major: "Search using Natural Language", Minor: {Status}/n{ex...}/n{ex...}
+            // If Regex -> Major: "Search using Regex", Minor: {ex...}
+            // If Text -> Major: "Text search all files and folders", Minor: {...}
+
+            let current_mode = self.current_mode;
+            let mut major_text = if model.pending_search.is_some() {
+                Cow::Borrowed("Searching...")
+            } else if model.no_results.is_some_and(|v| v) {
+                Cow::Borrowed("No Results")
+            } else {
+                match current_mode {
+                    SearchMode::Text => Cow::Borrowed("Text search all files and folders"),
+                    SearchMode::Semantic => {
+                        Cow::Borrowed("Search all code objects using Natural Language")
+                    }
+                    SearchMode::Regex => Cow::Borrowed("Regex search all files and folders"),
+                }
+            };
+
+            let mut show_minor_text = true;
+            let semantic_status = self.semantic_state.as_ref().and_then(|semantic| {
+                let status = semantic.index_status;
+                match status {
+                    SemanticIndexStatus::NotAuthenticated => {
+                        major_text = Cow::Borrowed("Not Authenticated");
+                        show_minor_text = false;
+                        Some(vec![
+                            "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables."
+                                .to_string(), "If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()])
+                    }
+                    SemanticIndexStatus::Indexed => Some(vec!["Indexing complete".to_string()]),
+                    SemanticIndexStatus::Indexing {
+                        remaining_files,
+                        rate_limit_expiry,
+                    } => {
+                        if remaining_files == 0 {
+                            Some(vec![format!("Indexing...")])
+                        } else {
+                            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(vec![format!(
+                                        "Remaining files to index (rate limit resets in {}s): {}",
+                                        remaining_seconds.as_secs(),
+                                        remaining_files
+                                    )])
+                                } else {
+                                    Some(vec![format!("Remaining files to index: {}", remaining_files)])
+                                }
+                            } else {
+                                Some(vec![format!("Remaining files to index: {}", remaining_files)])
+                            }
+                        }
+                    }
+                    SemanticIndexStatus::NotIndexed => None,
+                }
+            });
+
+            let minor_text = if let Some(no_results) = model.no_results {
+                if model.pending_search.is_none() && no_results {
+                    vec!["No results found in this project for the provided query".to_owned()]
+                } else {
+                    vec![]
+                }
+            } else {
+                match current_mode {
+                    SearchMode::Semantic => {
+                        let mut minor_text: Vec<String> = Vec::new();
+                        minor_text.push("".into());
+                        if let Some(semantic_status) = semantic_status {
+                            minor_text.extend(semantic_status);
+                        }
+                        if show_minor_text {
+                            minor_text
+                                .push("Simply explain the code you are looking to find.".into());
+                            minor_text.push(
+                                "ex. 'prompt user for permissions to index their project'".into(),
+                            );
+                        }
+                        minor_text
+                    }
+                    _ => vec![
+                        "".to_owned(),
+                        "Include/exclude specific paths with the filter option.".to_owned(),
+                        "Matching exact word and/or casing is available too.".to_owned(),
+                    ],
+                }
+            };
+
+            let previous_query_keystrokes =
+                cx.binding_for_action(&PreviousHistoryQuery {})
+                    .map(|binding| {
+                        binding
+                            .keystrokes()
+                            .iter()
+                            .map(|k| k.to_string())
+                            .collect::<Vec<_>>()
+                    });
+            let next_query_keystrokes =
+                cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
+                    binding
+                        .keystrokes()
+                        .iter()
+                        .map(|k| k.to_string())
+                        .collect::<Vec<_>>()
+                });
+            let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
+                (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
+                    format!(
+                        "Search ({}/{} for previous/next query)",
+                        previous_query_keystrokes.join(" "),
+                        next_query_keystrokes.join(" ")
+                    )
+                }
+                (None, Some(next_query_keystrokes)) => {
+                    format!(
+                        "Search ({} for next query)",
+                        next_query_keystrokes.join(" ")
+                    )
+                }
+                (Some(previous_query_keystrokes), None) => {
+                    format!(
+                        "Search ({} for previous query)",
+                        previous_query_keystrokes.join(" ")
+                    )
+                }
+                (None, None) => String::new(),
+            };
+            self.query_editor.update(cx, |editor, cx| {
+                editor.set_placeholder_text(new_placeholder_text, cx);
+            });
+
+            MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
+                Flex::column()
+                    .with_child(Flex::column().contained().flex(1., true))
+                    .with_child(
+                        Flex::column()
+                            .align_children_center()
+                            .with_child(Label::new(
+                                major_text,
+                                theme.search.major_results_status.clone(),
+                            ))
+                            .with_children(
+                                minor_text.into_iter().map(|x| {
+                                    Label::new(x, theme.search.minor_results_status.clone())
+                                }),
+                            )
+                            .aligned()
+                            .top()
+                            .contained()
+                            .flex(7., true),
+                    )
+                    .contained()
+                    .with_background_color(theme.editor.background)
+            })
+            .on_down(MouseButton::Left, |_, _, cx| {
+                cx.focus_parent();
+            })
+            .into_any_named("project search view")
+        } else {
+            ChildView::new(&self.results_editor, cx)
+                .flex(1., true)
+                .into_any_named("project search view")
+        }
+    }
+
+    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        let handle = cx.weak_handle();
+        cx.update_global(|state: &mut ActiveSearches, cx| {
+            state
+                .0
+                .insert(self.model.read(cx).project.downgrade(), handle)
+        });
+
+        cx.update_global(|state: &mut ActiveSettings, cx| {
+            state.0.insert(
+                self.model.read(cx).project.downgrade(),
+                self.current_settings(),
+            );
+        });
+
+        if cx.is_self_focused() {
+            if self.query_editor_was_focused {
+                cx.focus(&self.query_editor);
+            } else {
+                cx.focus(&self.results_editor);
+            }
+        }
+    }
+}
+
+impl Item for ProjectSearchView {
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
+        let query_text = self.query_editor.read(cx).text(cx);
+
+        query_text
+            .is_empty()
+            .not()
+            .then(|| query_text.into())
+            .or_else(|| Some("Project Search".into()))
+    }
+    fn should_close_item_on_event(event: &Self::Event) -> bool {
+        event == &Self::Event::Dismiss
+    }
+
+    fn act_as_type<'a>(
+        &'a self,
+        type_id: TypeId,
+        self_handle: &'a ViewHandle<Self>,
+        _: &'a AppContext,
+    ) -> Option<&'a AnyViewHandle> {
+        if type_id == TypeId::of::<Self>() {
+            Some(self_handle)
+        } else if type_id == TypeId::of::<Editor>() {
+            Some(&self.results_editor)
+        } else {
+            None
+        }
+    }
+
+    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+        self.results_editor
+            .update(cx, |editor, cx| editor.deactivated(cx));
+    }
+
+    fn tab_content<T: 'static>(
+        &self,
+        _detail: Option<usize>,
+        tab_theme: &theme::Tab,
+        cx: &AppContext,
+    ) -> AnyElement<T> {
+        Flex::row()
+            .with_child(
+                Svg::new("icons/magnifying_glass.svg")
+                    .with_color(tab_theme.label.text.color)
+                    .constrained()
+                    .with_width(tab_theme.type_icon_width)
+                    .aligned()
+                    .contained()
+                    .with_margin_right(tab_theme.spacing),
+            )
+            .with_child({
+                let tab_name: Option<Cow<_>> = self
+                    .model
+                    .read(cx)
+                    .search_history
+                    .current()
+                    .as_ref()
+                    .map(|query| {
+                        let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
+                        query_text.into()
+                    });
+                Label::new(
+                    tab_name
+                        .filter(|name| !name.is_empty())
+                        .unwrap_or("Project search".into()),
+                    tab_theme.label.clone(),
+                )
+                .aligned()
+            })
+            .into_any()
+    }
+
+    fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
+        self.results_editor.for_each_project_item(cx, f)
+    }
+
+    fn is_singleton(&self, _: &AppContext) -> bool {
+        false
+    }
+
+    fn can_save(&self, _: &AppContext) -> bool {
+        true
+    }
+
+    fn is_dirty(&self, cx: &AppContext) -> bool {
+        self.results_editor.read(cx).is_dirty(cx)
+    }
+
+    fn has_conflict(&self, cx: &AppContext) -> bool {
+        self.results_editor.read(cx).has_conflict(cx)
+    }
+
+    fn save(
+        &mut self,
+        project: ModelHandle<Project>,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<anyhow::Result<()>> {
+        self.results_editor
+            .update(cx, |editor, cx| editor.save(project, cx))
+    }
+
+    fn save_as(
+        &mut self,
+        _: ModelHandle<Project>,
+        _: PathBuf,
+        _: &mut ViewContext<Self>,
+    ) -> Task<anyhow::Result<()>> {
+        unreachable!("save_as should not have been called")
+    }
+
+    fn reload(
+        &mut self,
+        project: ModelHandle<Project>,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<anyhow::Result<()>> {
+        self.results_editor
+            .update(cx, |editor, cx| editor.reload(project, cx))
+    }
+
+    fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
+    where
+        Self: Sized,
+    {
+        let model = self.model.update(cx, |model, cx| model.clone(cx));
+        Some(Self::new(model, cx, None))
+    }
+
+    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
+        self.results_editor
+            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+    }
+
+    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
+        self.results_editor.update(cx, |editor, _| {
+            editor.set_nav_history(Some(nav_history));
+        });
+    }
+
+    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
+        self.results_editor
+            .update(cx, |editor, cx| editor.navigate(data, cx))
+    }
+
+    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+        match event {
+            ViewEvent::UpdateTab => {
+                smallvec::smallvec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab]
+            }
+            ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event),
+            ViewEvent::Dismiss => smallvec::smallvec![ItemEvent::CloseItem],
+            _ => SmallVec::new(),
+        }
+    }
+
+    fn breadcrumb_location(&self) -> ToolbarItemLocation {
+        if self.has_matches() {
+            ToolbarItemLocation::Secondary
+        } else {
+            ToolbarItemLocation::Hidden
+        }
+    }
+
+    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+        self.results_editor.breadcrumbs(theme, cx)
+    }
+
+    fn serialized_item_kind() -> Option<&'static str> {
+        None
+    }
+
+    fn deserialize(
+        _project: ModelHandle<Project>,
+        _workspace: WeakViewHandle<Workspace>,
+        _workspace_id: workspace::WorkspaceId,
+        _item_id: workspace::ItemId,
+        _cx: &mut ViewContext<Pane>,
+    ) -> Task<anyhow::Result<ViewHandle<Self>>> {
+        unimplemented!()
+    }
+}
+
+impl ProjectSearchView {
+    fn toggle_filters(&mut self, cx: &mut ViewContext<Self>) {
+        self.filters_enabled = !self.filters_enabled;
+        cx.update_global(|state: &mut ActiveSettings, cx| {
+            state.0.insert(
+                self.model.read(cx).project.downgrade(),
+                self.current_settings(),
+            );
+        });
+    }
+
+    fn current_settings(&self) -> ProjectSearchSettings {
+        ProjectSearchSettings {
+            search_options: self.search_options,
+            filters_enabled: self.filters_enabled,
+            current_mode: self.current_mode,
+        }
+    }
+    fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) {
+        self.search_options.toggle(option);
+        cx.update_global(|state: &mut ActiveSettings, cx| {
+            state.0.insert(
+                self.model.read(cx).project.downgrade(),
+                self.current_settings(),
+            );
+        });
+    }
+
+    fn index_project(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(semantic_index) = SemanticIndex::global(cx) {
+            // Semantic search uses no options
+            self.search_options = SearchOptions::none();
+
+            let project = self.model.read(cx).project.clone();
+
+            semantic_index.update(cx, |semantic_index, cx| {
+                semantic_index
+                    .index_project(project.clone(), cx)
+                    .detach_and_log_err(cx);
+            });
+
+            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),
+            });
+            self.semantic_index_changed(semantic_index, cx);
+        }
+    }
+
+    fn semantic_index_changed(
+        &mut self,
+        semantic_index: ModelHandle<SemanticIndex>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let project = self.model.read(cx).project.clone();
+        if let Some(semantic_state) = self.semantic_state.as_mut() {
+            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;
+                }
+            } else {
+                semantic_state.maintain_rate_limit = None;
+            }
+        }
+    }
+
+    fn clear_search(&mut self, cx: &mut ViewContext<Self>) {
+        self.model.update(cx, |model, cx| {
+            model.pending_search = None;
+            model.no_results = None;
+            model.match_ranges.clear();
+
+            model.excerpts.update(cx, |excerpts, cx| {
+                excerpts.clear(cx);
+            });
+        });
+    }
+
+    fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
+        let previous_mode = self.current_mode;
+        if previous_mode == mode {
+            return;
+        }
+
+        self.clear_search(cx);
+        self.current_mode = mode;
+        self.active_match_index = None;
+
+        match mode {
+            SearchMode::Semantic => {
+                let has_permission = self.semantic_permissioned(cx);
+                self.active_match_index = None;
+                cx.spawn(|this, mut cx| async move {
+                    let has_permission = has_permission.await?;
+
+                    if !has_permission {
+                        let mut answer = this.update(&mut cx, |this, cx| {
+                            let project = this.model.read(cx).project.clone();
+                            let project_name = project
+                                .read(cx)
+                                .worktree_root_names(cx)
+                                .collect::<Vec<&str>>()
+                                .join("/");
+                            let is_plural =
+                                project_name.chars().filter(|letter| *letter == '/').count() > 0;
+                            let prompt_text = format!("Would you like to index the '{}' project{} for semantic search? This requires sending code to the OpenAI API", project_name,
+                                if is_plural {
+                                    "s"
+                                } else {""});
+                            cx.prompt(
+                                PromptLevel::Info,
+                                prompt_text.as_str(),
+                                &["Continue", "Cancel"],
+                            )
+                        })?;
+
+                        if answer.next().await == Some(0) {
+                            this.update(&mut cx, |this, _| {
+                                this.semantic_permissioned = Some(true);
+                            })?;
+                        } else {
+                            this.update(&mut cx, |this, cx| {
+                                this.semantic_permissioned = Some(false);
+                                debug_assert_ne!(previous_mode, SearchMode::Semantic, "Tried to re-enable semantic search mode after user modal was rejected");
+                                this.activate_search_mode(previous_mode, cx);
+                            })?;
+                            return anyhow::Ok(());
+                        }
+                    }
+
+                    this.update(&mut cx, |this, cx| {
+                        this.index_project(cx);
+                    })?;
+
+                    anyhow::Ok(())
+                }).detach_and_log_err(cx);
+            }
+            SearchMode::Regex | SearchMode::Text => {
+                self.semantic_state = None;
+                self.active_match_index = None;
+                self.search(cx);
+            }
+        }
+
+        cx.update_global(|state: &mut ActiveSettings, cx| {
+            state.0.insert(
+                self.model.read(cx).project.downgrade(),
+                self.current_settings(),
+            );
+        });
+
+        cx.notify();
+    }
+    fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
+        let model = self.model.read(cx);
+        if let Some(query) = model.active_query.as_ref() {
+            if model.match_ranges.is_empty() {
+                return;
+            }
+            if let Some(active_index) = self.active_match_index {
+                let query = query.clone().with_replacement(self.replacement(cx));
+                self.results_editor.replace(
+                    &(Box::new(model.match_ranges[active_index].clone()) as _),
+                    &query,
+                    cx,
+                );
+                self.select_match(Direction::Next, cx)
+            }
+        }
+    }
+    pub fn replacement(&self, cx: &AppContext) -> String {
+        self.replacement_editor.read(cx).text(cx)
+    }
+    fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
+        let model = self.model.read(cx);
+        if let Some(query) = model.active_query.as_ref() {
+            if model.match_ranges.is_empty() {
+                return;
+            }
+            if self.active_match_index.is_some() {
+                let query = query.clone().with_replacement(self.replacement(cx));
+                let matches = model
+                    .match_ranges
+                    .iter()
+                    .map(|item| Box::new(item.clone()) as _)
+                    .collect::<Vec<_>>();
+                for item in matches {
+                    self.results_editor.replace(&item, &query, cx);
+                }
+            }
+        }
+    }
+
+    fn new(
+        model: ModelHandle<ProjectSearch>,
+        cx: &mut ViewContext<Self>,
+        settings: Option<ProjectSearchSettings>,
+    ) -> Self {
+        let project;
+        let excerpts;
+        let mut replacement_text = None;
+        let mut query_text = String::new();
+
+        // Read in settings if available
+        let (mut options, current_mode, filters_enabled) = if let Some(settings) = settings {
+            (
+                settings.search_options,
+                settings.current_mode,
+                settings.filters_enabled,
+            )
+        } else {
+            (SearchOptions::NONE, Default::default(), false)
+        };
+
+        {
+            let model = model.read(cx);
+            project = model.project.clone();
+            excerpts = model.excerpts.clone();
+            if let Some(active_query) = model.active_query.as_ref() {
+                query_text = active_query.as_str().to_string();
+                replacement_text = active_query.replacement().map(ToOwned::to_owned);
+                options = SearchOptions::from_query(active_query);
+            }
+        }
+        cx.observe(&model, |this, _, cx| this.model_changed(cx))
+            .detach();
+
+        let query_editor = cx.add_view(|cx| {
+            let mut editor = Editor::single_line(
+                Some(Arc::new(|theme| theme.search.editor.input.clone())),
+                cx,
+            );
+            editor.set_placeholder_text("Text search all files", cx);
+            editor.set_text(query_text, cx);
+            editor
+        });
+        // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
+        cx.subscribe(&query_editor, |_, _, event, cx| {
+            cx.emit(ViewEvent::EditorEvent(event.clone()))
+        })
+        .detach();
+        let replacement_editor = cx.add_view(|cx| {
+            let mut editor = Editor::single_line(
+                Some(Arc::new(|theme| theme.search.editor.input.clone())),
+                cx,
+            );
+            editor.set_placeholder_text("Replace in project..", cx);
+            if let Some(text) = replacement_text {
+                editor.set_text(text, cx);
+            }
+            editor
+        });
+        let results_editor = cx.add_view(|cx| {
+            let mut editor = Editor::for_multibuffer(excerpts, Some(project.clone()), cx);
+            editor.set_searchable(false);
+            editor
+        });
+        cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
+            .detach();
+
+        cx.subscribe(&results_editor, |this, _, event, cx| {
+            if matches!(event, editor::Event::SelectionsChanged { .. }) {
+                this.update_match_index(cx);
+            }
+            // Reraise editor events for workspace item activation purposes
+            cx.emit(ViewEvent::EditorEvent(event.clone()));
+        })
+        .detach();
+
+        let included_files_editor = cx.add_view(|cx| {
+            let mut editor = Editor::single_line(
+                Some(Arc::new(|theme| {
+                    theme.search.include_exclude_editor.input.clone()
+                })),
+                cx,
+            );
+            editor.set_placeholder_text("Include: crates/**/*.toml", cx);
+
+            editor
+        });
+        // Subscribe to include_files_editor in order to reraise editor events for workspace item activation purposes
+        cx.subscribe(&included_files_editor, |_, _, event, cx| {
+            cx.emit(ViewEvent::EditorEvent(event.clone()))
+        })
+        .detach();
+
+        let excluded_files_editor = cx.add_view(|cx| {
+            let mut editor = Editor::single_line(
+                Some(Arc::new(|theme| {
+                    theme.search.include_exclude_editor.input.clone()
+                })),
+                cx,
+            );
+            editor.set_placeholder_text("Exclude: vendor/*, *.lock", cx);
+
+            editor
+        });
+        // Subscribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes
+        cx.subscribe(&excluded_files_editor, |_, _, event, cx| {
+            cx.emit(ViewEvent::EditorEvent(event.clone()))
+        })
+        .detach();
+
+        // Check if Worktrees have all been previously indexed
+        let mut this = ProjectSearchView {
+            replacement_editor,
+            search_id: model.read(cx).search_id,
+            model,
+            query_editor,
+            results_editor,
+            semantic_state: None,
+            semantic_permissioned: None,
+            search_options: options,
+            panels_with_errors: HashSet::new(),
+            active_match_index: None,
+            query_editor_was_focused: false,
+            included_files_editor,
+            excluded_files_editor,
+            filters_enabled,
+            current_mode,
+            replace_enabled: false,
+        };
+        this.model_changed(cx);
+        this
+    }
+
+    fn semantic_permissioned(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
+        if let Some(value) = self.semantic_permissioned {
+            return Task::ready(Ok(value));
+        }
+
+        SemanticIndex::global(cx)
+            .map(|semantic| {
+                let project = self.model.read(cx).project.clone();
+                semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx))
+            })
+            .unwrap_or(Task::ready(Ok(false)))
+    }
+    pub fn new_search_in_directory(
+        workspace: &mut Workspace,
+        dir_entry: &Entry,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        if !dir_entry.is_dir() {
+            return;
+        }
+        let Some(filter_str) = dir_entry.path.to_str() else {
+            return;
+        };
+
+        let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
+        let search = cx.add_view(|cx| ProjectSearchView::new(model, cx, None));
+        workspace.add_item(Box::new(search.clone()), cx);
+        search.update(cx, |search, cx| {
+            search
+                .included_files_editor
+                .update(cx, |editor, cx| editor.set_text(filter_str, cx));
+            search.filters_enabled = true;
+            search.focus_query_editor(cx)
+        });
+    }
+
+    // Re-activate the most recently activated search or the most recent if it has been closed.
+    // If no search exists in the workspace, create a new one.
+    fn deploy(
+        workspace: &mut Workspace,
+        _: &workspace::NewSearch,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        // Clean up entries for dropped projects
+        cx.update_global(|state: &mut ActiveSearches, cx| {
+            state.0.retain(|project, _| project.is_upgradable(cx))
+        });
+
+        let active_search = cx
+            .global::<ActiveSearches>()
+            .0
+            .get(&workspace.project().downgrade());
+
+        let existing = active_search
+            .and_then(|active_search| {
+                workspace
+                    .items_of_type::<ProjectSearchView>(cx)
+                    .find(|search| search == active_search)
+            })
+            .or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
+
+        let query = workspace.active_item(cx).and_then(|item| {
+            let editor = item.act_as::<Editor>(cx)?;
+            let query = editor.query_suggestion(cx);
+            if query.is_empty() {
+                None
+            } else {
+                Some(query)
+            }
+        });
+
+        let search = if let Some(existing) = existing {
+            workspace.activate_item(&existing, cx);
+            existing
+        } else {
+            let settings = cx
+                .global::<ActiveSettings>()
+                .0
+                .get(&workspace.project().downgrade());
+
+            let settings = if let Some(settings) = settings {
+                Some(settings.clone())
+            } else {
+                None
+            };
+
+            let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
+            let view = cx.add_view(|cx| ProjectSearchView::new(model, cx, settings));
+
+            workspace.add_item(Box::new(view.clone()), cx);
+            view
+        };
+
+        search.update(cx, |search, cx| {
+            if let Some(query) = query {
+                search.set_query(&query, cx);
+            }
+            search.focus_query_editor(cx)
+        });
+    }
+
+    fn search(&mut self, cx: &mut ViewContext<Self>) {
+        let mode = self.current_mode;
+        match mode {
+            SearchMode::Semantic => {
+                if self.semantic_state.is_some() {
+                    if let Some(query) = self.build_search_query(cx) {
+                        self.model
+                            .update(cx, |model, cx| model.semantic_search(query.as_inner(), cx));
+                    }
+                }
+            }
+
+            _ => {
+                if let Some(query) = self.build_search_query(cx) {
+                    self.model.update(cx, |model, cx| model.search(query, cx));
+                }
+            }
+        }
+    }
+
+    fn build_search_query(&mut self, cx: &mut ViewContext<Self>) -> Option<SearchQuery> {
+        let text = self.query_editor.read(cx).text(cx);
+        let included_files =
+            match Self::parse_path_matches(&self.included_files_editor.read(cx).text(cx)) {
+                Ok(included_files) => {
+                    self.panels_with_errors.remove(&InputPanel::Include);
+                    included_files
+                }
+                Err(_e) => {
+                    self.panels_with_errors.insert(InputPanel::Include);
+                    cx.notify();
+                    return None;
+                }
+            };
+        let excluded_files =
+            match Self::parse_path_matches(&self.excluded_files_editor.read(cx).text(cx)) {
+                Ok(excluded_files) => {
+                    self.panels_with_errors.remove(&InputPanel::Exclude);
+                    excluded_files
+                }
+                Err(_e) => {
+                    self.panels_with_errors.insert(InputPanel::Exclude);
+                    cx.notify();
+                    return None;
+                }
+            };
+        let current_mode = self.current_mode;
+        match current_mode {
+            SearchMode::Regex => {
+                match SearchQuery::regex(
+                    text,
+                    self.search_options.contains(SearchOptions::WHOLE_WORD),
+                    self.search_options.contains(SearchOptions::CASE_SENSITIVE),
+                    included_files,
+                    excluded_files,
+                ) {
+                    Ok(query) => {
+                        self.panels_with_errors.remove(&InputPanel::Query);
+                        Some(query)
+                    }
+                    Err(_e) => {
+                        self.panels_with_errors.insert(InputPanel::Query);
+                        cx.notify();
+                        None
+                    }
+                }
+            }
+            _ => match SearchQuery::text(
+                text,
+                self.search_options.contains(SearchOptions::WHOLE_WORD),
+                self.search_options.contains(SearchOptions::CASE_SENSITIVE),
+                included_files,
+                excluded_files,
+            ) {
+                Ok(query) => {
+                    self.panels_with_errors.remove(&InputPanel::Query);
+                    Some(query)
+                }
+                Err(_e) => {
+                    self.panels_with_errors.insert(InputPanel::Query);
+                    cx.notify();
+                    None
+                }
+            },
+        }
+    }
+
+    fn parse_path_matches(text: &str) -> anyhow::Result<Vec<PathMatcher>> {
+        text.split(',')
+            .map(str::trim)
+            .filter(|maybe_glob_str| !maybe_glob_str.is_empty())
+            .map(|maybe_glob_str| {
+                PathMatcher::new(maybe_glob_str)
+                    .with_context(|| format!("parsing {maybe_glob_str} as path matcher"))
+            })
+            .collect()
+    }
+
+    fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+        if let Some(index) = self.active_match_index {
+            let match_ranges = self.model.read(cx).match_ranges.clone();
+            let new_index = self.results_editor.update(cx, |editor, cx| {
+                editor.match_index_for_direction(&match_ranges, index, direction, 1, cx)
+            });
+
+            let range_to_select = match_ranges[new_index].clone();
+            self.results_editor.update(cx, |editor, cx| {
+                let range_to_select = editor.range_for_match(&range_to_select);
+                editor.unfold_ranges([range_to_select.clone()], false, true, cx);
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.select_ranges([range_to_select])
+                });
+            });
+        }
+    }
+
+    fn focus_query_editor(&mut self, cx: &mut ViewContext<Self>) {
+        self.query_editor.update(cx, |query_editor, cx| {
+            query_editor.select_all(&SelectAll, cx);
+        });
+        self.query_editor_was_focused = true;
+        cx.focus(&self.query_editor);
+    }
+
+    fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) {
+        self.query_editor
+            .update(cx, |query_editor, cx| query_editor.set_text(query, cx));
+    }
+
+    fn focus_results_editor(&mut self, cx: &mut ViewContext<Self>) {
+        self.query_editor.update(cx, |query_editor, cx| {
+            let cursor = query_editor.selections.newest_anchor().head();
+            query_editor.change_selections(None, cx, |s| s.select_ranges([cursor.clone()..cursor]));
+        });
+        self.query_editor_was_focused = false;
+        cx.focus(&self.results_editor);
+    }
+
+    fn model_changed(&mut self, cx: &mut ViewContext<Self>) {
+        let match_ranges = self.model.read(cx).match_ranges.clone();
+        if match_ranges.is_empty() {
+            self.active_match_index = None;
+        } else {
+            self.active_match_index = Some(0);
+            self.update_match_index(cx);
+            let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id);
+            let is_new_search = self.search_id != prev_search_id;
+            self.results_editor.update(cx, |editor, cx| {
+                if is_new_search {
+                    let range_to_select = match_ranges
+                        .first()
+                        .clone()
+                        .map(|range| editor.range_for_match(range));
+                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                        s.select_ranges(range_to_select)
+                    });
+                }
+                editor.highlight_background::<Self>(
+                    match_ranges,
+                    |theme| theme.search.match_background,
+                    cx,
+                );
+            });
+            if is_new_search && self.query_editor.is_focused(cx) {
+                self.focus_results_editor(cx);
+            }
+        }
+
+        cx.emit(ViewEvent::UpdateTab);
+        cx.notify();
+    }
+
+    fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
+        let results_editor = self.results_editor.read(cx);
+        let new_index = active_match_index(
+            &self.model.read(cx).match_ranges,
+            &results_editor.selections.newest_anchor().head(),
+            &results_editor.buffer().read(cx).snapshot(cx),
+        );
+        if self.active_match_index != new_index {
+            self.active_match_index = new_index;
+            cx.notify();
+        }
+    }
+
+    pub fn has_matches(&self) -> bool {
+        self.active_match_index.is_some()
+    }
+
+    fn move_focus_to_results(pane: &mut Pane, _: &ToggleFocus, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |search_view, cx| {
+                if !search_view.results_editor.is_focused(cx)
+                    && !search_view.model.read(cx).match_ranges.is_empty()
+                {
+                    return search_view.focus_results_editor(cx);
+                }
+            });
+        }
+
+        cx.propagate_action();
+    }
+}
+
+impl Default for ProjectSearchBar {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ProjectSearchBar {
+    pub fn new() -> Self {
+        Self {
+            active_project_search: Default::default(),
+            subscription: Default::default(),
+        }
+    }
+    fn cycle_mode(workspace: &mut Workspace, _: &CycleMode, cx: &mut ViewContext<Workspace>) {
+        if let Some(search_view) = workspace
+            .active_item(cx)
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |this, cx| {
+                let new_mode =
+                    crate::mode::next_mode(&this.current_mode, SemanticIndex::enabled(cx));
+                this.activate_search_mode(new_mode, cx);
+                cx.focus(&this.query_editor);
+            })
+        }
+    }
+    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+        let mut should_propagate = true;
+        if let Some(search_view) = self.active_project_search.as_ref() {
+            search_view.update(cx, |search_view, cx| {
+                if !search_view.replacement_editor.is_focused(cx) {
+                    should_propagate = false;
+                    search_view.search(cx);
+                }
+            });
+        }
+        if should_propagate {
+            cx.propagate_action();
+        }
+    }
+
+    fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
+        if let Some(search_view) = workspace
+            .active_item(cx)
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            let new_query = search_view.update(cx, |search_view, cx| {
+                let new_query = search_view.build_search_query(cx);
+                if new_query.is_some() {
+                    if let Some(old_query) = search_view.model.read(cx).active_query.clone() {
+                        search_view.query_editor.update(cx, |editor, cx| {
+                            editor.set_text(old_query.as_str(), cx);
+                        });
+                        search_view.search_options = SearchOptions::from_query(&old_query);
+                    }
+                }
+                new_query
+            });
+            if let Some(new_query) = new_query {
+                let model = cx.add_model(|cx| {
+                    let mut model = ProjectSearch::new(workspace.project().clone(), cx);
+                    model.search(new_query, cx);
+                    model
+                });
+                workspace.add_item(
+                    Box::new(cx.add_view(|cx| ProjectSearchView::new(model, cx, None))),
+                    cx,
+                );
+            }
+        }
+    }
+
+    fn select_next_match(pane: &mut Pane, _: &SelectNextMatch, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |view, cx| view.select_match(Direction::Next, cx));
+        } else {
+            cx.propagate_action();
+        }
+    }
+
+    fn replace_next(pane: &mut Pane, _: &ReplaceNext, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |view, cx| view.replace_next(&ReplaceNext, cx));
+        } else {
+            cx.propagate_action();
+        }
+    }
+    fn replace_all(pane: &mut Pane, _: &ReplaceAll, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |view, cx| view.replace_all(&ReplaceAll, cx));
+        } else {
+            cx.propagate_action();
+        }
+    }
+    fn select_prev_match(pane: &mut Pane, _: &SelectPrevMatch, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |view, cx| view.select_match(Direction::Prev, cx));
+        } else {
+            cx.propagate_action();
+        }
+    }
+
+    fn tab(&mut self, _: &editor::Tab, cx: &mut ViewContext<Self>) {
+        self.cycle_field(Direction::Next, cx);
+    }
+
+    fn tab_previous(&mut self, _: &editor::TabPrev, cx: &mut ViewContext<Self>) {
+        self.cycle_field(Direction::Prev, cx);
+    }
+
+    fn cycle_field(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+        let active_project_search = match &self.active_project_search {
+            Some(active_project_search) => active_project_search,
+
+            None => {
+                cx.propagate_action();
+                return;
+            }
+        };
+
+        active_project_search.update(cx, |project_view, cx| {
+            let mut views = vec![&project_view.query_editor];
+            if project_view.filters_enabled {
+                views.extend([
+                    &project_view.included_files_editor,
+                    &project_view.excluded_files_editor,
+                ]);
+            }
+            if project_view.replace_enabled {
+                views.push(&project_view.replacement_editor);
+            }
+            let current_index = match views
+                .iter()
+                .enumerate()
+                .find(|(_, view)| view.is_focused(cx))
+            {
+                Some((index, _)) => index,
+
+                None => {
+                    cx.propagate_action();
+                    return;
+                }
+            };
+
+            let new_index = match direction {
+                Direction::Next => (current_index + 1) % views.len(),
+                Direction::Prev if current_index == 0 => views.len() - 1,
+                Direction::Prev => (current_index - 1) % views.len(),
+            };
+            cx.focus(views[new_index]);
+        });
+    }
+
+    fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) -> bool {
+        if let Some(search_view) = self.active_project_search.as_ref() {
+            search_view.update(cx, |search_view, cx| {
+                search_view.toggle_search_option(option, cx);
+                search_view.search(cx);
+            });
+
+            cx.notify();
+            true
+        } else {
+            false
+        }
+    }
+    fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
+        if let Some(search) = &self.active_project_search {
+            search.update(cx, |this, cx| {
+                this.replace_enabled = !this.replace_enabled;
+                if !this.replace_enabled {
+                    cx.focus(&this.query_editor);
+                }
+                cx.notify();
+            });
+        }
+    }
+    fn toggle_replace_on_a_pane(pane: &mut Pane, _: &ToggleReplace, cx: &mut ViewContext<Pane>) {
+        let mut should_propagate = true;
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |this, cx| {
+                should_propagate = false;
+                this.replace_enabled = !this.replace_enabled;
+                if !this.replace_enabled {
+                    cx.focus(&this.query_editor);
+                }
+                cx.notify();
+            });
+        }
+        if should_propagate {
+            cx.propagate_action();
+        }
+    }
+    fn activate_text_mode(pane: &mut Pane, _: &ActivateTextMode, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |view, cx| {
+                view.activate_search_mode(SearchMode::Text, cx)
+            });
+        } else {
+            cx.propagate_action();
+        }
+    }
+
+    fn activate_regex_mode(pane: &mut Pane, _: &ActivateRegexMode, cx: &mut ViewContext<Pane>) {
+        if let Some(search_view) = pane
+            .active_item()
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            search_view.update(cx, |view, cx| {
+                view.activate_search_mode(SearchMode::Regex, cx)
+            });
+        } else {
+            cx.propagate_action();
+        }
+    }
+
+    fn activate_semantic_mode(
+        pane: &mut Pane,
+        _: &ActivateSemanticMode,
+        cx: &mut ViewContext<Pane>,
+    ) {
+        if SemanticIndex::enabled(cx) {
+            if let Some(search_view) = pane
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+            {
+                search_view.update(cx, |view, cx| {
+                    view.activate_search_mode(SearchMode::Semantic, cx)
+                });
+            } else {
+                cx.propagate_action();
+            }
+        }
+    }
+
+    fn toggle_filters(&mut self, cx: &mut ViewContext<Self>) -> bool {
+        if let Some(search_view) = self.active_project_search.as_ref() {
+            search_view.update(cx, |search_view, cx| {
+                search_view.toggle_filters(cx);
+                search_view
+                    .included_files_editor
+                    .update(cx, |_, cx| cx.notify());
+                search_view
+                    .excluded_files_editor
+                    .update(cx, |_, cx| cx.notify());
+                cx.refresh_windows();
+                cx.notify();
+            });
+            cx.notify();
+            true
+        } else {
+            false
+        }
+    }
+
+    fn activate_search_mode(&self, mode: SearchMode, cx: &mut ViewContext<Self>) {
+        // Update Current Mode
+        if let Some(search_view) = self.active_project_search.as_ref() {
+            search_view.update(cx, |search_view, cx| {
+                search_view.activate_search_mode(mode, cx);
+            });
+            cx.notify();
+        }
+    }
+
+    fn is_option_enabled(&self, option: SearchOptions, cx: &AppContext) -> bool {
+        if let Some(search) = self.active_project_search.as_ref() {
+            search.read(cx).search_options.contains(option)
+        } else {
+            false
+        }
+    }
+
+    fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
+        if let Some(search_view) = self.active_project_search.as_ref() {
+            search_view.update(cx, |search_view, cx| {
+                let new_query = search_view.model.update(cx, |model, _| {
+                    if let Some(new_query) = model.search_history.next().map(str::to_string) {
+                        new_query
+                    } else {
+                        model.search_history.reset_selection();
+                        String::new()
+                    }
+                });
+                search_view.set_query(&new_query, cx);
+            });
+        }
+    }
+
+    fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
+        if let Some(search_view) = self.active_project_search.as_ref() {
+            search_view.update(cx, |search_view, cx| {
+                if search_view.query_editor.read(cx).text(cx).is_empty() {
+                    if let Some(new_query) = search_view
+                        .model
+                        .read(cx)
+                        .search_history
+                        .current()
+                        .map(str::to_string)
+                    {
+                        search_view.set_query(&new_query, cx);
+                        return;
+                    }
+                }
+
+                if let Some(new_query) = search_view.model.update(cx, |model, _| {
+                    model.search_history.previous().map(str::to_string)
+                }) {
+                    search_view.set_query(&new_query, cx);
+                }
+            });
+        }
+    }
+}
+
+impl Entity for ProjectSearchBar {
+    type Event = ();
+}
+
+impl View for ProjectSearchBar {
+    fn ui_name() -> &'static str {
+        "ProjectSearchBar"
+    }
+
+    fn update_keymap_context(
+        &self,
+        keymap: &mut gpui::keymap_matcher::KeymapContext,
+        cx: &AppContext,
+    ) {
+        Self::reset_to_default_keymap_context(keymap);
+        let in_replace = self
+            .active_project_search
+            .as_ref()
+            .map(|search| {
+                search
+                    .read(cx)
+                    .replacement_editor
+                    .read_with(cx, |_, cx| cx.is_self_focused())
+            })
+            .flatten()
+            .unwrap_or(false);
+        if in_replace {
+            keymap.add_identifier("in_replace");
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        if let Some(_search) = self.active_project_search.as_ref() {
+            let search = _search.read(cx);
+            let theme = theme::current(cx).clone();
+            let query_container_style = if search.panels_with_errors.contains(&InputPanel::Query) {
+                theme.search.invalid_editor
+            } else {
+                theme.search.editor.input.container
+            };
+
+            let search = _search.read(cx);
+            let filter_button = render_option_button_icon(
+                search.filters_enabled,
+                "icons/filter.svg",
+                0,
+                "Toggle filters",
+                Box::new(ToggleFilters),
+                move |_, this, cx| {
+                    this.toggle_filters(cx);
+                },
+                cx,
+            );
+
+            let search = _search.read(cx);
+            let is_semantic_available = SemanticIndex::enabled(cx);
+            let is_semantic_disabled = search.semantic_state.is_none();
+            let icon_style = theme.search.editor_icon.clone();
+            let is_active = search.active_match_index.is_some();
+
+            let render_option_button_icon = |path, option, cx: &mut ViewContext<Self>| {
+                crate::search_bar::render_option_button_icon(
+                    self.is_option_enabled(option, cx),
+                    path,
+                    option.bits as usize,
+                    format!("Toggle {}", option.label()),
+                    option.to_toggle_action(),
+                    move |_, this, cx| {
+                        this.toggle_search_option(option, cx);
+                    },
+                    cx,
+                )
+            };
+            let case_sensitive = is_semantic_disabled.then(|| {
+                render_option_button_icon(
+                    "icons/case_insensitive.svg",
+                    SearchOptions::CASE_SENSITIVE,
+                    cx,
+                )
+            });
+
+            let whole_word = is_semantic_disabled.then(|| {
+                render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx)
+            });
+
+            let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| {
+                let is_active = if let Some(search) = self.active_project_search.as_ref() {
+                    let search = search.read(cx);
+                    search.current_mode == mode
+                } else {
+                    false
+                };
+                render_search_mode_button(
+                    mode,
+                    side,
+                    is_active,
+                    move |_, this, cx| {
+                        this.activate_search_mode(mode, cx);
+                    },
+                    cx,
+                )
+            };
+
+            let search = _search.read(cx);
+
+            let include_container_style =
+                if search.panels_with_errors.contains(&InputPanel::Include) {
+                    theme.search.invalid_include_exclude_editor
+                } else {
+                    theme.search.include_exclude_editor.input.container
+                };
+
+            let exclude_container_style =
+                if search.panels_with_errors.contains(&InputPanel::Exclude) {
+                    theme.search.invalid_include_exclude_editor
+                } else {
+                    theme.search.include_exclude_editor.input.container
+                };
+
+            let matches = search.active_match_index.map(|match_ix| {
+                Label::new(
+                    format!(
+                        "{}/{}",
+                        match_ix + 1,
+                        search.model.read(cx).match_ranges.len()
+                    ),
+                    theme.search.match_index.text.clone(),
+                )
+                .contained()
+                .with_style(theme.search.match_index.container)
+                .aligned()
+            });
+            let should_show_replace_input = search.replace_enabled;
+            let replacement = should_show_replace_input.then(|| {
+                Flex::row()
+                    .with_child(
+                        Svg::for_style(theme.search.replace_icon.clone().icon)
+                            .contained()
+                            .with_style(theme.search.replace_icon.clone().container),
+                    )
+                    .with_child(ChildView::new(&search.replacement_editor, cx).flex(1., true))
+                    .align_children_center()
+                    .flex(1., true)
+                    .contained()
+                    .with_style(query_container_style)
+                    .constrained()
+                    .with_min_width(theme.search.editor.min_width)
+                    .with_max_width(theme.search.editor.max_width)
+                    .with_height(theme.search.search_bar_row_height)
+                    .flex(1., false)
+            });
+            let replace_all = should_show_replace_input.then(|| {
+                super::replace_action(
+                    ReplaceAll,
+                    "Replace all",
+                    "icons/replace_all.svg",
+                    theme.tooltip.clone(),
+                    theme.search.action_button.clone(),
+                )
+            });
+            let replace_next = should_show_replace_input.then(|| {
+                super::replace_action(
+                    ReplaceNext,
+                    "Replace next",
+                    "icons/replace_next.svg",
+                    theme.tooltip.clone(),
+                    theme.search.action_button.clone(),
+                )
+            });
+            let query_column = Flex::column()
+                .with_spacing(theme.search.search_row_spacing)
+                .with_child(
+                    Flex::row()
+                        .with_child(
+                            Svg::for_style(icon_style.icon)
+                                .contained()
+                                .with_style(icon_style.container),
+                        )
+                        .with_child(ChildView::new(&search.query_editor, cx).flex(1., true))
+                        .with_child(
+                            Flex::row()
+                                .with_child(filter_button)
+                                .with_children(case_sensitive)
+                                .with_children(whole_word)
+                                .flex(1., false)
+                                .constrained()
+                                .contained(),
+                        )
+                        .align_children_center()
+                        .contained()
+                        .with_style(query_container_style)
+                        .constrained()
+                        .with_min_width(theme.search.editor.min_width)
+                        .with_max_width(theme.search.editor.max_width)
+                        .with_height(theme.search.search_bar_row_height)
+                        .flex(1., false),
+                )
+                .with_children(search.filters_enabled.then(|| {
+                    Flex::row()
+                        .with_child(
+                            ChildView::new(&search.included_files_editor, cx)
+                                .contained()
+                                .with_style(include_container_style)
+                                .constrained()
+                                .with_height(theme.search.search_bar_row_height)
+                                .flex(1., true),
+                        )
+                        .with_child(
+                            ChildView::new(&search.excluded_files_editor, cx)
+                                .contained()
+                                .with_style(exclude_container_style)
+                                .constrained()
+                                .with_height(theme.search.search_bar_row_height)
+                                .flex(1., true),
+                        )
+                        .constrained()
+                        .with_min_width(theme.search.editor.min_width)
+                        .with_max_width(theme.search.editor.max_width)
+                        .flex(1., false)
+                }))
+                .flex(1., false);
+            let switches_column = Flex::row()
+                .align_children_center()
+                .with_child(super::toggle_replace_button(
+                    search.replace_enabled,
+                    theme.tooltip.clone(),
+                    theme.search.option_button_component.clone(),
+                ))
+                .constrained()
+                .with_height(theme.search.search_bar_row_height)
+                .contained()
+                .with_style(theme.search.option_button_group);
+            let mode_column =
+                Flex::row()
+                    .with_child(search_button_for_mode(
+                        SearchMode::Text,
+                        Some(Side::Left),
+                        cx,
+                    ))
+                    .with_child(search_button_for_mode(
+                        SearchMode::Regex,
+                        if is_semantic_available {
+                            None
+                        } else {
+                            Some(Side::Right)
+                        },
+                        cx,
+                    ))
+                    .with_children(is_semantic_available.then(|| {
+                        search_button_for_mode(SearchMode::Semantic, Some(Side::Right), cx)
+                    }))
+                    .contained()
+                    .with_style(theme.search.modes_container);
+
+            let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| {
+                render_nav_button(
+                    label,
+                    direction,
+                    is_active,
+                    move |_, this, cx| {
+                        if let Some(search) = this.active_project_search.as_ref() {
+                            search.update(cx, |search, cx| search.select_match(direction, cx));
+                        }
+                    },
+                    cx,
+                )
+            };
+
+            let nav_column = Flex::row()
+                .with_children(replace_next)
+                .with_children(replace_all)
+                .with_child(Flex::row().with_children(matches))
+                .with_child(nav_button_for_direction("<", Direction::Prev, cx))
+                .with_child(nav_button_for_direction(">", Direction::Next, cx))
+                .constrained()
+                .with_height(theme.search.search_bar_row_height)
+                .flex_float();
+
+            Flex::row()
+                .with_child(query_column)
+                .with_child(mode_column)
+                .with_child(switches_column)
+                .with_children(replacement)
+                .with_child(nav_column)
+                .contained()
+                .with_style(theme.search.container)
+                .into_any_named("project search")
+        } else {
+            Empty::new().into_any()
+        }
+    }
+}
+
+impl ToolbarItemView for ProjectSearchBar {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) -> ToolbarItemLocation {
+        cx.notify();
+        self.subscription = None;
+        self.active_project_search = None;
+        if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
+            search.update(cx, |search, cx| {
+                if search.current_mode == SearchMode::Semantic {
+                    search.index_project(cx);
+                }
+            });
+
+            self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
+            self.active_project_search = Some(search);
+            ToolbarItemLocation::PrimaryLeft {
+                flex: Some((1., true)),
+            }
+        } else {
+            ToolbarItemLocation::Hidden
+        }
+    }
+
+    fn row_count(&self, cx: &ViewContext<Self>) -> usize {
+        if let Some(search) = self.active_project_search.as_ref() {
+            if search.read(cx).filters_enabled {
+                return 2;
+            }
+        }
+        1
+    }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+    use editor::DisplayPoint;
+    use gpui::{color::Color, executor::Deterministic, TestAppContext};
+    use project::FakeFs;
+    use semantic_index::semantic_index_settings::SemanticIndexSettings;
+    use serde_json::json;
+    use settings::SettingsStore;
+    use std::sync::Arc;
+    use theme::ThemeSettings;
+
+    #[gpui::test]
+    async fn test_project_search(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.background());
+        fs.insert_tree(
+            "/dir",
+            json!({
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
+                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
+            }),
+        )
+        .await;
+        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+        let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
+        let search_view = cx
+            .add_window(|cx| ProjectSearchView::new(search.clone(), cx, None))
+            .root(cx);
+
+        search_view.update(cx, |search_view, cx| {
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
+            search_view.search(cx);
+        });
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.display_text(cx)),
+                "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;"
+            );
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.all_text_background_highlights(cx)),
+                &[
+                    (
+                        DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35),
+                        Color::red()
+                    ),
+                    (
+                        DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40),
+                        Color::red()
+                    ),
+                    (
+                        DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9),
+                        Color::red()
+                    )
+                ]
+            );
+            assert_eq!(search_view.active_match_index, Some(0));
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)]
+            );
+
+            search_view.select_match(Direction::Next, cx);
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.active_match_index, Some(1));
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)]
+            );
+            search_view.select_match(Direction::Next, cx);
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.active_match_index, Some(2));
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)]
+            );
+            search_view.select_match(Direction::Next, cx);
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.active_match_index, Some(0));
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)]
+            );
+            search_view.select_match(Direction::Prev, cx);
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.active_match_index, Some(2));
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)]
+            );
+            search_view.select_match(Direction::Prev, cx);
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.active_match_index, Some(1));
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
+                [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)]
+            );
+        });
+    }
+
+    #[gpui::test]
+    async fn test_project_search_focus(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.background());
+        fs.insert_tree(
+            "/dir",
+            json!({
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
+                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
+            }),
+        )
+        .await;
+        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+
+        let active_item = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        });
+        assert!(
+            active_item.is_none(),
+            "Expected no search panel to be active, but got: {active_item:?}"
+        );
+
+        workspace.update(cx, |workspace, cx| {
+            ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
+        });
+
+        let Some(search_view) = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        }) else {
+            panic!("Search view expected to appear after new search event trigger")
+        };
+        let search_view_id = search_view.id();
+
+        cx.spawn(|mut cx| async move {
+            window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+        })
+        .detach();
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert!(
+                search_view.query_editor.is_focused(cx),
+                "Empty search view should be focused after the toggle focus event: no results panel to focus on",
+            );
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            let query_editor = &search_view.query_editor;
+            assert!(
+                query_editor.is_focused(cx),
+                "Search view should be focused after the new search view is activated",
+            );
+            let query_text = query_editor.read(cx).text(cx);
+            assert!(
+                query_text.is_empty(),
+                "New search query should be empty but got '{query_text}'",
+            );
+            let results_text = search_view
+                .results_editor
+                .update(cx, |editor, cx| editor.display_text(cx));
+            assert!(
+                results_text.is_empty(),
+                "Empty search view should have no results but got '{results_text}'"
+            );
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            search_view.query_editor.update(cx, |query_editor, cx| {
+                query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", cx)
+            });
+            search_view.search(cx);
+        });
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            let results_text = search_view
+                .results_editor
+                .update(cx, |editor, cx| editor.display_text(cx));
+            assert!(
+                results_text.is_empty(),
+                "Search view for mismatching query should have no results but got '{results_text}'"
+            );
+            assert!(
+                search_view.query_editor.is_focused(cx),
+                "Search view should be focused after mismatching query had been used in search",
+            );
+        });
+        cx.spawn(
+            |mut cx| async move { window.dispatch_action(search_view_id, &ToggleFocus, &mut cx) },
+        )
+        .detach();
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert!(
+                search_view.query_editor.is_focused(cx),
+                "Search view with mismatching query should be focused after the toggle focus event: still no results panel to focus on",
+            );
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
+            search_view.search(cx);
+        });
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.display_text(cx)),
+                "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
+                "Search view results should match the query"
+            );
+            assert!(
+                search_view.results_editor.is_focused(cx),
+                "Search view with mismatching query should be focused after search results are available",
+            );
+        });
+        cx.spawn(|mut cx| async move {
+            window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+        })
+        .detach();
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert!(
+                search_view.results_editor.is_focused(cx),
+                "Search view with matching query should still have its results editor focused after the toggle focus event",
+            );
+        });
+
+        workspace.update(cx, |workspace, cx| {
+            ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.display_text(cx)),
+                "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
+                "Results should be unchanged after search view 2nd open in a row"
+            );
+            assert!(
+                search_view.query_editor.is_focused(cx),
+                "Focus should be moved into query editor again after search view 2nd open in a row"
+            );
+        });
+
+        cx.spawn(|mut cx| async move {
+            window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+        })
+        .detach();
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert!(
+                search_view.results_editor.is_focused(cx),
+                "Search view with matching query should switch focus to the results editor after the toggle focus event",
+            );
+        });
+    }
+
+    #[gpui::test]
+    async fn test_new_project_search_in_directory(
+        deterministic: Arc<Deterministic>,
+        cx: &mut TestAppContext,
+    ) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.background());
+        fs.insert_tree(
+            "/dir",
+            json!({
+                "a": {
+                    "one.rs": "const ONE: usize = 1;",
+                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+                },
+                "b": {
+                    "three.rs": "const THREE: usize = one::ONE + two::TWO;",
+                    "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
+                },
+            }),
+        )
+        .await;
+        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+        let worktree_id = project.read_with(cx, |project, cx| {
+            project.worktrees(cx).next().unwrap().read(cx).id()
+        });
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
+
+        let active_item = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        });
+        assert!(
+            active_item.is_none(),
+            "Expected no search panel to be active, but got: {active_item:?}"
+        );
+
+        let one_file_entry = cx.update(|cx| {
+            workspace
+                .read(cx)
+                .project()
+                .read(cx)
+                .entry_for_path(&(worktree_id, "a/one.rs").into(), cx)
+                .expect("no entry for /a/one.rs file")
+        });
+        assert!(one_file_entry.is_file());
+        workspace.update(cx, |workspace, cx| {
+            ProjectSearchView::new_search_in_directory(workspace, &one_file_entry, cx)
+        });
+        let active_search_entry = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        });
+        assert!(
+            active_search_entry.is_none(),
+            "Expected no search panel to be active for file entry"
+        );
+
+        let a_dir_entry = cx.update(|cx| {
+            workspace
+                .read(cx)
+                .project()
+                .read(cx)
+                .entry_for_path(&(worktree_id, "a").into(), cx)
+                .expect("no entry for /a/ directory")
+        });
+        assert!(a_dir_entry.is_dir());
+        workspace.update(cx, |workspace, cx| {
+            ProjectSearchView::new_search_in_directory(workspace, &a_dir_entry, cx)
+        });
+
+        let Some(search_view) = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        }) else {
+            panic!("Search view expected to appear after new search in directory event trigger")
+        };
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert!(
+                search_view.query_editor.is_focused(cx),
+                "On new search in directory, focus should be moved into query editor"
+            );
+            search_view.excluded_files_editor.update(cx, |editor, cx| {
+                assert!(
+                    editor.display_text(cx).is_empty(),
+                    "New search in directory should not have any excluded files"
+                );
+            });
+            search_view.included_files_editor.update(cx, |editor, cx| {
+                assert_eq!(
+                    editor.display_text(cx),
+                    a_dir_entry.path.to_str().unwrap(),
+                    "New search in directory should have included dir entry path"
+                );
+            });
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("const", cx));
+            search_view.search(cx);
+        });
+        deterministic.run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(
+                search_view
+                    .results_editor
+                    .update(cx, |editor, cx| editor.display_text(cx)),
+                "\n\nconst ONE: usize = 1;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
+                "New search in directory should have a filter that matches a certain directory"
+            );
+        });
+    }
+
+    #[gpui::test]
+    async fn test_search_query_history(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.background());
+        fs.insert_tree(
+            "/dir",
+            json!({
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
+                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
+            }),
+        )
+        .await;
+        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        workspace.update(cx, |workspace, cx| {
+            ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
+        });
+
+        let search_view = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+                .expect("Search view expected to appear after new search event trigger")
+        });
+
+        let search_bar = window.add_view(cx, |cx| {
+            let mut search_bar = ProjectSearchBar::new();
+            search_bar.set_active_pane_item(Some(&search_view), cx);
+            // search_bar.show(cx);
+            search_bar
+        });
+
+        // Add 3 search items into the history + another unsubmitted one.
+        search_view.update(cx, |search_view, cx| {
+            search_view.search_options = SearchOptions::CASE_SENSITIVE;
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("ONE", cx));
+            search_view.search(cx);
+        });
+        cx.foreground().run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
+            search_view.search(cx);
+        });
+        cx.foreground().run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("THREE", cx));
+            search_view.search(cx);
+        });
+        cx.foreground().run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            search_view.query_editor.update(cx, |query_editor, cx| {
+                query_editor.set_text("JUST_TEXT_INPUT", cx)
+            });
+        });
+        cx.foreground().run_until_parked();
+
+        // Ensure that the latest input with search settings is active.
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(
+                search_view.query_editor.read(cx).text(cx),
+                "JUST_TEXT_INPUT"
+            );
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Next history query after the latest should set the query to the empty string.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // First previous query for empty current query should set the query to the latest submitted one.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Further previous items should go over the history in reverse order.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Previous items should never go behind the first history item.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "ONE");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "ONE");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // Next items should go over the history in the original order.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        search_view.update(cx, |search_view, cx| {
+            search_view
+                .query_editor
+                .update(cx, |query_editor, cx| query_editor.set_text("TWO_NEW", cx));
+            search_view.search(cx);
+        });
+        cx.foreground().run_until_parked();
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO_NEW");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+
+        // New search input should add another entry to history and move the selection to the end of the history.
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.previous_history_query(&PreviousHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO_NEW");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+        search_bar.update(cx, |search_bar, cx| {
+            search_bar.next_history_query(&NextHistoryQuery, cx);
+        });
+        search_view.update(cx, |search_view, cx| {
+            assert_eq!(search_view.query_editor.read(cx).text(cx), "");
+            assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
+        });
+    }
+
+    pub fn init_test(cx: &mut TestAppContext) {
+        cx.foreground().forbid_parking();
+        let fonts = cx.font_cache();
+        let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default);
+        theme.search.match_background = Color::red();
+
+        cx.update(|cx| {
+            cx.set_global(SettingsStore::test(cx));
+            cx.set_global(ActiveSearches::default());
+            settings::register::<SemanticIndexSettings>(cx);
+
+            theme::init((), cx);
+            cx.update_global::<SettingsStore, _, _>(|store, _| {
+                let mut settings = store.get::<ThemeSettings>(None).clone();
+                settings.theme = Arc::new(theme);
+                store.override_global(settings)
+            });
+
+            language::init(cx);
+            client::init_settings(cx);
+            editor::init(cx);
+            workspace::init_settings(cx);
+            Project::init_settings(cx);
+            super::init(cx);
+        });
+    }
+}

crates/search2/src/search.rs 🔗

@@ -0,0 +1,117 @@
+use bitflags::bitflags;
+pub use buffer_search::BufferSearchBar;
+use gpui::{actions, Action, AppContext, RenderOnce};
+pub use mode::SearchMode;
+use project::search::SearchQuery;
+use ui::ButtonVariant;
+//pub use project_search::{ProjectSearchBar, ProjectSearchView};
+// use theme::components::{
+//     action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
+// };
+
+pub mod buffer_search;
+mod history;
+mod mode;
+//pub mod project_search;
+pub(crate) mod search_bar;
+
+pub fn init(cx: &mut AppContext) {
+    buffer_search::init(cx);
+    //project_search::init(cx);
+}
+
+actions!(
+    CycleMode,
+    ToggleWholeWord,
+    ToggleCaseSensitive,
+    ToggleReplace,
+    SelectNextMatch,
+    SelectPrevMatch,
+    SelectAllMatches,
+    NextHistoryQuery,
+    PreviousHistoryQuery,
+    ActivateTextMode,
+    ActivateSemanticMode,
+    ActivateRegexMode,
+    ReplaceAll,
+    ReplaceNext,
+);
+
+bitflags! {
+    #[derive(Default)]
+    pub struct SearchOptions: u8 {
+        const NONE = 0b000;
+        const WHOLE_WORD = 0b001;
+        const CASE_SENSITIVE = 0b010;
+    }
+}
+
+impl SearchOptions {
+    pub fn label(&self) -> &'static str {
+        match *self {
+            SearchOptions::WHOLE_WORD => "Match Whole Word",
+            SearchOptions::CASE_SENSITIVE => "Match Case",
+            _ => panic!("{:?} is not a named SearchOption", self),
+        }
+    }
+
+    pub fn icon(&self) -> ui::Icon {
+        match *self {
+            SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
+            SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
+            _ => panic!("{:?} is not a named SearchOption", self),
+        }
+    }
+
+    pub fn to_toggle_action(&self) -> Box<dyn Action + Sync + Send + 'static> {
+        match *self {
+            SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
+            SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
+            _ => panic!("{:?} is not a named SearchOption", self),
+        }
+    }
+
+    pub fn none() -> SearchOptions {
+        SearchOptions::NONE
+    }
+
+    pub fn from_query(query: &SearchQuery) -> SearchOptions {
+        let mut options = SearchOptions::NONE;
+        options.set(SearchOptions::WHOLE_WORD, query.whole_word());
+        options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
+        options
+    }
+
+    pub fn as_button(&self, active: bool) -> impl RenderOnce {
+        ui::IconButton::new(0, self.icon())
+            .on_click({
+                let action = self.to_toggle_action();
+                move |_, cx| {
+                    cx.dispatch_action(action.boxed_clone());
+                }
+            })
+            .variant(ui::ButtonVariant::Ghost)
+            .when(active, |button| button.variant(ButtonVariant::Filled))
+    }
+}
+
+fn toggle_replace_button(active: bool) -> impl RenderOnce {
+    // todo: add toggle_replace button
+    ui::IconButton::new(0, ui::Icon::Replace)
+        .on_click(|_, cx| {
+            cx.dispatch_action(Box::new(ToggleReplace));
+            cx.notify();
+        })
+        .variant(ui::ButtonVariant::Ghost)
+        .when(active, |button| button.variant(ButtonVariant::Filled))
+}
+
+fn render_replace_button(
+    action: impl Action + 'static + Send + Sync,
+    icon: ui::Icon,
+) -> impl RenderOnce {
+    // todo: add tooltip
+    ui::IconButton::new(0, icon).on_click(move |_, cx| {
+        cx.dispatch_action(action.boxed_clone());
+    })
+}

crates/search2/src/search_bar.rs 🔗

@@ -0,0 +1,35 @@
+use gpui::{MouseDownEvent, RenderOnce, WindowContext};
+use ui::{Button, ButtonVariant, IconButton};
+
+use crate::mode::SearchMode;
+
+pub(super) fn render_nav_button(
+    icon: ui::Icon,
+    _active: bool,
+    on_click: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+) -> impl RenderOnce {
+    // let tooltip_style = cx.theme().tooltip.clone();
+    // let cursor_style = if active {
+    //     CursorStyle::PointingHand
+    // } else {
+    //     CursorStyle::default()
+    // };
+    // enum NavButton {}
+    IconButton::new("search-nav-button", icon).on_click(on_click)
+}
+
+pub(crate) fn render_search_mode_button(
+    mode: SearchMode,
+    is_active: bool,
+    on_click: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+) -> Button {
+    let button_variant = if is_active {
+        ButtonVariant::Filled
+    } else {
+        ButtonVariant::Ghost
+    };
+
+    Button::new(mode.label())
+        .on_click(on_click)
+        .variant(button_variant)
+}

crates/story/Cargo.toml 🔗

@@ -0,0 +1,10 @@
+[package]
+name = "story"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+gpui = { package = "gpui2", path = "../gpui2" }

crates/story/src/story.rs 🔗

@@ -0,0 +1,35 @@
+use gpui::prelude::*;
+use gpui::{div, hsla, Div, SharedString};
+
+pub struct Story {}
+
+impl Story {
+    pub fn container() -> Div {
+        div().size_full().flex().flex_col().pt_2().px_4().bg(hsla(
+            0. / 360.,
+            0. / 100.,
+            100. / 100.,
+            1.,
+        ))
+    }
+
+    pub fn title(title: impl Into<SharedString>) -> impl Element {
+        div()
+            .text_xl()
+            .text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
+            .child(title.into())
+    }
+
+    pub fn title_for<T>() -> impl Element {
+        Self::title(std::any::type_name::<T>())
+    }
+
+    pub fn label(label: impl Into<SharedString>) -> impl Element {
+        div()
+            .mt_4()
+            .mb_2()
+            .text_xs()
+            .text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
+            .child(label.into())
+    }
+}

crates/storybook2/Cargo.toml 🔗

@@ -25,6 +25,7 @@ serde.workspace = true
 settings2 = { path = "../settings2" }
 simplelog = "0.9"
 smallvec.workspace = true
+story = { path = "../story" }
 strum = { version = "0.25.0", features = ["derive"] }
 theme = { path = "../theme" }
 theme2 = { path = "../theme2" }

crates/storybook2/src/stories.rs 🔗

@@ -1,4 +1,3 @@
-mod colors;
 mod focus;
 mod kitchen_sink;
 mod picker;
@@ -6,7 +5,6 @@ mod scroll;
 mod text;
 mod z_index;
 
-pub use colors::*;
 pub use focus::*;
 pub use kitchen_sink::*;
 pub use picker::*;

crates/storybook2/src/stories/colors.rs 🔗

@@ -1,44 +0,0 @@
-use crate::story::Story;
-use gpui::{prelude::*, px, Div, Render};
-use theme2::{default_color_scales, ColorScaleStep};
-use ui::prelude::*;
-
-pub struct ColorsStory;
-
-impl Render<Self> for ColorsStory {
-    type Element = Div<Self>;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        let color_scales = default_color_scales();
-
-        Story::container(cx)
-            .child(Story::title(cx, "Colors"))
-            .child(
-                div()
-                    .id("colors")
-                    .flex()
-                    .flex_col()
-                    .gap_1()
-                    .overflow_y_scroll()
-                    .text_color(gpui::white())
-                    .children(color_scales.into_iter().map(|scale| {
-                        div()
-                            .flex()
-                            .child(
-                                div()
-                                    .w(px(75.))
-                                    .line_height(px(24.))
-                                    .child(scale.name().clone()),
-                            )
-                            .child(
-                                div()
-                                    .flex()
-                                    .gap_1()
-                                    .children(ColorScaleStep::ALL.map(|step| {
-                                        div().flex().size_6().bg(scale.step(cx, step))
-                                    })),
-                            )
-                    })),
-            )
-    }
-}

crates/storybook2/src/stories/focus.rs 🔗

@@ -26,8 +26,8 @@ impl FocusStory {
     }
 }
 
-impl Render<Self> for FocusStory {
-    type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
+impl Render for FocusStory {
+    type Element = Focusable<Stateful<Div>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
@@ -42,18 +42,18 @@ impl Render<Self> for FocusStory {
             .id("parent")
             .focusable()
             .key_context("parent")
-            .on_action(|_, action: &ActionA, cx| {
+            .on_action(cx.listener(|_, action: &ActionA, cx| {
                 println!("Action A dispatched on parent");
-            })
-            .on_action(|_, action: &ActionB, cx| {
+            }))
+            .on_action(cx.listener(|_, action: &ActionB, cx| {
                 println!("Action B dispatched on parent");
-            })
-            .on_focus(|_, _, _| println!("Parent focused"))
-            .on_blur(|_, _, _| println!("Parent blurred"))
-            .on_focus_in(|_, _, _| println!("Parent focus_in"))
-            .on_focus_out(|_, _, _| println!("Parent focus_out"))
-            .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event))
-            .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event))
+            }))
+            .on_focus(cx.listener(|_, _, _| println!("Parent focused")))
+            .on_blur(cx.listener(|_, _, _| println!("Parent blurred")))
+            .on_focus_in(cx.listener(|_, _, _| println!("Parent focus_in")))
+            .on_focus_out(cx.listener(|_, _, _| println!("Parent focus_out")))
+            .on_key_down(cx.listener(|_, event, _| println!("Key down on parent {:?}", event)))
+            .on_key_up(cx.listener(|_, event, _| println!("Key up on parent {:?}", event)))
             .size_full()
             .bg(color_1)
             .focus(|style| style.bg(color_2))
@@ -61,38 +61,42 @@ impl Render<Self> for FocusStory {
                 div()
                     .track_focus(&self.child_1_focus)
                     .key_context("child-1")
-                    .on_action(|_, action: &ActionB, cx| {
+                    .on_action(cx.listener(|_, action: &ActionB, cx| {
                         println!("Action B dispatched on child 1 during");
-                    })
+                    }))
                     .w_full()
                     .h_6()
                     .bg(color_4)
                     .focus(|style| style.bg(color_5))
                     .in_focus(|style| style.bg(color_6))
-                    .on_focus(|_, _, _| println!("Child 1 focused"))
-                    .on_blur(|_, _, _| println!("Child 1 blurred"))
-                    .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
-                    .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
-                    .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event))
-                    .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event))
+                    .on_focus(cx.listener(|_, _, _| println!("Child 1 focused")))
+                    .on_blur(cx.listener(|_, _, _| println!("Child 1 blurred")))
+                    .on_focus_in(cx.listener(|_, _, _| println!("Child 1 focus_in")))
+                    .on_focus_out(cx.listener(|_, _, _| println!("Child 1 focus_out")))
+                    .on_key_down(
+                        cx.listener(|_, event, _| println!("Key down on child 1 {:?}", event)),
+                    )
+                    .on_key_up(cx.listener(|_, event, _| println!("Key up on child 1 {:?}", event)))
                     .child("Child 1"),
             )
             .child(
                 div()
                     .track_focus(&self.child_2_focus)
                     .key_context("child-2")
-                    .on_action(|_, action: &ActionC, cx| {
+                    .on_action(cx.listener(|_, action: &ActionC, cx| {
                         println!("Action C dispatched on child 2");
-                    })
+                    }))
                     .w_full()
                     .h_6()
                     .bg(color_4)
-                    .on_focus(|_, _, _| println!("Child 2 focused"))
-                    .on_blur(|_, _, _| println!("Child 2 blurred"))
-                    .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
-                    .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
-                    .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event))
-                    .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event))
+                    .on_focus(cx.listener(|_, _, _| println!("Child 2 focused")))
+                    .on_blur(cx.listener(|_, _, _| println!("Child 2 blurred")))
+                    .on_focus_in(cx.listener(|_, _, _| println!("Child 2 focus_in")))
+                    .on_focus_out(cx.listener(|_, _, _| println!("Child 2 focus_out")))
+                    .on_key_down(
+                        cx.listener(|_, event, _| println!("Key down on child 2 {:?}", event)),
+                    )
+                    .on_key_up(cx.listener(|_, event, _| println!("Key up on child 2 {:?}", event)))
                     .child("Child 2"),
             )
     }

crates/storybook2/src/stories/kitchen_sink.rs 🔗

@@ -1,8 +1,10 @@
-use crate::{story::Story, story_selector::ComponentStory};
 use gpui::{prelude::*, Div, Render, Stateful, View};
+use story::Story;
 use strum::IntoEnumIterator;
 use ui::prelude::*;
 
+use crate::story_selector::ComponentStory;
+
 pub struct KitchenSinkStory;
 
 impl KitchenSinkStory {
@@ -11,19 +13,19 @@ impl KitchenSinkStory {
     }
 }
 
-impl Render<Self> for KitchenSinkStory {
-    type Element = Stateful<Self, Div<Self>>;
+impl Render for KitchenSinkStory {
+    type Element = Stateful<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let component_stories = ComponentStory::iter()
             .map(|selector| selector.story(cx))
             .collect::<Vec<_>>();
 
-        Story::container(cx)
+        Story::container()
             .id("kitchen-sink")
             .overflow_y_scroll()
-            .child(Story::title(cx, "Kitchen Sink"))
-            .child(Story::label(cx, "Components"))
+            .child(Story::title("Kitchen Sink"))
+            .child(Story::label("Components"))
             .child(div().flex().flex_col().children(component_stories))
             // Add a bit of space at the bottom of the kitchen sink so elements
             // don't end up squished right up against the bottom of the screen.

crates/storybook2/src/stories/picker.rs 🔗

@@ -36,7 +36,7 @@ impl Delegate {
 }
 
 impl PickerDelegate for Delegate {
-    type ListItem = Div<Picker<Self>>;
+    type ListItem = Div;
 
     fn match_count(&self) -> usize {
         self.candidates.len()
@@ -205,8 +205,8 @@ impl PickerStory {
     }
 }
 
-impl Render<Self> for PickerStory {
-    type Element = Div<Self>;
+impl Render for PickerStory {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         div()

crates/storybook2/src/stories/scroll.rs 🔗

@@ -10,8 +10,8 @@ impl ScrollStory {
     }
 }
 
-impl Render<Self> for ScrollStory {
-    type Element = Stateful<Self, Div<Self>>;
+impl Render for ScrollStory {
+    type Element = Stateful<Div>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
@@ -38,7 +38,7 @@ impl Render<Self> for ScrollStory {
                         };
                         div()
                             .id(id)
-                            .tooltip(move |_, cx| Tooltip::text(format!("{}, {}", row, column), cx))
+                            .tooltip(move |cx| Tooltip::text(format!("{}, {}", row, column), cx))
                             .bg(bg)
                             .size(px(100. as f32))
                             .when(row >= 5 && column >= 5, |d| {

crates/storybook2/src/stories/text.rs 🔗

@@ -11,8 +11,8 @@ impl TextStory {
     }
 }
 
-impl Render<Self> for TextStory {
-    type Element = Div<Self>;
+impl Render for TextStory {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         v_stack()

crates/storybook2/src/stories/z_index.rs 🔗

@@ -1,52 +1,49 @@
 use gpui::{px, rgb, Div, Hsla, Render, RenderOnce};
+use story::Story;
 use ui::prelude::*;
 
-use crate::story::Story;
-
 /// A reimplementation of the MDN `z-index` example, found here:
 /// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
 pub struct ZIndexStory;
 
-impl Render<Self> for ZIndexStory {
-    type Element = Div<Self>;
+impl Render for ZIndexStory {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        Story::container(cx)
-            .child(Story::title(cx, "z-index"))
-            .child(
-                div()
-                    .flex()
-                    .child(
-                        div()
-                            .w(px(250.))
-                            .child(Story::label(cx, "z-index: auto"))
-                            .child(ZIndexExample::new(0)),
-                    )
-                    .child(
-                        div()
-                            .w(px(250.))
-                            .child(Story::label(cx, "z-index: 1"))
-                            .child(ZIndexExample::new(1)),
-                    )
-                    .child(
-                        div()
-                            .w(px(250.))
-                            .child(Story::label(cx, "z-index: 3"))
-                            .child(ZIndexExample::new(3)),
-                    )
-                    .child(
-                        div()
-                            .w(px(250.))
-                            .child(Story::label(cx, "z-index: 5"))
-                            .child(ZIndexExample::new(5)),
-                    )
-                    .child(
-                        div()
-                            .w(px(250.))
-                            .child(Story::label(cx, "z-index: 7"))
-                            .child(ZIndexExample::new(7)),
-                    ),
-            )
+        Story::container().child(Story::title("z-index")).child(
+            div()
+                .flex()
+                .child(
+                    div()
+                        .w(px(250.))
+                        .child(Story::label("z-index: auto"))
+                        .child(ZIndexExample::new(0)),
+                )
+                .child(
+                    div()
+                        .w(px(250.))
+                        .child(Story::label("z-index: 1"))
+                        .child(ZIndexExample::new(1)),
+                )
+                .child(
+                    div()
+                        .w(px(250.))
+                        .child(Story::label("z-index: 3"))
+                        .child(ZIndexExample::new(3)),
+                )
+                .child(
+                    div()
+                        .w(px(250.))
+                        .child(Story::label("z-index: 5"))
+                        .child(ZIndexExample::new(5)),
+                )
+                .child(
+                    div()
+                        .w(px(250.))
+                        .child(Story::label("z-index: 7"))
+                        .child(ZIndexExample::new(7)),
+                ),
+        )
     }
 }
 
@@ -77,17 +74,17 @@ trait Styles: Styled + Sized {
     }
 }
 
-impl<V: 'static> Styles for Div<V> {}
+impl Styles for Div {}
 
 #[derive(RenderOnce)]
 struct ZIndexExample {
     z_index: u32,
 }
 
-impl<V: 'static> Component<V> for ZIndexExample {
-    type Rendered = Div<V>;
+impl Component for ZIndexExample {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         div()
             .relative()
             .size_full()

crates/storybook2/src/story_selector.rs 🔗

@@ -8,49 +8,22 @@ use clap::ValueEnum;
 use gpui::{AnyView, VisualContext};
 use strum::{EnumIter, EnumString, IntoEnumIterator};
 use ui::prelude::*;
-use ui::{AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory};
+use ui::{AvatarStory, ButtonStory, IconStory, InputStory, LabelStory};
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 pub enum ComponentStory {
-    AssistantPanel,
     Avatar,
-    Breadcrumb,
-    Buffer,
     Button,
-    ChatPanel,
     Checkbox,
-    CollabPanel,
-    Colors,
-    CommandPalette,
     ContextMenu,
-    Copilot,
-    Details,
-    Facepile,
     Focus,
     Icon,
     Input,
     Keybinding,
     Label,
-    LanguageSelector,
-    MultiBuffer,
-    NotificationsPanel,
-    Palette,
-    Panel,
-    ProjectPanel,
-    Players,
-    RecentProjects,
     Scroll,
-    Tab,
-    TabBar,
-    Terminal,
     Text,
-    ThemeSelector,
-    TitleBar,
-    Toast,
-    Toolbar,
-    TrafficLights,
-    Workspace,
     ZIndex,
     Picker,
 }
@@ -58,44 +31,17 @@ pub enum ComponentStory {
 impl ComponentStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
-            Self::AssistantPanel => cx.build_view(|_| ui::AssistantPanelStory).into(),
             Self::Avatar => cx.build_view(|_| AvatarStory).into(),
-            Self::Breadcrumb => cx.build_view(|_| ui::BreadcrumbStory).into(),
-            Self::Buffer => cx.build_view(|_| ui::BufferStory).into(),
             Self::Button => cx.build_view(|_| ButtonStory).into(),
-            Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into(),
             Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
-            Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into(),
-            Self::Colors => cx.build_view(|_| ColorsStory).into(),
-            Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into(),
             Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
-            Self::Copilot => cx.build_view(|_| ui::CopilotModalStory).into(),
-            Self::Details => cx.build_view(|_| DetailsStory).into(),
-            Self::Facepile => cx.build_view(|_| ui::FacepileStory).into(),
             Self::Focus => FocusStory::view(cx).into(),
             Self::Icon => cx.build_view(|_| IconStory).into(),
             Self::Input => cx.build_view(|_| InputStory).into(),
             Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
             Self::Label => cx.build_view(|_| LabelStory).into(),
-            Self::LanguageSelector => cx.build_view(|_| ui::LanguageSelectorStory).into(),
-            Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(),
-            Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(),
-            Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(),
-            Self::Players => cx.build_view(|_| theme2::PlayerStory).into(),
-            Self::Panel => cx.build_view(|cx| ui::PanelStory).into(),
-            Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(),
-            Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(),
             Self::Scroll => ScrollStory::view(cx).into(),
-            Self::Tab => cx.build_view(|_| ui::TabStory).into(),
-            Self::TabBar => cx.build_view(|_| ui::TabBarStory).into(),
-            Self::Terminal => cx.build_view(|_| ui::TerminalStory).into(),
             Self::Text => TextStory::view(cx).into(),
-            Self::ThemeSelector => cx.build_view(|_| ui::ThemeSelectorStory).into(),
-            Self::TitleBar => ui::TitleBarStory::view(cx).into(),
-            Self::Toast => cx.build_view(|_| ui::ToastStory).into(),
-            Self::Toolbar => cx.build_view(|_| ui::ToolbarStory).into(),
-            Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
-            Self::Workspace => ui::WorkspaceStory::view(cx).into(),
             Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
             Self::Picker => PickerStory::new(cx).into(),
         }

crates/storybook2/src/storybook2.rs 🔗

@@ -2,7 +2,6 @@
 
 mod assets;
 mod stories;
-mod story;
 mod story_selector;
 
 use std::sync::Arc;
@@ -15,7 +14,6 @@ use gpui::{
 use log::LevelFilter;
 use settings2::{default_settings, Settings, SettingsStore};
 use simplelog::SimpleLogger;
-use story_selector::ComponentStory;
 use theme2::{ThemeRegistry, ThemeSettings};
 use ui::prelude::*;
 
@@ -62,15 +60,13 @@ fn main() {
 
         theme2::init(theme2::LoadThemes::All, cx);
 
-        let selector =
-            story_selector.unwrap_or(StorySelector::Component(ComponentStory::Workspace));
+        let selector = story_selector.unwrap_or(StorySelector::KitchenSink);
 
         let theme_registry = cx.global::<ThemeRegistry>();
         let mut theme_settings = ThemeSettings::get_global(cx).clone();
         theme_settings.active_theme = theme_registry.get(&theme_name).unwrap();
         ThemeSettings::override_global(theme_settings, cx);
 
-        ui::settings::init(cx);
         language::init(cx);
         editor::init(cx);
 
@@ -105,8 +101,8 @@ impl StoryWrapper {
     }
 }
 
-impl Render<Self> for StoryWrapper {
-    type Element = Div<Self>;
+impl Render for StoryWrapper {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()

crates/storybook3/Cargo.toml 🔗

@@ -1,17 +0,0 @@
-[package]
-name = "storybook3"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[[bin]]
-name = "storybook"
-path = "src/storybook3.rs"
-
-[dependencies]
-anyhow.workspace = true
-
-gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2", features = ["stories"] }
-theme = { package = "theme2", path = "../theme2", features = ["stories"] }
-settings = { package = "settings2", path = "../settings2"}

crates/storybook3/src/storybook3.rs 🔗

@@ -1,87 +0,0 @@
-use anyhow::Result;
-use gpui::{
-    div, px, size, AnyView, Bounds, Div, Render, ViewContext, VisualContext, WindowBounds,
-    WindowOptions,
-};
-use gpui::{white, AssetSource};
-use settings::{default_settings, Settings, SettingsStore};
-use std::borrow::Cow;
-use std::sync::Arc;
-use theme::ThemeSettings;
-use ui::{prelude::*, ContextMenuStory};
-
-struct Assets;
-
-impl AssetSource for Assets {
-    fn load(&self, _path: &str) -> Result<Cow<[u8]>> {
-        todo!();
-    }
-
-    fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
-        Ok(vec![])
-    }
-}
-
-fn main() {
-    let asset_source = Arc::new(Assets);
-    gpui::App::production(asset_source).run(move |cx| {
-        let mut store = SettingsStore::default();
-        store
-            .set_default_settings(default_settings().as_ref(), cx)
-            .unwrap();
-        cx.set_global(store);
-        ui::settings::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
-
-        cx.open_window(
-            WindowOptions {
-                bounds: WindowBounds::Fixed(Bounds {
-                    origin: Default::default(),
-                    size: size(px(1500.), px(780.)).into(),
-                }),
-                ..Default::default()
-            },
-            move |cx| {
-                let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
-                cx.set_rem_size(ui_font_size);
-
-                cx.build_view(|cx| TestView {
-                    story: cx.build_view(|_| ContextMenuStory).into(),
-                })
-            },
-        );
-
-        cx.activate(true);
-    })
-}
-
-struct TestView {
-    #[allow(unused)]
-    story: AnyView,
-}
-
-impl Render<Self> for TestView {
-    type Element = Div<Self>;
-
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
-        div()
-            .flex()
-            .bg(gpui::blue())
-            .flex_col()
-            .size_full()
-            .font("Helvetica")
-            .child(div().h_5())
-            .child(
-                div()
-                    .flex()
-                    .w_96()
-                    .bg(white())
-                    .relative()
-                    .child(div().child(concat!(
-            "The quick brown fox jumps over the lazy dog. ",
-            "Meanwhile, the lazy dog decided it was time for a change. ",
-            "He started daily workout routines, ate healthier and became the fastest dog in town.",
-        ))),
-            )
-    }
-}

crates/terminal_view2/src/terminal_panel.rs 🔗

@@ -335,8 +335,8 @@ impl TerminalPanel {
 
 impl EventEmitter<PanelEvent> for TerminalPanel {}
 
-impl Render<Self> for TerminalPanel {
-    type Element = Div<Self>;
+impl Render for TerminalPanel {
+    type Element = Div;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
         div().child(self.pane.clone())

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -9,10 +9,10 @@ pub mod terminal_panel;
 // use crate::terminal_element::TerminalElement;
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions, div, Action, AnyElement, AppContext, DispatchPhase, Div, Element, EventEmitter,
-    FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, InputHandler,
-    InteractiveElement, KeyDownEvent, Keystroke, Model, MouseButton, ParentElement, Pixels, Render,
-    SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView,
+    actions, div, Action, AnyElement, AppContext, Div, Element, EventEmitter, FocusEvent,
+    FocusHandle, Focusable, FocusableElement, FocusableView, InputHandler, InteractiveElement,
+    KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Render,
+    SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use language::Bias;
 use persistence::TERMINAL_DB;
@@ -31,7 +31,7 @@ use workspace::{
     notifications::NotifyResultExt,
     register_deserializable_item,
     searchable::{SearchEvent, SearchOptions, SearchableItem},
-    ui::{ContextMenu, Icon, IconElement, Label, ListEntry},
+    ui::{ContextMenu, Icon, IconElement, Label},
     CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
 };
 
@@ -63,7 +63,6 @@ pub struct SendKeystroke(String);
 actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
 
 pub fn init(cx: &mut AppContext) {
-    workspace::ui::init(cx);
     terminal_panel::init(cx);
     terminal::init(cx);
 
@@ -84,7 +83,7 @@ pub struct TerminalView {
     has_new_content: bool,
     //Currently using iTerm bell, show bell emoji in tab until input is received
     has_bell: bool,
-    context_menu: Option<View<ContextMenu<Self>>>,
+    context_menu: Option<View<ContextMenu>>,
     blink_state: bool,
     blinking_on: bool,
     blinking_paused: bool,
@@ -300,14 +299,8 @@ impl TerminalView {
         cx: &mut ViewContext<Self>,
     ) {
         self.context_menu = Some(ContextMenu::build(cx, |menu, _| {
-            menu.action(
-                ListEntry::new("clear", Label::new("Clear")),
-                Box::new(Clear),
-            )
-            .action(
-                ListEntry::new("close", Label::new("Close")),
-                Box::new(CloseActiveItem { save_intent: None }),
-            )
+            menu.action("Clear", Box::new(Clear))
+                .action("Close", Box::new(CloseActiveItem { save_intent: None }))
         }));
         dbg!(&position);
         // todo!()
@@ -508,12 +501,7 @@ pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<Re
 }
 
 impl TerminalView {
-    fn key_down(
-        &mut self,
-        event: &KeyDownEvent,
-        _dispatch_phase: DispatchPhase,
-        cx: &mut ViewContext<Self>,
-    ) {
+    fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) {
         self.clear_bel(cx);
         self.pause_cursor_blinking(cx);
 
@@ -540,8 +528,8 @@ impl TerminalView {
     }
 }
 
-impl Render<Self> for TerminalView {
-    type Element = Focusable<Self, Div<Self>>;
+impl Render for TerminalView {
+    type Element = Focusable<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let terminal_handle = self.terminal.clone().downgrade();
@@ -555,14 +543,14 @@ impl Render<Self> for TerminalView {
                 div()
                     .z_index(0)
                     .absolute()
-                    .on_key_down(Self::key_down)
-                    .on_action(TerminalView::send_text)
-                    .on_action(TerminalView::send_keystroke)
-                    .on_action(TerminalView::copy)
-                    .on_action(TerminalView::paste)
-                    .on_action(TerminalView::clear)
-                    .on_action(TerminalView::show_character_palette)
-                    .on_action(TerminalView::select_all)
+                    .on_key_down(cx.listener(Self::key_down))
+                    .on_action(cx.listener(TerminalView::send_text))
+                    .on_action(cx.listener(TerminalView::send_keystroke))
+                    .on_action(cx.listener(TerminalView::copy))
+                    .on_action(cx.listener(TerminalView::paste))
+                    .on_action(cx.listener(TerminalView::clear))
+                    .on_action(cx.listener(TerminalView::show_character_palette))
+                    .on_action(cx.listener(TerminalView::select_all))
                     // todo!()
                     .child(
                         "TERMINAL HERE", //     TerminalElement::new(
@@ -572,10 +560,13 @@ impl Render<Self> for TerminalView {
                                          //     self.can_navigate_to_selected_word,
                                          // )
                     )
-                    .on_mouse_down(MouseButton::Right, |this, event, cx| {
-                        this.deploy_context_menu(event.position, cx);
-                        cx.notify();
-                    }),
+                    .on_mouse_down(
+                        MouseButton::Right,
+                        cx.listener(|this, event: &MouseDownEvent, cx| {
+                            this.deploy_context_menu(event.position, cx);
+                            cx.notify();
+                        }),
+                    ),
             )
             .children(
                 self.context_menu
@@ -583,8 +574,8 @@ impl Render<Self> for TerminalView {
                     .map(|context_menu| div().z_index(1).absolute().child(context_menu)),
             )
             .track_focus(&self.focus_handle)
-            .on_focus_in(Self::focus_in)
-            .on_focus_out(Self::focus_out)
+            .on_focus_in(cx.listener(Self::focus_in))
+            .on_focus_out(cx.listener(Self::focus_out))
     }
 }
 
@@ -749,11 +740,7 @@ impl Item for TerminalView {
         Some(self.terminal().read(cx).title().into())
     }
 
-    fn tab_content<T: 'static>(
-        &self,
-        _detail: Option<usize>,
-        cx: &gpui::AppContext,
-    ) -> AnyElement<T> {
+    fn tab_content(&self, _detail: Option<usize>, cx: &WindowContext) -> AnyElement {
         let title = self.terminal().read(cx).title();
 
         div()
@@ -795,7 +782,7 @@ impl Item for TerminalView {
     // }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft { flex: None }
+        ToolbarItemLocation::PrimaryLeft
     }
 
     fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {

crates/theme2/Cargo.toml 🔗

@@ -5,9 +5,9 @@ edition = "2021"
 publish = false
 
 [features]
-default = ["stories"]
+default = []
 importing-themes = []
-stories = ["dep:itertools"]
+stories = ["dep:itertools", "dep:story"]
 test-support = [
     "gpui/test-support",
     "fs/test-support",
@@ -30,6 +30,7 @@ serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true
 settings = { package = "settings2", path = "../settings2" }
+story = { path = "../story", optional = true }
 toml.workspace = true
 uuid.workspace = true
 util = { path = "../util" }

crates/theme2/src/settings.rs 🔗

@@ -184,7 +184,7 @@ impl settings::Settings for ThemeSettings {
     ) -> schemars::schema::RootSchema {
         let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
         let theme_names = cx
-            .global::<Arc<ThemeRegistry>>()
+            .global::<ThemeRegistry>()
             .list_names(params.staff_mode)
             .map(|theme_name| Value::String(theme_name.to_string()))
             .collect();

crates/theme2/src/story.rs 🔗

@@ -1,41 +0,0 @@
-use gpui::{div, Div, Element, ParentElement, SharedString, Styled, ViewContext};
-
-use crate::ActiveTheme;
-
-pub struct Story {}
-
-impl Story {
-    pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
-        div()
-            .size_full()
-            .flex()
-            .flex_col()
-            .pt_2()
-            .px_4()
-            .font("Zed Mono")
-            .bg(cx.theme().colors().background)
-    }
-
-    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: SharedString) -> impl Element<V> {
-        div()
-            .text_xl()
-            .text_color(cx.theme().colors().text)
-            .child(title)
-    }
-
-    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
-        Self::title(cx, std::any::type_name::<T>().into())
-    }
-
-    pub fn label<V: 'static>(
-        cx: &mut ViewContext<V>,
-        label: impl Into<SharedString>,
-    ) -> impl Element<V> {
-        div()
-            .mt_4()
-            .mb_2()
-            .text_xs()
-            .text_color(cx.theme().colors().text)
-            .child(label.into())
-    }
-}

crates/theme2/src/styles.rs 🔗

@@ -4,8 +4,14 @@ mod status;
 mod syntax;
 mod system;
 
+#[cfg(feature = "stories")]
+mod stories;
+
 pub use colors::*;
 pub use players::*;
 pub use status::*;
 pub use syntax::*;
 pub use system::*;
+
+#[cfg(feature = "stories")]
+pub use stories::*;

crates/theme2/src/styles/players.rs 🔗

@@ -1,5 +1,7 @@
 use gpui::Hsla;
 
+use crate::{amber, blue, jade, lime, orange, pink, purple, red};
+
 #[derive(Debug, Clone, Copy, Default)]
 pub struct PlayerColor {
     pub cursor: Hsla,
@@ -133,141 +135,3 @@ impl PlayerColors {
         self.0[(participant_index as usize % len) + 1]
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-use crate::{amber, blue, jade, lime, orange, pink, purple, red};
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{ActiveTheme, Story};
-    use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
-
-    pub struct PlayerStory;
-
-    impl Render<Self> for PlayerStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx).child(
-                div()
-                    .flex()
-                    .flex_col()
-                    .gap_4()
-                    .child(Story::title_for::<_, PlayerColors>(cx))
-                    .child(Story::label(cx, "Player Colors"))
-                    .child(
-                        div()
-                            .flex()
-                            .flex_col()
-                            .gap_1()
-                            .child(
-                                div().flex().gap_1().children(
-                                    cx.theme().players().0.clone().iter_mut().map(|player| {
-                                        div().w_8().h_8().rounded_md().bg(player.cursor)
-                                    }),
-                                ),
-                            )
-                            .child(div().flex().gap_1().children(
-                                cx.theme().players().0.clone().iter_mut().map(|player| {
-                                    div().w_8().h_8().rounded_md().bg(player.background)
-                                }),
-                            ))
-                            .child(div().flex().gap_1().children(
-                                cx.theme().players().0.clone().iter_mut().map(|player| {
-                                    div().w_8().h_8().rounded_md().bg(player.selection)
-                                }),
-                            )),
-                    )
-                    .child(Story::label(cx, "Avatar Rings"))
-                    .child(div().flex().gap_1().children(
-                        cx.theme().players().0.clone().iter_mut().map(|player| {
-                            div()
-                                .my_1()
-                                .rounded_full()
-                                .border_2()
-                                .border_color(player.cursor)
-                                .child(
-                                    img()
-                                        .rounded_full()
-                                        .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                        .size_6()
-                                        .bg(gpui::red()),
-                                )
-                        }),
-                    ))
-                    .child(Story::label(cx, "Player Backgrounds"))
-                    .child(div().flex().gap_1().children(
-                        cx.theme().players().0.clone().iter_mut().map(|player| {
-                            div()
-                                .my_1()
-                                .rounded_xl()
-                                .flex()
-                                .items_center()
-                                .h_8()
-                                .py_0p5()
-                                .px_1p5()
-                                .bg(player.background)
-                                .child(
-                                div().relative().neg_mx_1().rounded_full().z_index(3)
-                                    .border_2()
-                                    .border_color(player.background)
-                                    .size(px(28.))
-                                    .child(
-                                    img()
-                                        .rounded_full()
-                                        .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                        .size(px(24.))
-                                        .bg(gpui::red()),
-                                ),
-                            ).child(
-                            div().relative().neg_mx_1().rounded_full().z_index(2)
-                                .border_2()
-                                .border_color(player.background)
-                                .size(px(28.))
-                                .child(
-                                img()
-                                    .rounded_full()
-                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                    .size(px(24.))
-                                    .bg(gpui::red()),
-                            ),
-                        ).child(
-                        div().relative().neg_mx_1().rounded_full().z_index(1)
-                            .border_2()
-                            .border_color(player.background)
-                            .size(px(28.))
-                            .child(
-                            img()
-                                .rounded_full()
-                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                .size(px(24.))
-                                .bg(gpui::red()),
-                        ),
-                    )
-                        }),
-                    ))
-                    .child(Story::label(cx, "Player Selections"))
-                    .child(div().flex().flex_col().gap_px().children(
-                        cx.theme().players().0.clone().iter_mut().map(|player| {
-                            div()
-                                .flex()
-                                .child(
-                                    div()
-                                        .flex()
-                                        .flex_none()
-                                        .rounded_sm()
-                                        .px_0p5()
-                                        .text_color(cx.theme().colors().text)
-                                        .bg(player.selection)
-                                        .child("The brown fox jumped over the lazy dog."),
-                                )
-                                .child(div().flex_1())
-                        }),
-                    )),
-            )
-        }
-    }
-}

crates/theme2/src/styles/stories/color.rs 🔗

@@ -0,0 +1,41 @@
+use gpui::prelude::*;
+use gpui::{div, px, Div, ViewContext};
+use story::Story;
+
+use crate::{default_color_scales, ColorScaleStep};
+
+pub struct ColorsStory;
+
+impl Render for ColorsStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let color_scales = default_color_scales();
+
+        Story::container().child(Story::title("Colors")).child(
+            div()
+                .id("colors")
+                .flex()
+                .flex_col()
+                .gap_1()
+                .overflow_y_scroll()
+                .text_color(gpui::white())
+                .children(color_scales.into_iter().map(|scale| {
+                    div()
+                        .flex()
+                        .child(
+                            div()
+                                .w(px(75.))
+                                .line_height(px(24.))
+                                .child(scale.name().clone()),
+                        )
+                        .child(
+                            div().flex().gap_1().children(
+                                ColorScaleStep::ALL
+                                    .map(|step| div().flex().size_6().bg(scale.step(cx, step))),
+                            ),
+                        )
+                })),
+        )
+    }
+}

crates/theme2/src/styles/stories/players.rs 🔗

@@ -0,0 +1,137 @@
+use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
+use story::Story;
+
+use crate::{ActiveTheme, PlayerColors};
+
+pub struct PlayerStory;
+
+impl Render for PlayerStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container().child(
+            div()
+                .flex()
+                .flex_col()
+                .gap_4()
+                .child(Story::title_for::<PlayerColors>())
+                .child(Story::label("Player Colors"))
+                .child(
+                    div()
+                        .flex()
+                        .flex_col()
+                        .gap_1()
+                        .child(
+                            div().flex().gap_1().children(
+                                cx.theme()
+                                    .players()
+                                    .0
+                                    .clone()
+                                    .iter_mut()
+                                    .map(|player| div().w_8().h_8().rounded_md().bg(player.cursor)),
+                            ),
+                        )
+                        .child(
+                            div().flex().gap_1().children(
+                                cx.theme().players().0.clone().iter_mut().map(|player| {
+                                    div().w_8().h_8().rounded_md().bg(player.background)
+                                }),
+                            ),
+                        )
+                        .child(
+                            div().flex().gap_1().children(
+                                cx.theme().players().0.clone().iter_mut().map(|player| {
+                                    div().w_8().h_8().rounded_md().bg(player.selection)
+                                }),
+                            ),
+                        ),
+                )
+                .child(Story::label("Avatar Rings"))
+                .child(div().flex().gap_1().children(
+                    cx.theme().players().0.clone().iter_mut().map(|player| {
+                        div()
+                            .my_1()
+                            .rounded_full()
+                            .border_2()
+                            .border_color(player.cursor)
+                            .child(
+                                img()
+                                    .rounded_full()
+                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                    .size_6()
+                                    .bg(gpui::red()),
+                            )
+                    }),
+                ))
+                .child(Story::label("Player Backgrounds"))
+                .child(div().flex().gap_1().children(
+                    cx.theme().players().0.clone().iter_mut().map(|player| {
+                        div()
+                                .my_1()
+                                .rounded_xl()
+                                .flex()
+                                .items_center()
+                                .h_8()
+                                .py_0p5()
+                                .px_1p5()
+                                .bg(player.background)
+                                .child(
+                                div().relative().neg_mx_1().rounded_full().z_index(3)
+                                    .border_2()
+                                    .border_color(player.background)
+                                    .size(px(28.))
+                                    .child(
+                                    img()
+                                        .rounded_full()
+                                        .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                        .size(px(24.))
+                                        .bg(gpui::red()),
+                                ),
+                            ).child(
+                            div().relative().neg_mx_1().rounded_full().z_index(2)
+                                .border_2()
+                                .border_color(player.background)
+                                .size(px(28.))
+                                .child(
+                                img()
+                                    .rounded_full()
+                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                    .size(px(24.))
+                                    .bg(gpui::red()),
+                            ),
+                        ).child(
+                        div().relative().neg_mx_1().rounded_full().z_index(1)
+                            .border_2()
+                            .border_color(player.background)
+                            .size(px(28.))
+                            .child(
+                            img()
+                                .rounded_full()
+                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                .size(px(24.))
+                                .bg(gpui::red()),
+                        ),
+                    )
+                    }),
+                ))
+                .child(Story::label("Player Selections"))
+                .child(div().flex().flex_col().gap_px().children(
+                    cx.theme().players().0.clone().iter_mut().map(|player| {
+                        div()
+                            .flex()
+                            .child(
+                                div()
+                                    .flex()
+                                    .flex_none()
+                                    .rounded_sm()
+                                    .px_0p5()
+                                    .text_color(cx.theme().colors().text)
+                                    .bg(player.selection)
+                                    .child("The brown fox jumped over the lazy dog."),
+                            )
+                            .child(div().flex_1())
+                    }),
+                )),
+        )
+    }
+}

crates/theme2/src/theme2.rs 🔗

@@ -63,6 +63,12 @@ impl ActiveTheme for AppContext {
     }
 }
 
+// impl<'a> ActiveTheme for WindowContext<'a> {
+//     fn theme(&self) -> &Arc<Theme> {
+//         &ThemeSettings::get_global(self.app()).active_theme
+//     }
+// }
+
 pub struct ThemeFamily {
     pub id: String,
     pub name: SharedString,
@@ -138,8 +144,3 @@ pub struct DiagnosticStyle {
     pub hint: Hsla,
     pub ignored: Hsla,
 }
-
-#[cfg(feature = "stories")]
-mod story;
-#[cfg(feature = "stories")]
-pub use story::*;

crates/ui2/Cargo.toml 🔗

@@ -4,6 +4,10 @@ version = "0.1.0"
 edition = "2021"
 publish = false
 
+[lib]
+name = "ui2"
+path = "src/ui2.rs"
+
 [dependencies]
 anyhow.workspace = true
 chrono = "0.4"
@@ -13,10 +17,11 @@ menu = { package = "menu2", path = "../menu2"}
 serde.workspace = true
 settings2 = { path = "../settings2" }
 smallvec.workspace = true
+story = { path = "../story", optional = true }
 strum = { version = "0.25.0", features = ["derive"] }
 theme2 = { path = "../theme2" }
 rand = "0.8"
 
 [features]
 default = []
-stories = ["dep:itertools"]
+stories = ["dep:itertools", "dep:story"]

crates/ui2/src/components.rs 🔗

@@ -2,56 +2,40 @@ mod avatar;
 mod button;
 mod checkbox;
 mod context_menu;
-mod details;
+mod disclosure;
 mod divider;
-mod elevated_surface;
-mod facepile;
 mod icon;
 mod icon_button;
-mod indicator;
 mod input;
 mod keybinding;
 mod label;
 mod list;
-mod modal;
-mod notification_toast;
-mod palette;
-mod panel;
-mod player;
-mod player_stack;
+mod popover;
 mod slot;
 mod stack;
-mod tab;
-mod toast;
 mod toggle;
-mod tool_divider;
 mod tooltip;
 
+#[cfg(feature = "stories")]
+mod stories;
+
 pub use avatar::*;
 pub use button::*;
 pub use checkbox::*;
 pub use context_menu::*;
-pub use details::*;
+pub use disclosure::*;
 pub use divider::*;
-pub use elevated_surface::*;
-pub use facepile::*;
 pub use icon::*;
 pub use icon_button::*;
-pub use indicator::*;
 pub use input::*;
 pub use keybinding::*;
 pub use label::*;
 pub use list::*;
-pub use modal::*;
-pub use notification_toast::*;
-pub use palette::*;
-pub use panel::*;
-pub use player::*;
-pub use player_stack::*;
+pub use popover::*;
 pub use slot::*;
 pub use stack::*;
-pub use tab::*;
-pub use toast::*;
 pub use toggle::*;
-pub use tool_divider::*;
 pub use tooltip::*;
+
+#[cfg(feature = "stories")]
+pub use stories::*;

crates/ui2/src/components/avatar.rs 🔗

@@ -1,16 +1,23 @@
 use crate::prelude::*;
 use gpui::{img, Img, RenderOnce};
 
+#[derive(Debug, Default, PartialEq, Clone)]
+pub enum Shape {
+    #[default]
+    Circle,
+    RoundedRectangle,
+}
+
 #[derive(RenderOnce)]
 pub struct Avatar {
     src: SharedString,
     shape: Shape,
 }
 
-impl<V: 'static> Component<V> for Avatar {
-    type Rendered = Img<V>;
+impl Component for Avatar {
+    type Rendered = Img;
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, _: &mut WindowContext) -> Self::Rendered {
         let mut img = img();
 
         if self.shape == Shape::Circle {
@@ -39,31 +46,3 @@ impl Avatar {
         self
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct AvatarStory;
-
-    impl Render<Self> for AvatarStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Avatar>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Avatar::new(
-                    "https://avatars.githubusercontent.com/u/1714999?v=4",
-                ))
-                .child(Avatar::new(
-                    "https://avatars.githubusercontent.com/u/326587?v=4",
-                ))
-        }
-    }
-}

crates/ui2/src/components/button.rs 🔗

@@ -1,28 +1,28 @@
-use std::sync::Arc;
+use std::rc::Rc;
 
 use gpui::{
-    DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful, StatefulInteractiveElement,
+    DefiniteLength, Div, Hsla, MouseButton, MouseDownEvent, RenderOnce, StatefulInteractiveElement,
     WindowContext,
 };
 
 use crate::prelude::*;
-use crate::{h_stack, Icon, IconButton, IconElement, Label, LineHeightStyle, TextColor};
+use crate::{h_stack, Color, Icon, IconButton, IconElement, Label, LineHeightStyle};
 
 /// Provides the flexibility to use either a standard
 /// button or an icon button in a given context.
-pub enum ButtonOrIconButton<V: 'static> {
-    Button(Button<V>),
-    IconButton(IconButton<V>),
+pub enum ButtonOrIconButton {
+    Button(Button),
+    IconButton(IconButton),
 }
 
-impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
-    fn from(value: Button<V>) -> Self {
+impl From<Button> for ButtonOrIconButton {
+    fn from(value: Button) -> Self {
         Self::Button(value)
     }
 }
 
-impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
-    fn from(value: IconButton<V>) -> Self {
+impl From<IconButton> for ButtonOrIconButton {
+    fn from(value: IconButton) -> Self {
         Self::IconButton(value)
     }
 }
@@ -64,42 +64,26 @@ impl ButtonVariant {
     }
 }
 
-pub type ClickHandler<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>)>;
-
-struct ButtonHandlers<V: 'static> {
-    click: Option<ClickHandler<V>>,
-}
-
-unsafe impl<S> Send for ButtonHandlers<S> {}
-unsafe impl<S> Sync for ButtonHandlers<S> {}
-
-impl<V: 'static> Default for ButtonHandlers<V> {
-    fn default() -> Self {
-        Self { click: None }
-    }
-}
-
 #[derive(RenderOnce)]
-pub struct Button<V: 'static> {
+pub struct Button {
     disabled: bool,
-    handlers: ButtonHandlers<V>,
+    click_handler: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
     icon: Option<Icon>,
     icon_position: Option<IconPosition>,
     label: SharedString,
     variant: ButtonVariant,
     width: Option<DefiniteLength>,
-    color: Option<TextColor>,
+    color: Option<Color>,
 }
 
-impl<V: 'static> Component<V> for Button<V> {
-    type Rendered = Stateful<V, Div<V>>;
+impl Component for Button {
+    type Rendered = gpui::Stateful<Div>;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let _view: &mut V = view;
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let (icon_color, label_color) = match (self.disabled, self.color) {
-            (true, _) => (TextColor::Disabled, TextColor::Disabled),
-            (_, None) => (TextColor::Default, TextColor::Default),
-            (_, Some(color)) => (TextColor::from(color), color),
+            (true, _) => (Color::Disabled, Color::Disabled),
+            (_, None) => (Color::Default, Color::Default),
+            (_, Some(color)) => (Color::from(color), color),
         };
 
         let mut button = h_stack()
@@ -133,9 +117,9 @@ impl<V: 'static> Component<V> for Button<V> {
             button = button.w(width).justify_center();
         }
 
-        if let Some(click_handler) = self.handlers.click.clone() {
-            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
-                click_handler(state, cx);
+        if let Some(click_handler) = self.click_handler.clone() {
+            button = button.on_mouse_down(MouseButton::Left, move |event, cx| {
+                click_handler(event, cx);
             });
         }
 
@@ -143,11 +127,11 @@ impl<V: 'static> Component<V> for Button<V> {
     }
 }
 
-impl<V: 'static> Button<V> {
+impl Button {
     pub fn new(label: impl Into<SharedString>) -> Self {
         Self {
             disabled: false,
-            handlers: ButtonHandlers::default(),
+            click_handler: None,
             icon: None,
             icon_position: None,
             label: label.into(),
@@ -184,8 +168,11 @@ impl<V: 'static> Button<V> {
         self
     }
 
-    pub fn on_click(mut self, handler: ClickHandler<V>) -> Self {
-        self.handlers.click = Some(handler);
+    pub fn on_click(
+        mut self,
+        handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+    ) -> Self {
+        self.click_handler = Some(Rc::new(handler));
         self
     }
 
@@ -194,14 +181,14 @@ impl<V: 'static> Button<V> {
         self
     }
 
-    pub fn color(mut self, color: Option<TextColor>) -> Self {
+    pub fn color(mut self, color: Option<Color>) -> Self {
         self.color = color;
         self
     }
 
-    pub fn label_color(&self, color: Option<TextColor>) -> TextColor {
+    pub fn label_color(&self, color: Option<Color>) -> Color {
         if self.disabled {
-            TextColor::Disabled
+            Color::Disabled
         } else if let Some(color) = color {
             color
         } else {
@@ -209,253 +196,38 @@ impl<V: 'static> Button<V> {
         }
     }
 
-    fn render_label(&self, color: TextColor) -> Label {
+    fn render_label(&self, color: Color) -> Label {
         Label::new(self.label.clone())
             .color(color)
             .line_height_style(LineHeightStyle::UILabel)
     }
 
-    fn render_icon(&self, icon_color: TextColor) -> Option<IconElement> {
+    fn render_icon(&self, icon_color: Color) -> Option<IconElement> {
         self.icon.map(|i| IconElement::new(i).color(icon_color))
     }
-
-    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        let (icon_color, label_color) = match (self.disabled, self.color) {
-            (true, _) => (TextColor::Disabled, TextColor::Disabled),
-            (_, None) => (TextColor::Default, TextColor::Default),
-            (_, Some(color)) => (TextColor::from(color), color),
-        };
-
-        let mut button = h_stack()
-            .id(SharedString::from(format!("{}", self.label)))
-            .relative()
-            .p_1()
-            .text_ui()
-            .rounded_md()
-            .bg(self.variant.bg_color(cx))
-            .cursor_pointer()
-            .hover(|style| style.bg(self.variant.bg_color_hover(cx)))
-            .active(|style| style.bg(self.variant.bg_color_active(cx)));
-
-        match (self.icon, self.icon_position) {
-            (Some(_), Some(IconPosition::Left)) => {
-                button = button
-                    .gap_1()
-                    .child(self.render_label(label_color))
-                    .children(self.render_icon(icon_color))
-            }
-            (Some(_), Some(IconPosition::Right)) => {
-                button = button
-                    .gap_1()
-                    .children(self.render_icon(icon_color))
-                    .child(self.render_label(label_color))
-            }
-            (_, _) => button = button.child(self.render_label(label_color)),
-        }
-
-        if let Some(width) = self.width {
-            button = button.w(width).justify_center();
-        }
-
-        if let Some(click_handler) = self.handlers.click.clone() {
-            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
-                click_handler(state, cx);
-            });
-        }
-
-        button
-    }
 }
 
 #[derive(RenderOnce)]
-pub struct ButtonGroup<V: 'static> {
-    buttons: Vec<Button<V>>,
+pub struct ButtonGroup {
+    buttons: Vec<Button>,
 }
 
-impl<V: 'static> Component<V> for ButtonGroup<V> {
-    type Rendered = Div<V>;
+impl Component for ButtonGroup {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let mut group = h_stack();
 
         for button in self.buttons.into_iter() {
-            group = group.child(button.render(view, cx));
+            group = group.child(button.render(cx));
         }
 
         group
     }
 }
 
-impl<V: 'static> ButtonGroup<V> {
-    pub fn new(buttons: Vec<Button<V>>) -> Self {
+impl ButtonGroup {
+    pub fn new(buttons: Vec<Button>) -> Self {
         Self { buttons }
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{h_stack, v_stack, Story, TextColor};
-    use gpui::{rems, Div, Render};
-    use strum::IntoEnumIterator;
-
-    pub struct ButtonStory;
-
-    impl Render<Self> for ButtonStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let states = InteractionState::iter();
-
-            Story::container(cx)
-                .child(Story::title_for::<_, Button<Self>>(cx))
-                .child(
-                    div()
-                        .flex()
-                        .gap_8()
-                        .child(
-                            div()
-                                .child(Story::label(cx, "Ghost (Default)"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
-                                        )
-                                })))
-                                .child(Story::label(cx, "Ghost – Left Icon"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Ghost)
-                                                .icon(Icon::Plus)
-                                                .icon_position(IconPosition::Left), // .state(state),
-                                        )
-                                })))
-                                .child(Story::label(cx, "Ghost – Right Icon"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Ghost)
-                                                .icon(Icon::Plus)
-                                                .icon_position(IconPosition::Right), // .state(state),
-                                        )
-                                }))),
-                        )
-                        .child(
-                            div()
-                                .child(Story::label(cx, "Filled"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
-                                        )
-                                })))
-                                .child(Story::label(cx, "Filled – Left Button"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Filled)
-                                                .icon(Icon::Plus)
-                                                .icon_position(IconPosition::Left), // .state(state),
-                                        )
-                                })))
-                                .child(Story::label(cx, "Filled – Right Button"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Filled)
-                                                .icon(Icon::Plus)
-                                                .icon_position(IconPosition::Right), // .state(state),
-                                        )
-                                }))),
-                        )
-                        .child(
-                            div()
-                                .child(Story::label(cx, "Fixed With"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Filled)
-                                                // .state(state)
-                                                .width(Some(rems(6.).into())),
-                                        )
-                                })))
-                                .child(Story::label(cx, "Fixed With – Left Icon"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Filled)
-                                                // .state(state)
-                                                .icon(Icon::Plus)
-                                                .icon_position(IconPosition::Left)
-                                                .width(Some(rems(6.).into())),
-                                        )
-                                })))
-                                .child(Story::label(cx, "Fixed With – Right Icon"))
-                                .child(h_stack().gap_2().children(states.clone().map(|state| {
-                                    v_stack()
-                                        .gap_1()
-                                        .child(
-                                            Label::new(state.to_string()).color(TextColor::Muted),
-                                        )
-                                        .child(
-                                            Button::new("Label")
-                                                .variant(ButtonVariant::Filled)
-                                                // .state(state)
-                                                .icon(Icon::Plus)
-                                                .icon_position(IconPosition::Right)
-                                                .width(Some(rems(6.).into())),
-                                        )
-                                }))),
-                        ),
-                )
-                .child(Story::label(cx, "Button with `on_click`"))
-                .child(
-                    Button::new("Label")
-                        .variant(ButtonVariant::Ghost)
-                        .on_click(Arc::new(|_view, _cx| println!("Button clicked."))),
-                )
-        }
-    }
-}

crates/ui2/src/components/checkbox.rs 🔗

@@ -1,10 +1,10 @@
-use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Stateful, Styled, ViewContext};
-use std::sync::Arc;
+use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Styled, WindowContext};
+
 use theme2::ActiveTheme;
 
-use crate::{Icon, IconElement, Selection, TextColor};
+use crate::{Color, Icon, IconElement, Selection};
 
-pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) + Send + Sync>;
+pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
 
 /// # Checkbox
 ///
@@ -12,17 +12,17 @@ pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) +
 /// Each checkbox works independently from other checkboxes in the list,
 /// therefore checking an additional box does not affect any other selections.
 #[derive(RenderOnce)]
-pub struct Checkbox<V: 'static> {
+pub struct Checkbox {
     id: ElementId,
     checked: Selection,
     disabled: bool,
-    on_click: Option<CheckHandler<V>>,
+    on_click: Option<CheckHandler>,
 }
 
-impl<V: 'static> Component<V> for Checkbox<V> {
-    type Rendered = Stateful<V, Div<V>>;
+impl Component for Checkbox {
+    type Rendered = gpui::Stateful<Div>;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let group_id = format!("checkbox_group_{:?}", self.id);
 
         let icon = match self.checked {
@@ -34,9 +34,9 @@ impl<V: 'static> Component<V> for Checkbox<V> {
                         .color(
                             // If the checkbox is disabled we change the color of the icon.
                             if self.disabled {
-                                TextColor::Disabled
+                                Color::Disabled
                             } else {
-                                TextColor::Selected
+                                Color::Selected
                             },
                         ),
                 )
@@ -49,9 +49,9 @@ impl<V: 'static> Component<V> for Checkbox<V> {
                         .color(
                             // If the checkbox is disabled we change the color of the icon.
                             if self.disabled {
-                                TextColor::Disabled
+                                Color::Disabled
                             } else {
-                                TextColor::Selected
+                                Color::Selected
                             },
                         ),
                 )
@@ -137,13 +137,11 @@ impl<V: 'static> Component<V> for Checkbox<V> {
             )
             .when_some(
                 self.on_click.filter(|_| !self.disabled),
-                |this, on_click| {
-                    this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
-                },
+                |this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
             )
     }
 }
-impl<V: 'static> Checkbox<V> {
+impl Checkbox {
     pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
         Self {
             id: id.into(),
@@ -160,13 +158,13 @@ impl<V: 'static> Checkbox<V> {
 
     pub fn on_click(
         mut self,
-        handler: impl 'static + Fn(Selection, &mut V, &mut ViewContext<V>) + Send + Sync,
+        handler: impl 'static + Fn(&Selection, &mut WindowContext) + Send + Sync,
     ) -> Self {
-        self.on_click = Some(Arc::new(handler));
+        self.on_click = Some(Box::new(handler));
         self
     }
 
-    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    pub fn render(self, cx: &mut WindowContext) -> impl Element {
         let group_id = format!("checkbox_group_{:?}", self.id);
 
         let icon = match self.checked {
@@ -178,9 +176,9 @@ impl<V: 'static> Checkbox<V> {
                         .color(
                             // If the checkbox is disabled we change the color of the icon.
                             if self.disabled {
-                                TextColor::Disabled
+                                Color::Disabled
                             } else {
-                                TextColor::Selected
+                                Color::Selected
                             },
                         ),
                 )
@@ -193,9 +191,9 @@ impl<V: 'static> Checkbox<V> {
                         .color(
                             // If the checkbox is disabled we change the color of the icon.
                             if self.disabled {
-                                TextColor::Disabled
+                                Color::Disabled
                             } else {
-                                TextColor::Selected
+                                Color::Selected
                             },
                         ),
                 )
@@ -281,69 +279,7 @@ impl<V: 'static> Checkbox<V> {
             )
             .when_some(
                 self.on_click.filter(|_| !self.disabled),
-                |this, on_click| {
-                    this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
-                },
+                |this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
             )
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{h_stack, Story};
-    use gpui::{Div, Render};
-
-    pub struct CheckboxStory;
-
-    impl Render<Self> for CheckboxStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Checkbox<Self>>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    h_stack()
-                        .p_2()
-                        .gap_2()
-                        .rounded_md()
-                        .border()
-                        .border_color(cx.theme().colors().border)
-                        .child(Checkbox::new("checkbox-enabled", Selection::Unselected))
-                        .child(Checkbox::new(
-                            "checkbox-intermediate",
-                            Selection::Indeterminate,
-                        ))
-                        .child(Checkbox::new("checkbox-selected", Selection::Selected)),
-                )
-                .child(Story::label(cx, "Disabled"))
-                .child(
-                    h_stack()
-                        .p_2()
-                        .gap_2()
-                        .rounded_md()
-                        .border()
-                        .border_color(cx.theme().colors().border)
-                        .child(
-                            Checkbox::new("checkbox-disabled", Selection::Unselected)
-                                .disabled(true),
-                        )
-                        .child(
-                            Checkbox::new(
-                                "checkbox-disabled-intermediate",
-                                Selection::Indeterminate,
-                            )
-                            .disabled(true),
-                        )
-                        .child(
-                            Checkbox::new("checkbox-disabled-selected", Selection::Selected)
-                                .disabled(true),
-                        ),
-                )
-        }
-    }
-}

crates/ui2/src/components/context_menu.rs 🔗

@@ -1,47 +1,42 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use crate::{prelude::*, v_stack, List};
-use crate::{ListEntry, ListSeparator, ListSubHeader};
+use crate::{prelude::*, v_stack, Label, List};
+use crate::{ListItem, ListSeparator, ListSubHeader};
 use gpui::{
-    overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
-    EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
-    MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView,
+    overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase,
+    Div, EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
+    MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext,
 };
 
-pub enum ContextMenuItem<V: 'static> {
-    Separator(ListSeparator),
-    Header(ListSubHeader),
-    Entry(
-        ListEntry<ContextMenu<V>>,
-        Rc<dyn Fn(&mut V, &mut ViewContext<V>)>,
-    ),
+pub enum ContextMenuItem {
+    Separator,
+    Header(SharedString),
+    Entry(SharedString, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
 }
 
-pub struct ContextMenu<V: 'static> {
-    items: Vec<ContextMenuItem<V>>,
+pub struct ContextMenu {
+    items: Vec<ContextMenuItem>,
     focus_handle: FocusHandle,
-    handle: WeakView<V>,
 }
 
-impl<V: 'static> FocusableView for ContextMenu<V> {
+impl FocusableView for ContextMenu {
     fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
         self.focus_handle.clone()
     }
 }
 
-impl<V: 'static> EventEmitter<Manager> for ContextMenu<V> {}
+impl EventEmitter<Manager> for ContextMenu {}
 
-impl<V: 'static> ContextMenu<V> {
+impl ContextMenu {
     pub fn build(
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
+        cx: &mut WindowContext,
+        f: impl FnOnce(Self, &mut WindowContext) -> Self,
     ) -> View<Self> {
-        let handle = cx.view().downgrade();
+        // let handle = cx.view().downgrade();
         cx.build_view(|cx| {
             f(
                 Self {
-                    handle,
                     items: Default::default(),
                     focus_handle: cx.focus_handle(),
                 },
@@ -51,29 +46,30 @@ impl<V: 'static> ContextMenu<V> {
     }
 
     pub fn header(mut self, title: impl Into<SharedString>) -> Self {
-        self.items
-            .push(ContextMenuItem::Header(ListSubHeader::new(title)));
+        self.items.push(ContextMenuItem::Header(title.into()));
         self
     }
 
     pub fn separator(mut self) -> Self {
-        self.items.push(ContextMenuItem::Separator(ListSeparator));
+        self.items.push(ContextMenuItem::Separator);
         self
     }
 
     pub fn entry(
         mut self,
-        view: ListEntry<Self>,
-        on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static,
+        label: impl Into<SharedString>,
+        on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.items
-            .push(ContextMenuItem::Entry(view, Rc::new(on_click)));
+            .push(ContextMenuItem::Entry(label.into(), Rc::new(on_click)));
         self
     }
 
-    pub fn action(self, view: ListEntry<Self>, action: Box<dyn Action>) -> Self {
+    pub fn action(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
         // todo: add the keybindings to the list entry
-        self.entry(view, move |_, cx| cx.dispatch_action(action.boxed_clone()))
+        self.entry(label.into(), move |_, cx| {
+            cx.dispatch_action(action.boxed_clone())
+        })
     }
 
     pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -86,39 +82,42 @@ impl<V: 'static> ContextMenu<V> {
     }
 }
 
-impl<V: 'static> Render<Self> for ContextMenu<V> {
-    type Element = Div<Self>;
+impl Render for ContextMenu {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div().elevation_2(cx).flex().flex_row().child(
             v_stack()
                 .min_w(px(200.))
                 .track_focus(&self.focus_handle)
-                .on_mouse_down_out(|this: &mut Self, _, cx| this.cancel(&Default::default(), cx))
+                .on_mouse_down_out(
+                    cx.listener(|this: &mut Self, _, cx| this.cancel(&Default::default(), cx)),
+                )
                 // .on_action(ContextMenu::select_first)
                 // .on_action(ContextMenu::select_last)
                 // .on_action(ContextMenu::select_next)
                 // .on_action(ContextMenu::select_prev)
-                .on_action(ContextMenu::confirm)
-                .on_action(ContextMenu::cancel)
+                .on_action(cx.listener(ContextMenu::confirm))
+                .on_action(cx.listener(ContextMenu::cancel))
                 .flex_none()
                 // .bg(cx.theme().colors().elevated_surface_background)
                 // .border()
                 // .border_color(cx.theme().colors().border)
                 .child(
                     List::new().children(self.items.iter().map(|item| match item {
-                        ContextMenuItem::Separator(separator) => {
-                            separator.clone().render_into_any()
+                        ContextMenuItem::Separator => ListSeparator::new().render_into_any(),
+                        ContextMenuItem::Header(header) => {
+                            ListSubHeader::new(header.clone()).render_into_any()
                         }
-                        ContextMenuItem::Header(header) => header.clone().render_into_any(),
                         ContextMenuItem::Entry(entry, callback) => {
                             let callback = callback.clone();
-                            let handle = self.handle.clone();
-                            entry
-                                .clone()
-                                .on_click(move |this, cx| {
-                                    handle.update(cx, |view, cx| callback(view, cx)).ok();
-                                    cx.emit(Manager::Dismiss);
+                            let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss));
+
+                            ListItem::new(entry.clone())
+                                .child(Label::new(entry.clone()))
+                                .on_click(move |event, cx| {
+                                    callback(event, cx);
+                                    dismiss(event, cx)
                                 })
                                 .render_into_any()
                         }
@@ -128,22 +127,21 @@ impl<V: 'static> Render<Self> for ContextMenu<V> {
     }
 }
 
-pub struct MenuHandle<V: 'static, M: ManagedView> {
+pub struct MenuHandle<M: ManagedView> {
     id: ElementId,
-    child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement<V> + 'static>>,
-    menu_builder: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static>>,
-
+    child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement + 'static>>,
+    menu_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
     anchor: Option<AnchorCorner>,
     attach: Option<AnchorCorner>,
 }
 
-impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
-    pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static) -> Self {
+impl<M: ManagedView> MenuHandle<M> {
+    pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View<M> + 'static) -> Self {
         self.menu_builder = Some(Rc::new(f));
         self
     }
 
-    pub fn child<R: RenderOnce<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
+    pub fn child<R: RenderOnce>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
         self.child_builder = Some(Box::new(|b| f(b).render_once().into_any()));
         self
     }
@@ -162,7 +160,7 @@ impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
     }
 }
 
-pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<V, M> {
+pub fn menu_handle<M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<M> {
     MenuHandle {
         id: id.into(),
         child_builder: None,
@@ -172,21 +170,20 @@ pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> Menu
     }
 }
 
-pub struct MenuHandleState<V, M> {
+pub struct MenuHandleState<M> {
     menu: Rc<RefCell<Option<View<M>>>>,
     position: Rc<RefCell<Point<Pixels>>>,
     child_layout_id: Option<LayoutId>,
-    child_element: Option<AnyElement<V>>,
-    menu_element: Option<AnyElement<V>>,
+    child_element: Option<AnyElement>,
+    menu_element: Option<AnyElement>,
 }
-impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
-    type State = MenuHandleState<V, M>;
+impl<M: ManagedView> Element for MenuHandle<M> {
+    type State = MenuHandleState<M>;
 
     fn layout(
         &mut self,
-        view_state: &mut V,
         element_state: Option<Self::State>,
-        cx: &mut crate::ViewContext<V>,
+        cx: &mut WindowContext,
     ) -> (gpui::LayoutId, Self::State) {
         let (menu, position) = if let Some(element_state) = element_state {
             (element_state.menu, element_state.position)
@@ -197,14 +194,14 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
         let mut menu_layout_id = None;
 
         let menu_element = menu.borrow_mut().as_mut().map(|menu| {
-            let mut overlay = overlay::<V>().snap_to_window();
+            let mut overlay = overlay().snap_to_window();
             if let Some(anchor) = self.anchor {
                 overlay = overlay.anchor(anchor);
             }
             overlay = overlay.position(*position.borrow());
 
             let mut element = overlay.child(menu.clone()).into_any();
-            menu_layout_id = Some(element.layout(view_state, cx));
+            menu_layout_id = Some(element.layout(cx));
             element
         });
 
@@ -215,7 +212,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
 
         let child_layout_id = child_element
             .as_mut()
-            .map(|child_element| child_element.layout(view_state, cx));
+            .map(|child_element| child_element.layout(cx));
 
         let layout_id = cx.request_layout(
             &gpui::Style::default(),
@@ -237,16 +234,15 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
     fn paint(
         self,
         bounds: Bounds<gpui::Pixels>,
-        view_state: &mut V,
         element_state: &mut Self::State,
-        cx: &mut crate::ViewContext<V>,
+        cx: &mut WindowContext,
     ) {
         if let Some(child) = element_state.child_element.take() {
-            child.paint(view_state, cx);
+            child.paint(cx);
         }
 
         if let Some(menu) = element_state.menu_element.take() {
-            menu.paint(view_state, cx);
+            menu.paint(cx);
             return;
         }
 
@@ -258,7 +254,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
         let attach = self.attach.clone();
         let child_layout_id = element_state.child_layout_id.clone();
 
-        cx.on_mouse_event(move |view_state, event: &MouseDownEvent, phase, cx| {
+        cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
             if phase == DispatchPhase::Bubble
                 && event.button == MouseButton::Right
                 && bounds.contains_point(&event.position)
@@ -266,9 +262,9 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
                 cx.stop_propagation();
                 cx.prevent_default();
 
-                let new_menu = (builder)(view_state, cx);
+                let new_menu = (builder)(cx);
                 let menu2 = menu.clone();
-                cx.subscribe(&new_menu, move |this, modal, e, cx| match e {
+                cx.subscribe(&new_menu, move |modal, e, cx| match e {
                     &Manager::Dismiss => {
                         *menu2.borrow_mut() = None;
                         cx.notify();
@@ -291,7 +287,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
     }
 }
 
-impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
+impl<M: ManagedView> RenderOnce for MenuHandle<M> {
     type Element = Self;
 
     fn element_id(&self) -> Option<gpui::ElementId> {
@@ -302,120 +298,3 @@ impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
         self
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{story::Story, Label};
-    use gpui::{actions, Div, Render};
-
-    actions!(PrintCurrentDate, PrintBestFood);
-
-    fn build_menu<V: Render<V>>(
-        cx: &mut ViewContext<V>,
-        header: impl Into<SharedString>,
-    ) -> View<ContextMenu<V>> {
-        let handle = cx.view().clone();
-        ContextMenu::build(cx, |menu, _| {
-            menu.header(header)
-                .separator()
-                .entry(
-                    ListEntry::new("Print current time", Label::new("Print current time")),
-                    |v, cx| {
-                        println!("dispatching PrintCurrentTime action");
-                        cx.dispatch_action(PrintCurrentDate.boxed_clone())
-                    },
-                )
-                .entry(
-                    ListEntry::new("Print best food", Label::new("Print best food")),
-                    |v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
-                )
-        })
-    }
-
-    pub struct ContextMenuStory;
-
-    impl Render<Self> for ContextMenuStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .on_action(|_, _: &PrintCurrentDate, _| {
-                    println!("printing unix time!");
-                    if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
-                        println!("Current Unix time is {:?}", unix_time.as_secs());
-                    }
-                })
-                .on_action(|_, _: &PrintBestFood, _| {
-                    println!("burrito");
-                })
-                .flex()
-                .flex_row()
-                .justify_between()
-                .child(
-                    div()
-                        .flex()
-                        .flex_col()
-                        .justify_between()
-                        .child(
-                            menu_handle("test2")
-                                .child(|is_open| {
-                                    Label::new(if is_open {
-                                        "TOP LEFT"
-                                    } else {
-                                        "RIGHT CLICK ME"
-                                    })
-                                })
-                                .menu(move |_, cx| build_menu(cx, "top left")),
-                        )
-                        .child(
-                            menu_handle("test1")
-                                .child(|is_open| {
-                                    Label::new(if is_open {
-                                        "BOTTOM LEFT"
-                                    } else {
-                                        "RIGHT CLICK ME"
-                                    })
-                                })
-                                .anchor(AnchorCorner::BottomLeft)
-                                .attach(AnchorCorner::TopLeft)
-                                .menu(move |_, cx| build_menu(cx, "bottom left")),
-                        ),
-                )
-                .child(
-                    div()
-                        .flex()
-                        .flex_col()
-                        .justify_between()
-                        .child(
-                            menu_handle("test3")
-                                .child(|is_open| {
-                                    Label::new(if is_open {
-                                        "TOP RIGHT"
-                                    } else {
-                                        "RIGHT CLICK ME"
-                                    })
-                                })
-                                .anchor(AnchorCorner::TopRight)
-                                .menu(move |_, cx| build_menu(cx, "top right")),
-                        )
-                        .child(
-                            menu_handle("test4")
-                                .child(|is_open| {
-                                    Label::new(if is_open {
-                                        "BOTTOM RIGHT"
-                                    } else {
-                                        "RIGHT CLICK ME"
-                                    })
-                                })
-                                .anchor(AnchorCorner::BottomRight)
-                                .attach(AnchorCorner::TopRight)
-                                .menu(move |_, cx| build_menu(cx, "bottom right")),
-                        ),
-                )
-        }
-    }
-}

crates/ui2/src/components/details.rs 🔗

@@ -1,83 +0,0 @@
-use crate::prelude::*;
-use crate::{v_stack, ButtonGroup};
-
-#[derive(RenderOnce)]
-pub struct Details<V: 'static> {
-    text: &'static str,
-    meta: Option<&'static str>,
-    actions: Option<ButtonGroup<V>>,
-}
-
-impl<V: 'static> Component<V> for Details<V> {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        v_stack()
-            .p_1()
-            .gap_0p5()
-            .text_ui_sm()
-            .text_color(cx.theme().colors().text)
-            .size_full()
-            .child(self.text)
-            .children(self.meta.map(|m| m))
-            .children(self.actions.map(|a| a))
-    }
-}
-
-impl<V: 'static> Details<V> {
-    pub fn new(text: &'static str) -> Self {
-        Self {
-            text,
-            meta: None,
-            actions: None,
-        }
-    }
-
-    pub fn meta_text(mut self, meta: &'static str) -> Self {
-        self.meta = Some(meta);
-        self
-    }
-
-    pub fn actions(mut self, actions: ButtonGroup<V>) -> Self {
-        self.actions = Some(actions);
-        self
-    }
-}
-
-use gpui::{Div, RenderOnce};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{Button, Story};
-    use gpui::{Div, Render};
-
-    pub struct DetailsStory;
-
-    impl Render<Self> for DetailsStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Details<Self>>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Details::new("The quick brown fox jumps over the lazy dog"))
-                .child(Story::label(cx, "With meta"))
-                .child(
-                    Details::new("The quick brown fox jumps over the lazy dog")
-                        .meta_text("Sphinx of black quartz, judge my vow."),
-                )
-                .child(Story::label(cx, "With meta and actions"))
-                .child(
-                    Details::new("The quick brown fox jumps over the lazy dog")
-                        .meta_text("Sphinx of black quartz, judge my vow.")
-                        .actions(ButtonGroup::new(vec![
-                            Button::new("Decline"),
-                            Button::new("Accept").variant(crate::ButtonVariant::Filled),
-                        ])),
-                )
-        }
-    }
-}

crates/ui2/src/components/disclosure.rs 🔗

@@ -0,0 +1,19 @@
+use gpui::{div, Element, ParentElement};
+
+use crate::{Color, Icon, IconElement, IconSize, Toggle};
+
+pub fn disclosure_control(toggle: Toggle) -> impl Element {
+    match (toggle.is_toggleable(), toggle.is_toggled()) {
+        (false, _) => div(),
+        (_, true) => div().child(
+            IconElement::new(Icon::ChevronDown)
+                .color(Color::Muted)
+                .size(IconSize::Small),
+        ),
+        (_, false) => div().child(
+            IconElement::new(Icon::ChevronRight)
+                .color(Color::Muted)
+                .size(IconSize::Small),
+        ),
+    }
+}

crates/ui2/src/components/divider.rs 🔗

@@ -13,10 +13,10 @@ pub struct Divider {
     inset: bool,
 }
 
-impl<V: 'static> Component<V> for Divider {
-    type Rendered = Div<V>;
+impl Component for Divider {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         div()
             .map(|this| match self.direction {
                 DividerDirection::Horizontal => {
@@ -50,7 +50,7 @@ impl Divider {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, cx: &mut WindowContext) -> impl Element {
         div()
             .map(|this| match self.direction {
                 DividerDirection::Horizontal => {

crates/ui2/src/components/elevated_surface.rs 🔗

@@ -1,28 +0,0 @@
-use gpui::Div;
-
-use crate::{prelude::*, v_stack};
-
-/// Create an elevated surface.
-///
-/// Must be used inside of a relative parent element
-pub fn elevated_surface<V: 'static>(level: ElevationIndex, cx: &mut ViewContext<V>) -> Div<V> {
-    let colors = cx.theme().colors();
-
-    // let shadow = BoxShadow {
-    //     color: hsla(0., 0., 0., 0.1),
-    //     offset: point(px(0.), px(1.)),
-    //     blur_radius: px(3.),
-    //     spread_radius: px(0.),
-    // };
-
-    v_stack()
-        .rounded_lg()
-        .bg(colors.elevated_surface_background)
-        .border()
-        .border_color(colors.border)
-        .shadow(level.shadow())
-}
-
-pub fn modal<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
-    elevated_surface(ElevationIndex::ModalSurface, cx)
-}

crates/ui2/src/components/facepile.rs 🔗

@@ -1,64 +0,0 @@
-use crate::prelude::*;
-use crate::{Avatar, Player};
-
-#[derive(RenderOnce)]
-pub struct Facepile {
-    players: Vec<Player>,
-}
-
-impl<V: 'static> Component<V> for Facepile {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let player_count = self.players.len();
-        let player_list = self.players.iter().enumerate().map(|(ix, player)| {
-            let isnt_last = ix < player_count - 1;
-
-            div()
-                .when(isnt_last, |div| div.neg_mr_1())
-                .child(Avatar::new(player.avatar_src().to_string()))
-        });
-        div().p_1().flex().items_center().children(player_list)
-    }
-}
-
-impl Facepile {
-    pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
-        Self {
-            players: players.collect(),
-        }
-    }
-}
-
-use gpui::{Div, RenderOnce};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{static_players, Story};
-    use gpui::{Div, Render};
-
-    pub struct FacepileStory;
-
-    impl Render<Self> for FacepileStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let players = static_players();
-
-            Story::container(cx)
-                .child(Story::title_for::<_, Facepile>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    div()
-                        .flex()
-                        .gap_3()
-                        .child(Facepile::new(players.clone().into_iter().take(1)))
-                        .child(Facepile::new(players.clone().into_iter().take(2)))
-                        .child(Facepile::new(players.clone().into_iter().take(3))),
-                )
-        }
-    }
-}

crates/ui2/src/components/icon.rs 🔗

@@ -23,6 +23,7 @@ pub enum Icon {
     BellOff,
     BellRing,
     Bolt,
+    CaseSensitive,
     Check,
     ChevronDown,
     ChevronLeft,
@@ -65,6 +66,7 @@ pub enum Icon {
     Split,
     SplitMessage,
     Terminal,
+    WholeWord,
     XCircle,
 }
 
@@ -82,6 +84,7 @@ impl Icon {
             Icon::BellOff => "icons/bell-off.svg",
             Icon::BellRing => "icons/bell-ring.svg",
             Icon::Bolt => "icons/bolt.svg",
+            Icon::CaseSensitive => "icons/case_insensitive.svg",
             Icon::Check => "icons/check.svg",
             Icon::ChevronDown => "icons/chevron_down.svg",
             Icon::ChevronLeft => "icons/chevron_left.svg",
@@ -124,6 +127,7 @@ impl Icon {
             Icon::Split => "icons/split.svg",
             Icon::SplitMessage => "icons/split_message.svg",
             Icon::Terminal => "icons/terminal.svg",
+            Icon::WholeWord => "icons/word_search.svg",
             Icon::XCircle => "icons/error.svg",
         }
     }
@@ -132,14 +136,14 @@ impl Icon {
 #[derive(RenderOnce)]
 pub struct IconElement {
     path: SharedString,
-    color: TextColor,
+    color: Color,
     size: IconSize,
 }
 
-impl<V: 'static> Component<V> for IconElement {
-    type Rendered = Svg<V>;
+impl Component for IconElement {
+    type Rendered = Svg;
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let svg_size = match self.size {
             IconSize::Small => rems(0.75),
             IconSize::Medium => rems(0.9375),
@@ -157,7 +161,7 @@ impl IconElement {
     pub fn new(icon: Icon) -> Self {
         Self {
             path: icon.path().into(),
-            color: TextColor::default(),
+            color: Color::default(),
             size: IconSize::default(),
         }
     }
@@ -165,12 +169,12 @@ impl IconElement {
     pub fn from_path(path: impl Into<SharedString>) -> Self {
         Self {
             path: path.into(),
-            color: TextColor::default(),
+            color: Color::default(),
             size: IconSize::default(),
         }
     }
 
-    pub fn color(mut self, color: TextColor) -> Self {
+    pub fn color(mut self, color: Color) -> Self {
         self.color = color;
         self
     }
@@ -180,7 +184,7 @@ impl IconElement {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, cx: &mut WindowContext) -> impl Element {
         let svg_size = match self.size {
             IconSize::Small => rems(0.75),
             IconSize::Medium => rems(0.9375),
@@ -193,31 +197,3 @@ impl IconElement {
             .text_color(self.color.color(cx))
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use gpui::{Div, Render};
-    use strum::IntoEnumIterator;
-
-    use crate::Story;
-
-    use super::*;
-
-    pub struct IconStory;
-
-    impl Render<Self> for IconStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let icons = Icon::iter();
-
-            Story::container(cx)
-                .child(Story::title_for::<_, IconElement>(cx))
-                .child(Story::label(cx, "All Icons"))
-                .child(div().flex().gap_3().children(icons.map(IconElement::new)))
-        }
-    }
-}

crates/ui2/src/components/icon_button.rs 🔗

@@ -1,36 +1,25 @@
-use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement};
-use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful};
-use std::sync::Arc;
-
-struct IconButtonHandlers<V: 'static> {
-    click: Option<ClickHandler<V>>,
-}
-
-impl<V: 'static> Default for IconButtonHandlers<V> {
-    fn default() -> Self {
-        Self { click: None }
-    }
-}
+use crate::{h_stack, prelude::*, Icon, IconElement};
+use gpui::{prelude::*, Action, AnyView, Div, MouseButton, MouseDownEvent, Stateful};
 
 #[derive(RenderOnce)]
-pub struct IconButton<V: 'static> {
+pub struct IconButton {
     id: ElementId,
     icon: Icon,
-    color: TextColor,
+    color: Color,
     variant: ButtonVariant,
     state: InteractionState,
     selected: bool,
-    tooltip: Option<Box<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>>,
-    handlers: IconButtonHandlers<V>,
+    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
+    on_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
 }
 
-impl<V: 'static> Component<V> for IconButton<V> {
-    type Rendered = Stateful<V, Div<V>>;
+impl Component for IconButton {
+    type Rendered = Stateful<Div>;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let icon_color = match (self.state, self.color) {
-            (InteractionState::Disabled, _) => TextColor::Disabled,
-            (InteractionState::Active, _) => TextColor::Selected,
+            (InteractionState::Disabled, _) => Color::Disabled,
+            (InteractionState::Active, _) => Color::Selected,
             _ => self.color,
         };
 
@@ -65,16 +54,16 @@ impl<V: 'static> Component<V> for IconButton<V> {
             .active(|style| style.bg(bg_active_color))
             .child(IconElement::new(self.icon).color(icon_color));
 
-        if let Some(click_handler) = self.handlers.click.clone() {
-            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
+        if let Some(click_handler) = self.on_mouse_down {
+            button = button.on_mouse_down(MouseButton::Left, move |event, cx| {
                 cx.stop_propagation();
-                click_handler(state, cx);
+                click_handler(event, cx);
             })
         }
 
         if let Some(tooltip) = self.tooltip {
             if !self.selected {
-                button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
+                button = button.tooltip(move |cx| tooltip(cx))
             }
         }
 
@@ -82,17 +71,17 @@ impl<V: 'static> Component<V> for IconButton<V> {
     }
 }
 
-impl<V: 'static> IconButton<V> {
+impl IconButton {
     pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
         Self {
             id: id.into(),
             icon,
-            color: TextColor::default(),
+            color: Color::default(),
             variant: ButtonVariant::default(),
             state: InteractionState::default(),
             selected: false,
             tooltip: None,
-            handlers: IconButtonHandlers::default(),
+            on_mouse_down: None,
         }
     }
 
@@ -101,7 +90,7 @@ impl<V: 'static> IconButton<V> {
         self
     }
 
-    pub fn color(mut self, color: TextColor) -> Self {
+    pub fn color(mut self, color: Color) -> Self {
         self.color = color;
         self
     }
@@ -121,16 +110,16 @@ impl<V: 'static> IconButton<V> {
         self
     }
 
-    pub fn tooltip(
-        mut self,
-        tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
-    ) -> Self {
+    pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
         self.tooltip = Some(Box::new(tooltip));
         self
     }
 
-    pub fn on_click(mut self, handler: impl 'static + Fn(&mut V, &mut ViewContext<V>)) -> Self {
-        self.handlers.click = Some(Arc::new(handler));
+    pub fn on_click(
+        mut self,
+        handler: impl 'static + Fn(&MouseDownEvent, &mut WindowContext),
+    ) -> Self {
+        self.on_mouse_down = Some(Box::new(handler));
         self
     }
 

crates/ui2/src/components/indicator.rs 🔗

@@ -1,37 +0,0 @@
-use crate::prelude::*;
-use gpui::{px, Div, RenderOnce};
-
-#[derive(RenderOnce)]
-pub struct UnreadIndicator;
-
-impl<V: 'static> Component<V> for UnreadIndicator {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .rounded_full()
-            .border_2()
-            .border_color(cx.theme().colors().surface_background)
-            .w(px(9.0))
-            .h(px(9.0))
-            .z_index(2)
-            .bg(cx.theme().status().info)
-    }
-}
-
-impl UnreadIndicator {
-    pub fn new() -> Self {
-        Self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div()
-            .rounded_full()
-            .border_2()
-            .border_color(cx.theme().colors().surface_background)
-            .w(px(9.0))
-            .h(px(9.0))
-            .z_index(2)
-            .bg(cx.theme().status().info)
-    }
-}

crates/ui2/src/components/input.rs 🔗

@@ -18,10 +18,10 @@ pub struct Input {
     is_active: bool,
 }
 
-impl<V: 'static> Component<V> for Input {
-    type Rendered = Stateful<V, Div<V>>;
+impl Component for Input {
+    type Rendered = Stateful<Div>;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
             InputVariant::Ghost => (
                 cx.theme().colors().ghost_element_background,
@@ -36,15 +36,15 @@ impl<V: 'static> Component<V> for Input {
         };
 
         let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
-            TextColor::Disabled
+            Color::Disabled
         } else {
-            TextColor::Placeholder
+            Color::Placeholder
         });
 
         let label = Label::new(self.value.clone()).color(if self.disabled {
-            TextColor::Disabled
+            Color::Disabled
         } else {
-            TextColor::Default
+            Color::Default
         });
 
         div()
@@ -106,26 +106,3 @@ impl Input {
         self
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct InputStory;
-
-    impl Render<Self> for InputStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Input>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(div().flex().child(Input::new("Search")))
-        }
-    }
-}

crates/ui2/src/components/keybinding.rs 🔗

@@ -10,10 +10,10 @@ pub struct KeyBinding {
     key_binding: gpui::KeyBinding,
 }
 
-impl<V: 'static> Component<V> for KeyBinding {
-    type Rendered = Div<V>;
+impl Component for KeyBinding {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         div()
             .flex()
             .gap_2()
@@ -49,11 +49,10 @@ pub struct Key {
     key: SharedString,
 }
 
-impl<V: 'static> Component<V> for Key {
-    type Rendered = Div<V>;
+impl Component for Key {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let _view: &mut V = view;
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         div()
             .px_2()
             .py_0()
@@ -70,70 +69,3 @@ impl Key {
         Self { key: key.into() }
     }
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    pub use crate::KeyBinding;
-    use crate::Story;
-    use gpui::{actions, Div, Render};
-    use itertools::Itertools;
-    pub struct KeybindingStory;
-
-    actions!(NoAction);
-
-    pub fn binding(key: &str) -> gpui::KeyBinding {
-        gpui::KeyBinding::new(key, NoAction {}, None)
-    }
-
-    impl Render<Self> for KeybindingStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let all_modifier_permutations =
-                ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
-
-            Story::container(cx)
-                .child(Story::title_for::<_, KeyBinding>(cx))
-                .child(Story::label(cx, "Single Key"))
-                .child(KeyBinding::new(binding("Z")))
-                .child(Story::label(cx, "Single Key with Modifier"))
-                .child(
-                    div()
-                        .flex()
-                        .gap_3()
-                        .child(KeyBinding::new(binding("ctrl-c")))
-                        .child(KeyBinding::new(binding("alt-c")))
-                        .child(KeyBinding::new(binding("cmd-c")))
-                        .child(KeyBinding::new(binding("shift-c"))),
-                )
-                .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
-                .child(
-                    div().flex().flex_col().children(
-                        all_modifier_permutations
-                            .chunks(4)
-                            .into_iter()
-                            .map(|chunk| {
-                                div()
-                                    .flex()
-                                    .gap_4()
-                                    .py_3()
-                                    .children(chunk.map(|permutation| {
-                                        KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
-                                    }))
-                            }),
-                    ),
-                )
-                .child(Story::label(cx, "Single Key with All Modifiers"))
-                .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
-                .child(Story::label(cx, "Chord"))
-                .child(KeyBinding::new(binding("a z")))
-                .child(Story::label(cx, "Chord with Modifier"))
-                .child(KeyBinding::new(binding("ctrl-a shift-z")))
-                .child(KeyBinding::new(binding("fn-s")))
-        }
-    }
-}

crates/ui2/src/components/label.rs 🔗

@@ -9,48 +9,6 @@ pub enum LabelSize {
     Small,
 }
 
-#[derive(Default, PartialEq, Copy, Clone)]
-pub enum TextColor {
-    #[default]
-    Default,
-    Accent,
-    Created,
-    Deleted,
-    Disabled,
-    Error,
-    Hidden,
-    Info,
-    Modified,
-    Muted,
-    Placeholder,
-    Player(u32),
-    Selected,
-    Success,
-    Warning,
-}
-
-impl TextColor {
-    pub fn color(&self, cx: &WindowContext) -> Hsla {
-        match self {
-            TextColor::Default => cx.theme().colors().text,
-            TextColor::Muted => cx.theme().colors().text_muted,
-            TextColor::Created => cx.theme().status().created,
-            TextColor::Modified => cx.theme().status().modified,
-            TextColor::Deleted => cx.theme().status().deleted,
-            TextColor::Disabled => cx.theme().colors().text_disabled,
-            TextColor::Hidden => cx.theme().status().hidden,
-            TextColor::Info => cx.theme().status().info,
-            TextColor::Placeholder => cx.theme().colors().text_placeholder,
-            TextColor::Accent => cx.theme().colors().text_accent,
-            TextColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor,
-            TextColor::Error => cx.theme().status().error,
-            TextColor::Selected => cx.theme().colors().text_accent,
-            TextColor::Success => cx.theme().status().success,
-            TextColor::Warning => cx.theme().status().warning,
-        }
-    }
-}
-
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum LineHeightStyle {
     #[default]
@@ -64,14 +22,14 @@ pub struct Label {
     label: SharedString,
     size: LabelSize,
     line_height_style: LineHeightStyle,
-    color: TextColor,
+    color: Color,
     strikethrough: bool,
 }
 
-impl<V: 'static> Component<V> for Label {
-    type Rendered = Div<V>;
+impl Component for Label {
+    type Rendered = Div;
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         div()
             .when(self.strikethrough, |this| {
                 this.relative().child(
@@ -80,7 +38,7 @@ impl<V: 'static> Component<V> for Label {
                         .top_1_2()
                         .w_full()
                         .h_px()
-                        .bg(TextColor::Hidden.color(cx)),
+                        .bg(Color::Hidden.color(cx)),
                 )
             })
             .map(|this| match self.size {
@@ -101,7 +59,7 @@ impl Label {
             label: label.into(),
             size: LabelSize::Default,
             line_height_style: LineHeightStyle::default(),
-            color: TextColor::Default,
+            color: Color::Default,
             strikethrough: false,
         }
     }
@@ -111,7 +69,7 @@ impl Label {
         self
     }
 
-    pub fn color(mut self, color: TextColor) -> Self {
+    pub fn color(mut self, color: Color) -> Self {
         self.color = color;
         self
     }
@@ -131,15 +89,15 @@ impl Label {
 pub struct HighlightedLabel {
     label: SharedString,
     size: LabelSize,
-    color: TextColor,
+    color: Color,
     highlight_indices: Vec<usize>,
     strikethrough: bool,
 }
 
-impl<V: 'static> Component<V> for HighlightedLabel {
-    type Rendered = Div<V>;
+impl Component for HighlightedLabel {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let highlight_color = cx.theme().colors().text_accent;
         let mut text_style = cx.text_style().clone();
 
@@ -185,7 +143,7 @@ impl<V: 'static> Component<V> for HighlightedLabel {
                         .my_auto()
                         .w_full()
                         .h_px()
-                        .bg(TextColor::Hidden.color(cx)),
+                        .bg(Color::Hidden.color(cx)),
                 )
             })
             .map(|this| match self.size {
@@ -203,7 +161,7 @@ impl HighlightedLabel {
         Self {
             label: label.into(),
             size: LabelSize::Default,
-            color: TextColor::Default,
+            color: Color::Default,
             highlight_indices,
             strikethrough: false,
         }
@@ -214,7 +172,7 @@ impl HighlightedLabel {
         self
     }
 
-    pub fn color(mut self, color: TextColor) -> Self {
+    pub fn color(mut self, color: Color) -> Self {
         self.color = color;
         self
     }
@@ -230,35 +188,3 @@ struct Run {
     pub text: String,
     pub color: Hsla,
 }
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct LabelStory;
-
-    impl Render<Self> for LabelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Label>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Label::new("Hello, world!"))
-                .child(Story::label(cx, "Highlighted"))
-                .child(HighlightedLabel::new(
-                    "Hello, world!",
-                    vec![0, 1, 2, 7, 8, 12],
-                ))
-                .child(HighlightedLabel::new(
-                    "Héllo, world!",
-                    vec![0, 1, 3, 8, 9, 13],
-                ))
-        }
-    }
-}

crates/ui2/src/components/list.rs 🔗

@@ -1,8 +1,9 @@
-use gpui::{div, AnyElement, Div, RenderOnce, Stateful, StatefulInteractiveElement};
+use gpui::{
+    div, px, AnyElement, ClickEvent, Div, RenderOnce, Stateful, StatefulInteractiveElement,
+};
 use smallvec::SmallVec;
 use std::rc::Rc;
 
-use crate::settings::user_settings;
 use crate::{
     disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
 };
@@ -33,10 +34,10 @@ pub struct ListHeader {
     toggle: Toggle,
 }
 
-impl<V: 'static> Component<V> for ListHeader {
-    type Rendered = Div<V>;
+impl Component for ListHeader {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let disclosure_control = disclosure_control(self.toggle);
 
         let meta = match self.meta {
@@ -46,7 +47,7 @@ impl<V: 'static> Component<V> for ListHeader {
                     .items_center()
                     .children(icons.into_iter().map(|i| {
                         IconElement::new(i)
-                            .color(TextColor::Muted)
+                            .color(Color::Muted)
                             .size(IconSize::Small)
                     })),
             ),
@@ -79,10 +80,10 @@ impl<V: 'static> Component<V> for ListHeader {
                                     .items_center()
                                     .children(self.left_icon.map(|i| {
                                         IconElement::new(i)
-                                            .color(TextColor::Muted)
+                                            .color(Color::Muted)
                                             .size(IconSize::Small)
                                     }))
-                                    .child(Label::new(self.label.clone()).color(TextColor::Muted)),
+                                    .child(Label::new(self.label.clone()).color(Color::Muted)),
                             )
                             .child(disclosure_control),
                     )
@@ -118,7 +119,7 @@ impl ListHeader {
     }
 
     // before_ship!("delete")
-    // fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    // fn render<V: 'static>(self,  cx: &mut WindowContext) -> impl Element<V> {
     //     let disclosure_control = disclosure_control(self.toggle);
 
     //     let meta = match self.meta {
@@ -200,10 +201,10 @@ impl ListSubHeader {
     }
 }
 
-impl<V: 'static> Component<V> for ListSubHeader {
-    type Rendered = Div<V>;
+impl Component for ListSubHeader {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         h_stack().flex_1().w_full().relative().py_1().child(
             div()
                 .h_6()
@@ -221,10 +222,10 @@ impl<V: 'static> Component<V> for ListSubHeader {
                         .items_center()
                         .children(self.left_icon.map(|i| {
                             IconElement::new(i)
-                                .color(TextColor::Muted)
+                                .color(Color::Muted)
                                 .size(IconSize::Small)
                         }))
-                        .child(Label::new(self.label.clone()).color(TextColor::Muted)),
+                        .child(Label::new(self.label.clone()).color(Color::Muted)),
                 ),
         )
     }
@@ -238,55 +239,38 @@ pub enum ListEntrySize {
 }
 
 #[derive(RenderOnce)]
-pub struct ListEntry<V: 'static> {
+pub struct ListItem {
     id: ElementId,
     disabled: bool,
     // TODO: Reintroduce this
     // disclosure_control_style: DisclosureControlVisibility,
     indent_level: u32,
-    label: Label,
     left_slot: Option<GraphicSlot>,
     overflow: OverflowStyle,
     size: ListEntrySize,
     toggle: Toggle,
     variant: ListItemVariant,
-    on_click: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) + 'static>>,
+    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
+    children: SmallVec<[AnyElement; 2]>,
 }
 
-impl<V> Clone for ListEntry<V> {
-    fn clone(&self) -> Self {
-        Self {
-            id: self.id.clone(),
-            disabled: self.disabled,
-            indent_level: self.indent_level,
-            label: self.label.clone(),
-            left_slot: self.left_slot.clone(),
-            overflow: self.overflow,
-            size: self.size,
-            toggle: self.toggle,
-            variant: self.variant,
-            on_click: self.on_click.clone(),
-        }
-    }
-}
-
-impl<V: 'static> ListEntry<V> {
-    pub fn new(id: impl Into<ElementId>, label: Label) -> Self {
+impl ListItem {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
             id: id.into(),
             disabled: false,
             indent_level: 0,
-            label,
             left_slot: None,
             overflow: OverflowStyle::Hidden,
             size: ListEntrySize::default(),
             toggle: Toggle::NotToggleable,
             variant: ListItemVariant::default(),
             on_click: Default::default(),
+            children: SmallVec::new(),
         }
     }
 
-    pub fn on_click(mut self, handler: impl Fn(&mut V, &mut ViewContext<V>) + 'static) -> Self {
+    pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
         self.on_click = Some(Rc::new(handler));
         self
     }
@@ -327,18 +311,16 @@ impl<V: 'static> ListEntry<V> {
     }
 }
 
-impl<V: 'static> Component<V> for ListEntry<V> {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let settings = user_settings(cx);
+impl Component for ListItem {
+    type Rendered = Stateful<Div>;
 
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let left_content = match self.left_slot.clone() {
             Some(GraphicSlot::Icon(i)) => Some(
                 h_stack().child(
                     IconElement::new(i)
                         .size(IconSize::Small)
-                        .color(TextColor::Muted),
+                        .color(Color::Muted),
                 ),
             ),
             Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
@@ -359,26 +341,26 @@ impl<V: 'static> Component<V> for ListEntry<V> {
             })
             .on_click({
                 let on_click = self.on_click.clone();
-
-                move |view: &mut V, event, cx| {
+                move |event, cx| {
                     if let Some(on_click) = &on_click {
-                        (on_click)(view, cx)
+                        (on_click)(event, cx)
                     }
                 }
             })
-            .bg(cx.theme().colors().surface_background)
             // TODO: Add focus state
             // .when(self.state == InteractionState::Focused, |this| {
             //     this.border()
             //         .border_color(cx.theme().colors().border_focused)
             // })
+            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
+            .active(|style| style.bg(cx.theme().colors().ghost_element_active))
             .child(
                 sized_item
                     .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
                     // .ml(rems(0.75 * self.indent_level as f32))
                     .children((0..self.indent_level).map(|_| {
                         div()
-                            .w(*settings.list_indent_depth)
+                            .w(px(4.))
                             .h_full()
                             .flex()
                             .justify_center()
@@ -395,11 +377,17 @@ impl<V: 'static> Component<V> for ListEntry<V> {
                     .relative()
                     .child(disclosure_control(self.toggle))
                     .children(left_content)
-                    .child(self.label),
+                    .children(self.children),
             )
     }
 }
 
+impl ParentElement for ListItem {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
 #[derive(RenderOnce, Clone)]
 pub struct ListSeparator;
 
@@ -409,34 +397,32 @@ impl ListSeparator {
     }
 }
 
-impl<V: 'static> Component<V> for ListSeparator {
-    type Rendered = Div<V>;
+impl Component for ListSeparator {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         div().h_px().w_full().bg(cx.theme().colors().border_variant)
     }
 }
 
 #[derive(RenderOnce)]
-pub struct List<V: 'static> {
+pub struct List {
     /// Message to display when the list is empty
     /// Defaults to "No items"
     empty_message: SharedString,
     header: Option<ListHeader>,
     toggle: Toggle,
-    children: SmallVec<[AnyElement<V>; 2]>,
+    children: SmallVec<[AnyElement; 2]>,
 }
 
-impl<V: 'static> Component<V> for List<V> {
-    type Rendered = Div<V>;
+impl Component for List {
+    type Rendered = Div;
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let list_content = match (self.children.is_empty(), self.toggle) {
             (false, _) => div().children(self.children),
             (true, Toggle::Toggled(false)) => div(),
-            (true, _) => {
-                div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
-            }
+            (true, _) => div().child(Label::new(self.empty_message.clone()).color(Color::Muted)),
         };
 
         v_stack()
@@ -447,7 +433,7 @@ impl<V: 'static> Component<V> for List<V> {
     }
 }
 
-impl<V: 'static> List<V> {
+impl List {
     pub fn new() -> Self {
         Self {
             empty_message: "No items".into(),
@@ -473,8 +459,8 @@ impl<V: 'static> List<V> {
     }
 }
 
-impl<V: 'static> ParentElement<V> for List<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+impl ParentElement for List {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
         &mut self.children
     }
 }

crates/ui2/src/components/modal.rs 🔗

@@ -1,86 +0,0 @@
-use gpui::{AnyElement, Div, RenderOnce, Stateful};
-use smallvec::SmallVec;
-
-use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
-
-#[derive(RenderOnce)]
-pub struct Modal<V: 'static> {
-    id: ElementId,
-    title: Option<SharedString>,
-    primary_action: Option<Button<V>>,
-    secondary_action: Option<Button<V>>,
-    children: SmallVec<[AnyElement<V>; 2]>,
-}
-
-impl<V: 'static> Component<V> for Modal<V> {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let _view: &mut V = view;
-        v_stack()
-            .id(self.id.clone())
-            .w_96()
-            // .rounded_xl()
-            .bg(cx.theme().colors().background)
-            .border()
-            .border_color(cx.theme().colors().border)
-            .shadow_2xl()
-            .child(
-                h_stack()
-                    .justify_between()
-                    .p_1()
-                    .border_b()
-                    .border_color(cx.theme().colors().border)
-                    .child(div().children(self.title.clone().map(|t| Label::new(t))))
-                    .child(IconButton::new("close", Icon::Close)),
-            )
-            .child(v_stack().p_1().children(self.children))
-            .when(
-                self.primary_action.is_some() || self.secondary_action.is_some(),
-                |this| {
-                    this.child(
-                        h_stack()
-                            .border_t()
-                            .border_color(cx.theme().colors().border)
-                            .p_1()
-                            .justify_end()
-                            .children(self.secondary_action)
-                            .children(self.primary_action),
-                    )
-                },
-            )
-    }
-}
-
-impl<V: 'static> Modal<V> {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            title: None,
-            primary_action: None,
-            secondary_action: None,
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
-        self.title = Some(title.into());
-        self
-    }
-
-    pub fn primary_action(mut self, action: Button<V>) -> Self {
-        self.primary_action = Some(action);
-        self
-    }
-
-    pub fn secondary_action(mut self, action: Button<V>) -> Self {
-        self.secondary_action = Some(action);
-        self
-    }
-}
-
-impl<V: 'static> ParentElement<V> for Modal<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
-        &mut self.children
-    }
-}

crates/ui2/src/components/notification_toast.rs 🔗

@@ -1,40 +0,0 @@
-use gpui::rems;
-
-use crate::prelude::*;
-use crate::{h_stack, Icon};
-
-// #[derive(RenderOnce)]
-pub struct NotificationToast {
-    label: SharedString,
-    icon: Option<Icon>,
-}
-
-impl NotificationToast {
-    pub fn new(label: SharedString) -> Self {
-        Self { label, icon: None }
-    }
-
-    pub fn icon<I>(mut self, icon: I) -> Self
-    where
-        I: Into<Option<Icon>>,
-    {
-        self.icon = icon.into();
-        self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        h_stack()
-            .z_index(5)
-            .absolute()
-            .top_1()
-            .right_1()
-            .w(rems(9999.))
-            .max_w_56()
-            .py_1()
-            .px_1p5()
-            .rounded_lg()
-            .shadow_md()
-            .bg(cx.theme().colors().elevated_surface_background)
-            .child(div().size_full().child(self.label.clone()))
-    }
-}

crates/ui2/src/components/palette.rs 🔗

@@ -1,213 +0,0 @@
-use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
-use gpui::prelude::*;
-use gpui::Div;
-use gpui::Stateful;
-
-#[derive(RenderOnce)]
-pub struct Palette {
-    id: ElementId,
-    input_placeholder: SharedString,
-    empty_string: SharedString,
-    items: Vec<PaletteItem>,
-    default_order: OrderMethod,
-}
-
-impl<V: 'static> Component<V> for Palette {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        v_stack()
-            .id(self.id)
-            .w_96()
-            .rounded_lg()
-            .bg(cx.theme().colors().elevated_surface_background)
-            .border()
-            .border_color(cx.theme().colors().border)
-            .child(
-                v_stack()
-                    .gap_px()
-                    .child(v_stack().py_0p5().px_1().child(
-                        div().px_2().py_0p5().child(
-                            Label::new(self.input_placeholder).color(TextColor::Placeholder),
-                        ),
-                    ))
-                    .child(
-                        div()
-                            .h_px()
-                            .w_full()
-                            .bg(cx.theme().colors().element_background),
-                    )
-                    .child(
-                        v_stack()
-                            .id("items")
-                            .py_0p5()
-                            .px_1()
-                            .grow()
-                            .max_h_96()
-                            .overflow_y_scroll()
-                            .children(
-                                vec![if self.items.is_empty() {
-                                    Some(h_stack().justify_between().px_2().py_1().child(
-                                        Label::new(self.empty_string).color(TextColor::Muted),
-                                    ))
-                                } else {
-                                    None
-                                }]
-                                .into_iter()
-                                .flatten(),
-                            )
-                            .children(self.items.into_iter().enumerate().map(|(index, item)| {
-                                h_stack()
-                                    .id(index)
-                                    .justify_between()
-                                    .px_2()
-                                    .py_0p5()
-                                    .rounded_lg()
-                                    .hover(|style| {
-                                        style.bg(cx.theme().colors().ghost_element_hover)
-                                    })
-                                    .active(|style| {
-                                        style.bg(cx.theme().colors().ghost_element_active)
-                                    })
-                                    .child(item)
-                            })),
-                    ),
-            )
-    }
-}
-
-impl Palette {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            input_placeholder: "Find something...".into(),
-            empty_string: "No items found.".into(),
-            items: vec![],
-            default_order: OrderMethod::default(),
-        }
-    }
-
-    pub fn items(mut self, items: Vec<PaletteItem>) -> Self {
-        self.items = items;
-        self
-    }
-
-    pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
-        self.input_placeholder = input_placeholder.into();
-        self
-    }
-
-    pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
-        self.empty_string = empty_string.into();
-        self
-    }
-
-    // TODO: Hook up sort order
-    pub fn default_order(mut self, default_order: OrderMethod) -> Self {
-        self.default_order = default_order;
-        self
-    }
-}
-
-#[derive(RenderOnce)]
-pub struct PaletteItem {
-    pub label: SharedString,
-    pub sublabel: Option<SharedString>,
-    pub key_binding: Option<KeyBinding>,
-}
-
-impl<V: 'static> Component<V> for PaletteItem {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .flex()
-            .flex_row()
-            .grow()
-            .justify_between()
-            .child(
-                v_stack()
-                    .child(Label::new(self.label))
-                    .children(self.sublabel.map(|sublabel| Label::new(sublabel))),
-            )
-            .children(self.key_binding)
-    }
-}
-
-impl PaletteItem {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            label: label.into(),
-            sublabel: None,
-            key_binding: None,
-        }
-    }
-
-    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
-        self.label = label.into();
-        self
-    }
-
-    pub fn sublabel(mut self, sublabel: impl Into<Option<SharedString>>) -> Self {
-        self.sublabel = sublabel.into();
-        self
-    }
-
-    pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
-        self.key_binding = key_binding.into();
-        self
-    }
-}
-
-use gpui::ElementId;
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use gpui::{Div, Render};
-
-    use crate::{binding, Story};
-
-    use super::*;
-
-    pub struct PaletteStory;
-
-    impl Render<Self> for PaletteStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            {
-                Story::container(cx)
-                    .child(Story::title_for::<_, Palette>(cx))
-                    .child(Story::label(cx, "Default"))
-                    .child(Palette::new("palette-1"))
-                    .child(Story::label(cx, "With Items"))
-                    .child(
-                        Palette::new("palette-2")
-                            .placeholder("Execute a command...")
-                            .items(vec![
-                                PaletteItem::new("theme selector: toggle")
-                                    .key_binding(KeyBinding::new(binding("cmd-k cmd-t"))),
-                                PaletteItem::new("assistant: inline assist")
-                                    .key_binding(KeyBinding::new(binding("cmd-enter"))),
-                                PaletteItem::new("assistant: quote selection")
-                                    .key_binding(KeyBinding::new(binding("cmd-<"))),
-                                PaletteItem::new("assistant: toggle focus")
-                                    .key_binding(KeyBinding::new(binding("cmd-?"))),
-                                PaletteItem::new("auto update: check"),
-                                PaletteItem::new("auto update: view release notes"),
-                                PaletteItem::new("branches: open recent")
-                                    .key_binding(KeyBinding::new(binding("cmd-alt-b"))),
-                                PaletteItem::new("chat panel: toggle focus"),
-                                PaletteItem::new("cli: install"),
-                                PaletteItem::new("client: sign in"),
-                                PaletteItem::new("client: sign out"),
-                                PaletteItem::new("editor: cancel")
-                                    .key_binding(KeyBinding::new(binding("escape"))),
-                            ]),
-                    )
-            }
-        }
-    }
-}

crates/ui2/src/components/panel.rs 🔗

@@ -1,154 +0,0 @@
-use gpui::{prelude::*, AbsoluteLength, AnyElement, Div, RenderOnce, Stateful};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-use crate::settings::user_settings;
-use crate::v_stack;
-
-#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
-pub enum PanelAllowedSides {
-    LeftOnly,
-    RightOnly,
-    BottomOnly,
-    #[default]
-    LeftAndRight,
-    All,
-}
-
-impl PanelAllowedSides {
-    /// Return a `HashSet` that contains the allowable `PanelSide`s.
-    pub fn allowed_sides(&self) -> HashSet<PanelSide> {
-        match self {
-            Self::LeftOnly => HashSet::from_iter([PanelSide::Left]),
-            Self::RightOnly => HashSet::from_iter([PanelSide::Right]),
-            Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]),
-            Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]),
-            Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]),
-        }
-    }
-}
-
-#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
-pub enum PanelSide {
-    #[default]
-    Left,
-    Right,
-    Bottom,
-}
-
-use std::collections::HashSet;
-
-#[derive(RenderOnce)]
-pub struct Panel<V: 'static> {
-    id: ElementId,
-    current_side: PanelSide,
-    /// Defaults to PanelAllowedSides::LeftAndRight
-    allowed_sides: PanelAllowedSides,
-    initial_width: AbsoluteLength,
-    width: Option<AbsoluteLength>,
-    children: SmallVec<[AnyElement<V>; 2]>,
-}
-
-impl<V: 'static> Component<V> for Panel<V> {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let current_size = self.width.unwrap_or(self.initial_width);
-
-        v_stack()
-            .id(self.id.clone())
-            .flex_initial()
-            .map(|this| match self.current_side {
-                PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
-                PanelSide::Bottom => this,
-            })
-            .map(|this| match self.current_side {
-                PanelSide::Left => this.border_r(),
-                PanelSide::Right => this.border_l(),
-                PanelSide::Bottom => this.border_b().w_full().h(current_size),
-            })
-            .bg(cx.theme().colors().surface_background)
-            .border_color(cx.theme().colors().border)
-            .children(self.children)
-    }
-}
-
-impl<V: 'static> Panel<V> {
-    pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
-        let settings = user_settings(cx);
-
-        Self {
-            id: id.into(),
-            current_side: PanelSide::default(),
-            allowed_sides: PanelAllowedSides::default(),
-            initial_width: *settings.default_panel_size,
-            width: None,
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self {
-        self.initial_width = initial_width;
-        self
-    }
-
-    pub fn width(mut self, width: AbsoluteLength) -> Self {
-        self.width = Some(width);
-        self
-    }
-
-    pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self {
-        self.allowed_sides = allowed_sides;
-        self
-    }
-
-    pub fn side(mut self, side: PanelSide) -> Self {
-        let allowed_sides = self.allowed_sides.allowed_sides();
-
-        if allowed_sides.contains(&side) {
-            self.current_side = side;
-        } else {
-            panic!(
-                "The panel side {:?} was not added as allowed before it was set.",
-                side
-            );
-        }
-        self
-    }
-}
-
-impl<V: 'static> ParentElement<V> for Panel<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
-        &mut self.children
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{Label, Story};
-    use gpui::{Div, InteractiveElement, Render};
-
-    pub struct PanelStory;
-
-    impl Render<Self> for PanelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Panel<Self>>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    Panel::new("panel", cx).child(
-                        div()
-                            .id("panel-contents")
-                            .overflow_y_scroll()
-                            .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))),
-                    ),
-                )
-        }
-    }
-}

crates/ui2/src/components/player.rs 🔗

@@ -1,174 +0,0 @@
-use gpui::{Hsla, ViewContext};
-
-use crate::prelude::*;
-
-/// Represents a person with a Zed account's public profile.
-/// All data in this struct should be considered public.
-pub struct PublicPlayer {
-    pub username: SharedString,
-    pub avatar: SharedString,
-    pub is_contact: bool,
-}
-
-impl PublicPlayer {
-    pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
-        Self {
-            username: username.into(),
-            avatar: avatar.into(),
-            is_contact: false,
-        }
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
-pub enum PlayerStatus {
-    #[default]
-    Offline,
-    Online,
-    InCall,
-    Away,
-    DoNotDisturb,
-    Invisible,
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
-pub enum MicStatus {
-    Muted,
-    #[default]
-    Unmuted,
-}
-
-impl MicStatus {
-    pub fn inverse(&self) -> Self {
-        match self {
-            Self::Muted => Self::Unmuted,
-            Self::Unmuted => Self::Muted,
-        }
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
-pub enum VideoStatus {
-    On,
-    #[default]
-    Off,
-}
-
-impl VideoStatus {
-    pub fn inverse(&self) -> Self {
-        match self {
-            Self::On => Self::Off,
-            Self::Off => Self::On,
-        }
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
-pub enum ScreenShareStatus {
-    Shared,
-    #[default]
-    NotShared,
-}
-
-impl ScreenShareStatus {
-    pub fn inverse(&self) -> Self {
-        match self {
-            Self::Shared => Self::NotShared,
-            Self::NotShared => Self::Shared,
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct PlayerCallStatus {
-    pub mic_status: MicStatus,
-    /// Indicates if the player is currently speaking
-    /// And the intensity of the volume coming through
-    ///
-    /// 0.0 - 1.0
-    pub voice_activity: f32,
-    pub video_status: VideoStatus,
-    pub screen_share_status: ScreenShareStatus,
-    pub in_current_project: bool,
-    pub disconnected: bool,
-    pub following: Option<Vec<Player>>,
-    pub followers: Option<Vec<Player>>,
-}
-
-impl PlayerCallStatus {
-    pub fn new() -> Self {
-        Self {
-            mic_status: MicStatus::default(),
-            voice_activity: 0.,
-            video_status: VideoStatus::default(),
-            screen_share_status: ScreenShareStatus::default(),
-            in_current_project: true,
-            disconnected: false,
-            following: None,
-            followers: None,
-        }
-    }
-}
-
-#[derive(PartialEq, Clone)]
-pub struct Player {
-    index: usize,
-    avatar_src: String,
-    username: String,
-    status: PlayerStatus,
-}
-
-#[derive(Clone)]
-pub struct PlayerWithCallStatus {
-    player: Player,
-    call_status: PlayerCallStatus,
-}
-
-impl PlayerWithCallStatus {
-    pub fn new(player: Player, call_status: PlayerCallStatus) -> Self {
-        Self {
-            player,
-            call_status,
-        }
-    }
-
-    pub fn get_player(&self) -> &Player {
-        &self.player
-    }
-
-    pub fn get_call_status(&self) -> &PlayerCallStatus {
-        &self.call_status
-    }
-}
-
-impl Player {
-    pub fn new(index: usize, avatar_src: String, username: String) -> Self {
-        Self {
-            index,
-            avatar_src,
-            username,
-            status: Default::default(),
-        }
-    }
-
-    pub fn set_status(mut self, status: PlayerStatus) -> Self {
-        self.status = status;
-        self
-    }
-
-    pub fn cursor_color<V: 'static>(&self, cx: &mut ViewContext<V>) -> Hsla {
-        cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].cursor
-    }
-
-    pub fn selection_color<V: 'static>(&self, cx: &mut ViewContext<V>) -> Hsla {
-        cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].selection
-    }
-
-    pub fn avatar_src(&self) -> &str {
-        &self.avatar_src
-    }
-
-    pub fn index(&self) -> usize {
-        self.index
-    }
-}

crates/ui2/src/components/player_stack.rs 🔗

@@ -1,67 +0,0 @@
-use gpui::{Div, RenderOnce};
-
-use crate::prelude::*;
-use crate::{Avatar, Facepile, PlayerWithCallStatus};
-
-#[derive(RenderOnce)]
-pub struct PlayerStack {
-    player_with_call_status: PlayerWithCallStatus,
-}
-
-impl<V: 'static> Component<V> for PlayerStack {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let player = self.player_with_call_status.get_player();
-
-        let followers = self
-            .player_with_call_status
-            .get_call_status()
-            .followers
-            .as_ref()
-            .map(|followers| followers.clone());
-
-        // if we have no followers return a slightly different element
-        // if mic_status == muted add a red ring to avatar
-
-        div()
-            .h_full()
-            .flex()
-            .flex_col()
-            .gap_px()
-            .justify_center()
-            .child(
-                div()
-                    .flex()
-                    .justify_center()
-                    .w_full()
-                    .child(div().w_4().h_0p5().rounded_sm().bg(player.cursor_color(cx))),
-            )
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .justify_center()
-                    .h_6()
-                    .pl_1()
-                    .rounded_lg()
-                    .bg(if followers.is_none() {
-                        cx.theme().styles.system.transparent
-                    } else {
-                        player.selection_color(cx)
-                    })
-                    .child(Avatar::new(player.avatar_src().to_string()))
-                    .children(followers.map(|followers| {
-                        div().neg_ml_2().child(Facepile::new(followers.into_iter()))
-                    })),
-            )
-    }
-}
-
-impl PlayerStack {
-    pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
-        Self {
-            player_with_call_status,
-        }
-    }
-}

crates/ui2/src/components/popover.rs 🔗

@@ -0,0 +1,89 @@
+use gpui::{
+    AnyElement, Component, Div, Element, ElementId, ParentElement, RenderOnce, Styled,
+    WindowContext,
+};
+use smallvec::SmallVec;
+
+use crate::{v_stack, StyledExt};
+
+/// A popover is used to display a menu or show some options.
+///
+/// Clicking the element that launches the popover should not change the current view,
+/// and the popover should be statically positioned relative to that element (not the
+/// user's mouse.)
+///
+/// Example: A "new" menu with options like "new file", "new folder", etc,
+/// Linear's "Display" menu, a profile menu that appers when you click your avatar.
+///
+/// Related elements:
+///
+/// `ContextMenu`:
+///
+/// Used to display a popover menu that only contains a list of items. Context menus are always
+/// launched by secondary clicking on an element. The menu is positioned relative to the user's cursor.
+///
+/// Example: Right clicking a file in the file tree to get a list of actions, right clicking
+/// a tab to in the tab bar to get a list of actions.
+///
+/// `Dropdown`:
+///
+/// Used to display a list of options when the user clicks an element. The menu is
+/// positioned relative the element that was clicked, and clicking an item in the
+/// dropdown should change the value of the element that was clicked.
+///
+/// Example: A theme select control. Displays "One Dark", clicking it opens a list of themes.
+/// When one is selected, the theme select control displays the selected theme.
+#[derive(RenderOnce)]
+pub struct Popover {
+    children: SmallVec<[AnyElement; 2]>,
+    aside: Option<AnyElement>,
+}
+
+impl Component for Popover {
+    type Rendered = Div;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        v_stack()
+            .relative()
+            .elevation_2(cx)
+            .p_1()
+            .children(self.children)
+            .when_some(self.aside, |this, aside| {
+                // TODO: This will statically position the aside to the top right of the popover.
+                // We should update this to use gpui2::overlay avoid collisions with the window edges.
+                this.child(
+                    v_stack()
+                        .top_0()
+                        .left_full()
+                        .ml_1()
+                        .absolute()
+                        .elevation_2(cx)
+                        .p_1()
+                        .child(aside),
+                )
+            })
+    }
+}
+
+impl Popover {
+    pub fn new() -> Self {
+        Self {
+            children: SmallVec::new(),
+            aside: None,
+        }
+    }
+
+    pub fn aside(mut self, aside: impl RenderOnce) -> Self
+    where
+        Self: Sized,
+    {
+        self.aside = Some(aside.render_once().into_any());
+        self
+    }
+}
+
+impl ParentElement for Popover {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}

crates/ui2/src/components/stack.rs 🔗

@@ -5,13 +5,13 @@ use crate::StyledExt;
 /// Horizontally stacks elements.
 ///
 /// Sets `flex()`, `flex_row()`, `items_center()`
-pub fn h_stack<V: 'static>() -> Div<V> {
+pub fn h_stack() -> Div {
     div().h_flex()
 }
 
 /// Vertically stacks elements.
 ///
 /// Sets `flex()`, `flex_col()`
-pub fn v_stack<V: 'static>() -> Div<V> {
+pub fn v_stack() -> Div {
     div().v_flex()
 }

crates/ui2/src/components/stories.rs 🔗

@@ -0,0 +1,17 @@
+mod avatar;
+mod button;
+mod checkbox;
+mod context_menu;
+mod icon;
+mod input;
+mod keybinding;
+mod label;
+
+pub use avatar::*;
+pub use button::*;
+pub use checkbox::*;
+pub use context_menu::*;
+pub use icon::*;
+pub use input::*;
+pub use keybinding::*;
+pub use label::*;

crates/ui2/src/components/stories/avatar.rs 🔗

@@ -0,0 +1,23 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::prelude::*;
+use crate::Avatar;
+
+pub struct AvatarStory;
+
+impl Render for AvatarStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<Avatar>())
+            .child(Story::label("Default"))
+            .child(Avatar::new(
+                "https://avatars.githubusercontent.com/u/1714999?v=4",
+            ))
+            .child(Avatar::new(
+                "https://avatars.githubusercontent.com/u/326587?v=4",
+            ))
+    }
+}

crates/ui2/src/components/stories/button.rs 🔗

@@ -0,0 +1,145 @@
+use gpui::{rems, Div, Render};
+use story::Story;
+use strum::IntoEnumIterator;
+
+use crate::prelude::*;
+use crate::{h_stack, v_stack, Button, Icon, IconPosition, Label};
+
+pub struct ButtonStory;
+
+impl Render for ButtonStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let states = InteractionState::iter();
+
+        Story::container()
+            .child(Story::title_for::<Button>())
+            .child(
+                div()
+                    .flex()
+                    .gap_8()
+                    .child(
+                        div()
+                            .child(Story::label("Ghost (Default)"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
+                                    )
+                            })))
+                            .child(Story::label("Ghost – Left Icon"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Ghost)
+                                            .icon(Icon::Plus)
+                                            .icon_position(IconPosition::Left), // .state(state),
+                                    )
+                            })))
+                            .child(Story::label("Ghost – Right Icon"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Ghost)
+                                            .icon(Icon::Plus)
+                                            .icon_position(IconPosition::Right), // .state(state),
+                                    )
+                            }))),
+                    )
+                    .child(
+                        div()
+                            .child(Story::label("Filled"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
+                                    )
+                            })))
+                            .child(Story::label("Filled – Left Button"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Filled)
+                                            .icon(Icon::Plus)
+                                            .icon_position(IconPosition::Left), // .state(state),
+                                    )
+                            })))
+                            .child(Story::label("Filled – Right Button"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Filled)
+                                            .icon(Icon::Plus)
+                                            .icon_position(IconPosition::Right), // .state(state),
+                                    )
+                            }))),
+                    )
+                    .child(
+                        div()
+                            .child(Story::label("Fixed With"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Filled)
+                                            // .state(state)
+                                            .width(Some(rems(6.).into())),
+                                    )
+                            })))
+                            .child(Story::label("Fixed With – Left Icon"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Filled)
+                                            // .state(state)
+                                            .icon(Icon::Plus)
+                                            .icon_position(IconPosition::Left)
+                                            .width(Some(rems(6.).into())),
+                                    )
+                            })))
+                            .child(Story::label("Fixed With – Right Icon"))
+                            .child(h_stack().gap_2().children(states.clone().map(|state| {
+                                v_stack()
+                                    .gap_1()
+                                    .child(Label::new(state.to_string()).color(Color::Muted))
+                                    .child(
+                                        Button::new("Label")
+                                            .variant(ButtonVariant::Filled)
+                                            // .state(state)
+                                            .icon(Icon::Plus)
+                                            .icon_position(IconPosition::Right)
+                                            .width(Some(rems(6.).into())),
+                                    )
+                            }))),
+                    ),
+            )
+            .child(Story::label("Button with `on_click`"))
+            .child(
+                Button::new("Label")
+                    .variant(ButtonVariant::Ghost)
+                    .on_click(|_, cx| println!("Button clicked.")),
+            )
+    }
+}

crates/ui2/src/components/stories/checkbox.rs 🔗

@@ -0,0 +1,49 @@
+use gpui::{Div, Render, ViewContext};
+use story::Story;
+
+use crate::prelude::*;
+use crate::{h_stack, Checkbox};
+
+pub struct CheckboxStory;
+
+impl Render for CheckboxStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<Checkbox>())
+            .child(Story::label("Default"))
+            .child(
+                h_stack()
+                    .p_2()
+                    .gap_2()
+                    .rounded_md()
+                    .border()
+                    .border_color(cx.theme().colors().border)
+                    .child(Checkbox::new("checkbox-enabled", Selection::Unselected))
+                    .child(Checkbox::new(
+                        "checkbox-intermediate",
+                        Selection::Indeterminate,
+                    ))
+                    .child(Checkbox::new("checkbox-selected", Selection::Selected)),
+            )
+            .child(Story::label("Disabled"))
+            .child(
+                h_stack()
+                    .p_2()
+                    .gap_2()
+                    .rounded_md()
+                    .border()
+                    .border_color(cx.theme().colors().border)
+                    .child(Checkbox::new("checkbox-disabled", Selection::Unselected).disabled(true))
+                    .child(
+                        Checkbox::new("checkbox-disabled-intermediate", Selection::Indeterminate)
+                            .disabled(true),
+                    )
+                    .child(
+                        Checkbox::new("checkbox-disabled-selected", Selection::Selected)
+                            .disabled(true),
+                    ),
+            )
+    }
+}

crates/ui2/src/components/stories/context_menu.rs 🔗

@@ -0,0 +1,104 @@
+use gpui::{actions, Action, AnchorCorner, Div, Render, View};
+use story::Story;
+
+use crate::prelude::*;
+use crate::{menu_handle, ContextMenu, Label};
+
+actions!(PrintCurrentDate, PrintBestFood);
+
+fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<ContextMenu> {
+    ContextMenu::build(cx, |menu, _| {
+        menu.header(header)
+            .separator()
+            .entry("Print current time", |v, cx| {
+                println!("dispatching PrintCurrentTime action");
+                cx.dispatch_action(PrintCurrentDate.boxed_clone())
+            })
+            .entry("Print best foot", |v, cx| {
+                cx.dispatch_action(PrintBestFood.boxed_clone())
+            })
+    })
+}
+
+pub struct ContextMenuStory;
+
+impl Render for ContextMenuStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .on_action(|_: &PrintCurrentDate, _| {
+                println!("printing unix time!");
+                if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
+                    println!("Current Unix time is {:?}", unix_time.as_secs());
+                }
+            })
+            .on_action(|_: &PrintBestFood, _| {
+                println!("burrito");
+            })
+            .flex()
+            .flex_row()
+            .justify_between()
+            .child(
+                div()
+                    .flex()
+                    .flex_col()
+                    .justify_between()
+                    .child(
+                        menu_handle("test2")
+                            .child(|is_open| {
+                                Label::new(if is_open {
+                                    "TOP LEFT"
+                                } else {
+                                    "RIGHT CLICK ME"
+                                })
+                            })
+                            .menu(move |cx| build_menu(cx, "top left")),
+                    )
+                    .child(
+                        menu_handle("test1")
+                            .child(|is_open| {
+                                Label::new(if is_open {
+                                    "BOTTOM LEFT"
+                                } else {
+                                    "RIGHT CLICK ME"
+                                })
+                            })
+                            .anchor(AnchorCorner::BottomLeft)
+                            .attach(AnchorCorner::TopLeft)
+                            .menu(move |cx| build_menu(cx, "bottom left")),
+                    ),
+            )
+            .child(
+                div()
+                    .flex()
+                    .flex_col()
+                    .justify_between()
+                    .child(
+                        menu_handle("test3")
+                            .child(|is_open| {
+                                Label::new(if is_open {
+                                    "TOP RIGHT"
+                                } else {
+                                    "RIGHT CLICK ME"
+                                })
+                            })
+                            .anchor(AnchorCorner::TopRight)
+                            .menu(move |cx| build_menu(cx, "top right")),
+                    )
+                    .child(
+                        menu_handle("test4")
+                            .child(|is_open| {
+                                Label::new(if is_open {
+                                    "BOTTOM RIGHT"
+                                } else {
+                                    "RIGHT CLICK ME"
+                                })
+                            })
+                            .anchor(AnchorCorner::BottomRight)
+                            .attach(AnchorCorner::TopRight)
+                            .menu(move |cx| build_menu(cx, "bottom right")),
+                    ),
+            )
+    }
+}

crates/ui2/src/components/stories/icon.rs 🔗

@@ -0,0 +1,21 @@
+use gpui::{Div, Render};
+use story::Story;
+use strum::IntoEnumIterator;
+
+use crate::prelude::*;
+use crate::{Icon, IconElement};
+
+pub struct IconStory;
+
+impl Render for IconStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let icons = Icon::iter();
+
+        Story::container()
+            .child(Story::title_for::<IconElement>())
+            .child(Story::label("All Icons"))
+            .child(div().flex().gap_3().children(icons.map(IconElement::new)))
+    }
+}

crates/ui2/src/components/stories/input.rs 🔗

@@ -0,0 +1,18 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::prelude::*;
+use crate::Input;
+
+pub struct InputStory;
+
+impl Render for InputStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<Input>())
+            .child(Story::label("Default"))
+            .child(div().flex().child(Input::new("Search")))
+    }
+}

crates/ui2/src/components/stories/keybinding.rs 🔗

@@ -0,0 +1,61 @@
+use gpui::{actions, Div, Render};
+use itertools::Itertools;
+use story::Story;
+
+use crate::prelude::*;
+use crate::KeyBinding;
+
+pub struct KeybindingStory;
+
+actions!(NoAction);
+
+pub fn binding(key: &str) -> gpui::KeyBinding {
+    gpui::KeyBinding::new(key, NoAction {}, None)
+}
+
+impl Render for KeybindingStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
+
+        Story::container()
+            .child(Story::title_for::<KeyBinding>())
+            .child(Story::label("Single Key"))
+            .child(KeyBinding::new(binding("Z")))
+            .child(Story::label("Single Key with Modifier"))
+            .child(
+                div()
+                    .flex()
+                    .gap_3()
+                    .child(KeyBinding::new(binding("ctrl-c")))
+                    .child(KeyBinding::new(binding("alt-c")))
+                    .child(KeyBinding::new(binding("cmd-c")))
+                    .child(KeyBinding::new(binding("shift-c"))),
+            )
+            .child(Story::label("Single Key with Modifier (Permuted)"))
+            .child(
+                div().flex().flex_col().children(
+                    all_modifier_permutations
+                        .chunks(4)
+                        .into_iter()
+                        .map(|chunk| {
+                            div()
+                                .flex()
+                                .gap_4()
+                                .py_3()
+                                .children(chunk.map(|permutation| {
+                                    KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
+                                }))
+                        }),
+                ),
+            )
+            .child(Story::label("Single Key with All Modifiers"))
+            .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
+            .child(Story::label("Chord"))
+            .child(KeyBinding::new(binding("a z")))
+            .child(Story::label("Chord with Modifier"))
+            .child(KeyBinding::new(binding("ctrl-a shift-z")))
+            .child(KeyBinding::new(binding("fn-s")))
+    }
+}

crates/ui2/src/components/stories/label.rs 🔗

@@ -0,0 +1,27 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::prelude::*;
+use crate::{HighlightedLabel, Label};
+
+pub struct LabelStory;
+
+impl Render for LabelStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<Label>())
+            .child(Story::label("Default"))
+            .child(Label::new("Hello, world!"))
+            .child(Story::label("Highlighted"))
+            .child(HighlightedLabel::new(
+                "Hello, world!",
+                vec![0, 1, 2, 7, 8, 12],
+            ))
+            .child(HighlightedLabel::new(
+                "Héllo, world!",
+                vec![0, 1, 3, 8, 9, 13],
+            ))
+    }
+}

crates/ui2/src/components/tab.rs 🔗

@@ -1,276 +0,0 @@
-use crate::prelude::*;
-use crate::{Icon, IconElement, Label, TextColor};
-use gpui::{prelude::*, red, Div, ElementId, Render, RenderOnce, Stateful, View};
-
-#[derive(RenderOnce, Clone)]
-pub struct Tab {
-    id: ElementId,
-    title: String,
-    icon: Option<Icon>,
-    current: bool,
-    dirty: bool,
-    fs_status: FileSystemStatus,
-    git_status: GitStatus,
-    diagnostic_status: DiagnosticStatus,
-    close_side: IconSide,
-}
-
-#[derive(Clone, Debug)]
-struct TabDragState {
-    title: String,
-}
-
-impl Render<Self> for TabDragState {
-    type Element = Div<Self>;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        div().w_8().h_4().bg(red())
-    }
-}
-
-impl<V: 'static> Component<V> for Tab {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
-        let is_deleted = self.fs_status == FileSystemStatus::Deleted;
-
-        let label = match (self.git_status, is_deleted) {
-            (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone())
-                .color(TextColor::Hidden)
-                .set_strikethrough(true),
-            (GitStatus::None, false) => Label::new(self.title.clone()),
-            (GitStatus::Created, false) => Label::new(self.title.clone()).color(TextColor::Created),
-            (GitStatus::Modified, false) => {
-                Label::new(self.title.clone()).color(TextColor::Modified)
-            }
-            (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(TextColor::Accent),
-            (GitStatus::Conflict, false) => Label::new(self.title.clone()),
-        };
-
-        let close_icon = || IconElement::new(Icon::Close).color(TextColor::Muted);
-
-        let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
-            false => (
-                cx.theme().colors().tab_inactive_background,
-                cx.theme().colors().ghost_element_hover,
-                cx.theme().colors().ghost_element_active,
-            ),
-            true => (
-                cx.theme().colors().tab_active_background,
-                cx.theme().colors().element_hover,
-                cx.theme().colors().element_active,
-            ),
-        };
-
-        let drag_state = TabDragState {
-            title: self.title.clone(),
-        };
-
-        div()
-            .id(self.id.clone())
-            .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
-            .drag_over::<TabDragState>(|d| d.bg(cx.theme().colors().drop_target_background))
-            .on_drop(|_view, state: View<TabDragState>, cx| {
-                eprintln!("{:?}", state.read(cx));
-            })
-            .px_2()
-            .py_0p5()
-            .flex()
-            .items_center()
-            .justify_center()
-            .bg(tab_bg)
-            .hover(|h| h.bg(tab_hover_bg))
-            .active(|a| a.bg(tab_active_bg))
-            .child(
-                div()
-                    .px_1()
-                    .flex()
-                    .items_center()
-                    .gap_1p5()
-                    .children(has_fs_conflict.then(|| {
-                        IconElement::new(Icon::ExclamationTriangle)
-                            .size(crate::IconSize::Small)
-                            .color(TextColor::Warning)
-                    }))
-                    .children(self.icon.map(IconElement::new))
-                    .children(if self.close_side == IconSide::Left {
-                        Some(close_icon())
-                    } else {
-                        None
-                    })
-                    .child(label)
-                    .children(if self.close_side == IconSide::Right {
-                        Some(close_icon())
-                    } else {
-                        None
-                    }),
-            )
-    }
-}
-
-impl Tab {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            title: "untitled".to_string(),
-            icon: None,
-            current: false,
-            dirty: false,
-            fs_status: FileSystemStatus::None,
-            git_status: GitStatus::None,
-            diagnostic_status: DiagnosticStatus::None,
-            close_side: IconSide::Right,
-        }
-    }
-
-    pub fn current(mut self, current: bool) -> Self {
-        self.current = current;
-        self
-    }
-
-    pub fn title(mut self, title: String) -> Self {
-        self.title = title;
-        self
-    }
-
-    pub fn icon<I>(mut self, icon: I) -> Self
-    where
-        I: Into<Option<Icon>>,
-    {
-        self.icon = icon.into();
-        self
-    }
-
-    pub fn dirty(mut self, dirty: bool) -> Self {
-        self.dirty = dirty;
-        self
-    }
-
-    pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
-        self.fs_status = fs_status;
-        self
-    }
-
-    pub fn git_status(mut self, git_status: GitStatus) -> Self {
-        self.git_status = git_status;
-        self
-    }
-
-    pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
-        self.diagnostic_status = diagnostic_status;
-        self
-    }
-
-    pub fn close_side(mut self, close_side: IconSide) -> Self {
-        self.close_side = close_side;
-        self
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{h_stack, v_stack, Icon, Story};
-    use strum::IntoEnumIterator;
-
-    pub struct TabStory;
-
-    impl Render<Self> for TabStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let git_statuses = GitStatus::iter();
-            let fs_statuses = FileSystemStatus::iter();
-
-            Story::container(cx)
-                .child(Story::title_for::<_, Tab>(cx))
-                .child(
-                    h_stack().child(
-                        v_stack()
-                            .gap_2()
-                            .child(Story::label(cx, "Default"))
-                            .child(Tab::new("default")),
-                    ),
-                )
-                .child(
-                    h_stack().child(
-                        v_stack().gap_2().child(Story::label(cx, "Current")).child(
-                            h_stack()
-                                .gap_4()
-                                .child(
-                                    Tab::new("current")
-                                        .title("Current".to_string())
-                                        .current(true),
-                                )
-                                .child(
-                                    Tab::new("not_current")
-                                        .title("Not Current".to_string())
-                                        .current(false),
-                                ),
-                        ),
-                    ),
-                )
-                .child(
-                    h_stack().child(
-                        v_stack()
-                            .gap_2()
-                            .child(Story::label(cx, "Titled"))
-                            .child(Tab::new("titled").title("label".to_string())),
-                    ),
-                )
-                .child(
-                    h_stack().child(
-                        v_stack()
-                            .gap_2()
-                            .child(Story::label(cx, "With Icon"))
-                            .child(
-                                Tab::new("with_icon")
-                                    .title("label".to_string())
-                                    .icon(Some(Icon::Envelope)),
-                            ),
-                    ),
-                )
-                .child(
-                    h_stack().child(
-                        v_stack()
-                            .gap_2()
-                            .child(Story::label(cx, "Close Side"))
-                            .child(
-                                h_stack()
-                                    .gap_4()
-                                    .child(
-                                        Tab::new("left")
-                                            .title("Left".to_string())
-                                            .close_side(IconSide::Left),
-                                    )
-                                    .child(Tab::new("right").title("Right".to_string())),
-                            ),
-                    ),
-                )
-                .child(
-                    v_stack()
-                        .gap_2()
-                        .child(Story::label(cx, "Git Status"))
-                        .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
-                            Tab::new("git_status")
-                                .title(git_status.to_string())
-                                .git_status(git_status)
-                        }))),
-                )
-                .child(
-                    v_stack()
-                        .gap_2()
-                        .child(Story::label(cx, "File System Status"))
-                        .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
-                            Tab::new("file_system_status")
-                                .title(fs_status.to_string())
-                                .fs_status(fs_status)
-                        }))),
-                )
-        }
-    }
-}

crates/ui2/src/components/toast.rs 🔗

@@ -1,117 +0,0 @@
-use crate::prelude::*;
-use gpui::{prelude::*, AnyElement, RenderOnce};
-use gpui::{Div, Element};
-use smallvec::SmallVec;
-
-#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
-pub enum ToastOrigin {
-    #[default]
-    Bottom,
-    BottomRight,
-}
-
-/// Don't use toast directly:
-///
-/// - For messages with a required action, use a `NotificationToast`.
-/// - For messages that convey information, use a `StatusToast`.
-///
-/// A toast is a small, temporary window that appears to show a message to the user
-/// or indicate a required action.
-///
-/// Toasts should not persist on the screen for more than a few seconds unless
-/// they are actively showing the a process in progress.
-///
-/// Only one toast may be visible at a time.
-#[derive(RenderOnce)]
-pub struct Toast<V: 'static> {
-    origin: ToastOrigin,
-    children: SmallVec<[AnyElement<V>; 2]>,
-}
-
-impl<V: 'static> Component<V> for Toast<V> {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let mut div = div();
-
-        if self.origin == ToastOrigin::Bottom {
-            div = div.right_1_2();
-        } else {
-            div = div.right_2();
-        }
-
-        div.z_index(5)
-            .absolute()
-            .bottom_9()
-            .flex()
-            .py_1()
-            .px_1p5()
-            .rounded_lg()
-            .shadow_md()
-            .overflow_hidden()
-            .bg(cx.theme().colors().elevated_surface_background)
-            .children(self.children)
-    }
-}
-
-impl<V: 'static> Toast<V> {
-    pub fn new(origin: ToastOrigin) -> Self {
-        Self {
-            origin,
-            children: SmallVec::new(),
-        }
-    }
-
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        let mut div = div();
-
-        if self.origin == ToastOrigin::Bottom {
-            div = div.right_1_2();
-        } else {
-            div = div.right_2();
-        }
-
-        div.z_index(5)
-            .absolute()
-            .bottom_9()
-            .flex()
-            .py_1()
-            .px_1p5()
-            .rounded_lg()
-            .shadow_md()
-            .overflow_hidden()
-            .bg(cx.theme().colors().elevated_surface_background)
-            .children(self.children)
-    }
-}
-
-impl<V: 'static> ParentElement<V> for Toast<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
-        &mut self.children
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use gpui::{Div, Render};
-
-    use crate::{Label, Story};
-
-    use super::*;
-
-    pub struct ToastStory;
-
-    impl Render<Self> for ToastStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Toast<Self>>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Toast::new(ToastOrigin::Bottom).child(Label::new("label")))
-        }
-    }
-}

crates/ui2/src/components/toggle.rs 🔗

@@ -1,7 +1,3 @@
-use gpui::{div, Element, ParentElement};
-
-use crate::{Icon, IconElement, IconSize, TextColor};
-
 /// Whether the entry is toggleable, and if so, whether it is currently toggled.
 ///
 /// To make an element toggleable, simply add a `Toggle::Toggled(_)` and handle it's cases.
@@ -43,19 +39,3 @@ impl From<bool> for Toggle {
         Toggle::Toggled(toggled)
     }
 }
-
-pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Element<V> {
-    match (toggle.is_toggleable(), toggle.is_toggled()) {
-        (false, _) => div(),
-        (_, true) => div().child(
-            IconElement::new(Icon::ChevronDown)
-                .color(TextColor::Muted)
-                .size(IconSize::Small),
-        ),
-        (_, false) => div().child(
-            IconElement::new(Icon::ChevronRight)
-                .color(TextColor::Muted)
-                .size(IconSize::Small),
-        ),
-    }
-}

crates/ui2/src/components/tool_divider.rs 🔗

@@ -1,23 +0,0 @@
-use crate::prelude::*;
-use gpui::{Div, RenderOnce};
-
-#[derive(RenderOnce)]
-pub struct ToolDivider;
-
-impl<V: 'static> Component<V> for ToolDivider {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div().w_px().h_3().bg(cx.theme().colors().border)
-    }
-}
-
-impl ToolDivider {
-    pub fn new() -> Self {
-        Self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div().w_px().h_3().bg(cx.theme().colors().border)
-    }
-}

crates/ui2/src/components/tooltip.rs 🔗

@@ -3,7 +3,7 @@ use settings2::Settings;
 use theme2::{ActiveTheme, ThemeSettings};
 
 use crate::prelude::*;
-use crate::{h_stack, v_stack, KeyBinding, Label, LabelSize, StyledExt, TextColor};
+use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt};
 
 pub struct Tooltip {
     title: SharedString,
@@ -67,8 +67,8 @@ impl Tooltip {
     }
 }
 
-impl Render<Self> for Tooltip {
-    type Element = Overlay<Self>;
+impl Render for Tooltip {
+    type Element = Overlay;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
@@ -90,11 +90,7 @@ impl Render<Self> for Tooltip {
                             }),
                     )
                     .when_some(self.meta.clone(), |this, meta| {
-                        this.child(
-                            Label::new(meta)
-                                .size(LabelSize::Small)
-                                .color(TextColor::Muted),
-                        )
+                        this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
                     }),
             ),
         )

crates/ui2/src/prelude.rs 🔗

@@ -1,116 +1,14 @@
-use gpui::rems;
-use gpui::Rems;
 pub use gpui::{
     div, Component, Element, ElementId, InteractiveElement, ParentElement, SharedString, Styled,
     ViewContext, WindowContext,
 };
 
-pub use crate::elevation::*;
 pub use crate::StyledExt;
-pub use crate::{ButtonVariant, TextColor};
+pub use crate::{ButtonVariant, Color};
 pub use theme2::ActiveTheme;
 
-use gpui::Hsla;
 use strum::EnumIter;
 
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum UITextSize {
-    /// The default size for UI text.
-    ///
-    /// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
-    ///
-    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
-    #[default]
-    Default,
-    /// The small size for UI text.
-    ///
-    /// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
-    ///
-    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
-    Small,
-}
-
-impl UITextSize {
-    pub fn rems(self) -> Rems {
-        match self {
-            Self::Default => rems(0.875),
-            Self::Small => rems(0.75),
-        }
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum FileSystemStatus {
-    #[default]
-    None,
-    Conflict,
-    Deleted,
-}
-
-impl std::fmt::Display for FileSystemStatus {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}",
-            match self {
-                Self::None => "None",
-                Self::Conflict => "Conflict",
-                Self::Deleted => "Deleted",
-            }
-        )
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum GitStatus {
-    #[default]
-    None,
-    Created,
-    Modified,
-    Deleted,
-    Conflict,
-    Renamed,
-}
-
-impl GitStatus {
-    pub fn hsla(&self, cx: &WindowContext) -> Hsla {
-        match self {
-            Self::None => cx.theme().system().transparent,
-            Self::Created => cx.theme().status().created,
-            Self::Modified => cx.theme().status().modified,
-            Self::Deleted => cx.theme().status().deleted,
-            Self::Conflict => cx.theme().status().conflict,
-            Self::Renamed => cx.theme().status().renamed,
-        }
-    }
-}
-
-impl std::fmt::Display for GitStatus {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}",
-            match self {
-                Self::None => "None",
-                Self::Created => "Created",
-                Self::Modified => "Modified",
-                Self::Deleted => "Deleted",
-                Self::Conflict => "Conflict",
-                Self::Renamed => "Renamed",
-            }
-        )
-    }
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum DiagnosticStatus {
-    #[default]
-    None,
-    Error,
-    Warning,
-    Info,
-}
-
 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum IconSide {
     #[default]
@@ -118,45 +16,6 @@ pub enum IconSide {
     Right,
 }
 
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum OrderMethod {
-    #[default]
-    Ascending,
-    Descending,
-    MostRecent,
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum Shape {
-    #[default]
-    Circle,
-    RoundedRectangle,
-}
-
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum DisclosureControlVisibility {
-    #[default]
-    OnHover,
-    Always,
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
-pub enum DisclosureControlStyle {
-    /// Shows the disclosure control only when hovered where possible.
-    ///
-    /// More compact, but not available everywhere.
-    ChevronOnHover,
-    /// Shows an icon where possible, otherwise shows a chevron.
-    ///
-    /// For example, in a file tree a folder or file icon is shown
-    /// instead of a chevron
-    Icon,
-    /// Always shows a chevron.
-    Chevron,
-    /// Completely hides the disclosure control where possible.
-    None,
-}
-
 #[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
 pub enum OverflowStyle {
     Hidden,
@@ -165,12 +24,22 @@ pub enum OverflowStyle {
 
 #[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
 pub enum InteractionState {
+    /// An element that is enabled and not hovered, active, focused, or disabled.
+    ///
+    /// This is often referred to as the "default" state.
     #[default]
     Enabled,
+    /// An element that is hovered.
     Hovered,
+    /// An element has an active mouse down or touch start event on it.
     Active,
+    /// An element that is focused using the keyboard.
     Focused,
+    /// An element that is disabled.
     Disabled,
+    /// A toggleable element that is selected, like the active button in a
+    /// button toggle group.
+    Selected,
 }
 
 impl InteractionState {

crates/ui2/src/settings.rs 🔗

@@ -1,74 +0,0 @@
-use std::ops::Deref;
-
-use gpui::{rems, AbsoluteLength, AppContext, WindowContext};
-
-use crate::prelude::*;
-
-pub fn init(cx: &mut AppContext) {
-    cx.set_global(FakeSettings::default());
-}
-
-/// Returns the user settings.
-pub fn user_settings(cx: &WindowContext) -> FakeSettings {
-    cx.global::<FakeSettings>().clone()
-}
-
-pub fn user_settings_mut<'cx>(cx: &'cx mut WindowContext) -> &'cx mut FakeSettings {
-    cx.global_mut::<FakeSettings>()
-}
-
-#[derive(Clone)]
-pub enum SettingValue<T> {
-    UserDefined(T),
-    Default(T),
-}
-
-impl<T> Deref for SettingValue<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        match self {
-            Self::UserDefined(value) => value,
-            Self::Default(value) => value,
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct TitlebarSettings {
-    pub show_project_owner: SettingValue<bool>,
-    pub show_git_status: SettingValue<bool>,
-    pub show_git_controls: SettingValue<bool>,
-}
-
-impl Default for TitlebarSettings {
-    fn default() -> Self {
-        Self {
-            show_project_owner: SettingValue::Default(true),
-            show_git_status: SettingValue::Default(true),
-            show_git_controls: SettingValue::Default(true),
-        }
-    }
-}
-
-// These should be merged into settings
-#[derive(Clone)]
-pub struct FakeSettings {
-    pub default_panel_size: SettingValue<AbsoluteLength>,
-    pub list_disclosure_style: SettingValue<DisclosureControlStyle>,
-    pub list_indent_depth: SettingValue<AbsoluteLength>,
-    pub titlebar: TitlebarSettings,
-}
-
-impl Default for FakeSettings {
-    fn default() -> Self {
-        Self {
-            titlebar: TitlebarSettings::default(),
-            list_disclosure_style: SettingValue::Default(DisclosureControlStyle::ChevronOnHover),
-            list_indent_depth: SettingValue::Default(rems(0.3).into()),
-            default_panel_size: SettingValue::Default(rems(16.).into()),
-        }
-    }
-}
-
-impl FakeSettings {}

crates/ui2/src/static_data.rs 🔗

@@ -1,1123 +0,0 @@
-use std::path::PathBuf;
-use std::str::FromStr;
-use std::sync::Arc;
-
-use chrono::DateTime;
-use gpui::{AppContext, ViewContext};
-use rand::Rng;
-use theme2::ActiveTheme;
-
-use crate::{
-    binding, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
-    HighlightedLine, HighlightedText, Icon, KeyBinding, Label, ListEntry, ListEntrySize,
-    Livestream, MicStatus, Notification, NotificationAction, PaletteItem, Player, PlayerCallStatus,
-    PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, TextColor, Toggle,
-    VideoStatus,
-};
-
-pub fn static_tabs_example() -> Vec<Tab> {
-    vec![
-        Tab::new("wip.rs")
-            .title("wip.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false)
-            .fs_status(FileSystemStatus::Deleted),
-        Tab::new("Cargo.toml")
-            .title("Cargo.toml".to_string())
-            .icon(Icon::FileToml)
-            .current(false)
-            .git_status(GitStatus::Modified),
-        Tab::new("Channels Panel")
-            .title("Channels Panel".to_string())
-            .icon(Icon::Hash)
-            .current(false),
-        Tab::new("channels_panel.rs")
-            .title("channels_panel.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(true)
-            .git_status(GitStatus::Modified),
-        Tab::new("workspace.rs")
-            .title("workspace.rs".to_string())
-            .current(false)
-            .icon(Icon::FileRust)
-            .git_status(GitStatus::Modified),
-        Tab::new("icon_button.rs")
-            .title("icon_button.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false),
-        Tab::new("storybook.rs")
-            .title("storybook.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false)
-            .git_status(GitStatus::Created),
-        Tab::new("theme.rs")
-            .title("theme.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false),
-        Tab::new("theme_registry.rs")
-            .title("theme_registry.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false),
-        Tab::new("styleable_helpers.rs")
-            .title("styleable_helpers.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false),
-    ]
-}
-
-pub fn static_tabs_1() -> Vec<Tab> {
-    vec![
-        Tab::new("project_panel.rs")
-            .title("project_panel.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false)
-            .fs_status(FileSystemStatus::Deleted),
-        Tab::new("tab_bar.rs")
-            .title("tab_bar.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false)
-            .git_status(GitStatus::Modified),
-        Tab::new("workspace.rs")
-            .title("workspace.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false),
-        Tab::new("tab.rs")
-            .title("tab.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(true)
-            .git_status(GitStatus::Modified),
-    ]
-}
-
-pub fn static_tabs_2() -> Vec<Tab> {
-    vec![
-        Tab::new("tab_bar.rs")
-            .title("tab_bar.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(false)
-            .fs_status(FileSystemStatus::Deleted),
-        Tab::new("static_data.rs")
-            .title("static_data.rs".to_string())
-            .icon(Icon::FileRust)
-            .current(true)
-            .git_status(GitStatus::Modified),
-    ]
-}
-
-pub fn static_tabs_3() -> Vec<Tab> {
-    vec![Tab::new("static_tabs_3")
-        .git_status(GitStatus::Created)
-        .current(true)]
-}
-
-pub fn static_players() -> Vec<Player> {
-    vec![
-        Player::new(
-            0,
-            "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
-            "nathansobo".into(),
-        ),
-        Player::new(
-            1,
-            "https://avatars.githubusercontent.com/u/326587?v=4".into(),
-            "maxbrunsfeld".into(),
-        ),
-        Player::new(
-            2,
-            "https://avatars.githubusercontent.com/u/482957?v=4".into(),
-            "as-cii".into(),
-        ),
-        Player::new(
-            3,
-            "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
-            "iamnbutler".into(),
-        ),
-        Player::new(
-            4,
-            "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
-            "maxdeviant".into(),
-        ),
-    ]
-}
-
-#[derive(Debug)]
-pub struct PlayerData {
-    pub url: String,
-    pub name: String,
-}
-
-pub fn static_player_data() -> Vec<PlayerData> {
-    vec![
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
-            name: "iamnbutler".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/326587?v=4".into(),
-            name: "maxbrunsfeld".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/482957?v=4".into(),
-            name: "as-cii".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/1789?v=4".into(),
-            name: "nathansobo".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
-            name: "ForLoveOfCats".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(),
-            name: "SomeoneToIgnore".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(),
-            name: "JosephTLyons".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(),
-            name: "osiewicz".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(),
-            name: "KCaverly".into(),
-        },
-        PlayerData {
-            url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
-            name: "maxdeviant".into(),
-        },
-    ]
-}
-
-pub fn create_static_players(player_data: Vec<PlayerData>) -> Vec<Player> {
-    let mut players = Vec::new();
-    for data in player_data {
-        players.push(Player::new(players.len(), data.url, data.name));
-    }
-    players
-}
-
-pub fn static_player_1(data: &Vec<PlayerData>) -> Player {
-    Player::new(1, data[0].url.clone(), data[0].name.clone())
-}
-
-pub fn static_player_2(data: &Vec<PlayerData>) -> Player {
-    Player::new(2, data[1].url.clone(), data[1].name.clone())
-}
-
-pub fn static_player_3(data: &Vec<PlayerData>) -> Player {
-    Player::new(3, data[2].url.clone(), data[2].name.clone())
-}
-
-pub fn static_player_4(data: &Vec<PlayerData>) -> Player {
-    Player::new(4, data[3].url.clone(), data[3].name.clone())
-}
-
-pub fn static_player_5(data: &Vec<PlayerData>) -> Player {
-    Player::new(5, data[4].url.clone(), data[4].name.clone())
-}
-
-pub fn static_player_6(data: &Vec<PlayerData>) -> Player {
-    Player::new(6, data[5].url.clone(), data[5].name.clone())
-}
-
-pub fn static_player_7(data: &Vec<PlayerData>) -> Player {
-    Player::new(7, data[6].url.clone(), data[6].name.clone())
-}
-
-pub fn static_player_8(data: &Vec<PlayerData>) -> Player {
-    Player::new(8, data[7].url.clone(), data[7].name.clone())
-}
-
-pub fn static_player_9(data: &Vec<PlayerData>) -> Player {
-    Player::new(9, data[8].url.clone(), data[8].name.clone())
-}
-
-pub fn static_player_10(data: &Vec<PlayerData>) -> Player {
-    Player::new(10, data[9].url.clone(), data[9].name.clone())
-}
-
-pub fn static_livestream() -> Livestream {
-    Livestream {
-        players: random_players_with_call_status(7),
-        channel: Some("gpui2-ui".to_string()),
-    }
-}
-
-pub fn populate_player_call_status(
-    player: Player,
-    followers: Option<Vec<Player>>,
-) -> PlayerCallStatus {
-    let mut rng = rand::thread_rng();
-    let in_current_project: bool = rng.gen();
-    let disconnected: bool = rng.gen();
-    let voice_activity: f32 = rng.gen();
-    let mic_status = if rng.gen_bool(0.5) {
-        MicStatus::Muted
-    } else {
-        MicStatus::Unmuted
-    };
-    let video_status = if rng.gen_bool(0.5) {
-        VideoStatus::On
-    } else {
-        VideoStatus::Off
-    };
-    let screen_share_status = if rng.gen_bool(0.5) {
-        ScreenShareStatus::Shared
-    } else {
-        ScreenShareStatus::NotShared
-    };
-    PlayerCallStatus {
-        mic_status,
-        voice_activity,
-        video_status,
-        screen_share_status,
-        in_current_project,
-        disconnected,
-        following: None,
-        followers,
-    }
-}
-
-pub fn random_players_with_call_status(number_of_players: usize) -> Vec<PlayerWithCallStatus> {
-    let players = create_static_players(static_player_data());
-    let mut player_status = vec![];
-    for i in 0..number_of_players {
-        let followers = if i == 0 {
-            Some(vec![
-                players[1].clone(),
-                players[3].clone(),
-                players[5].clone(),
-                players[6].clone(),
-            ])
-        } else if i == 1 {
-            Some(vec![players[2].clone(), players[6].clone()])
-        } else {
-            None
-        };
-        let call_status = populate_player_call_status(players[i].clone(), followers);
-        player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status));
-    }
-    player_status
-}
-
-pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
-    let players = static_players();
-    let mut player_0_status = PlayerCallStatus::new();
-    let player_1_status = PlayerCallStatus::new();
-    let player_2_status = PlayerCallStatus::new();
-    let mut player_3_status = PlayerCallStatus::new();
-    let mut player_4_status = PlayerCallStatus::new();
-
-    player_0_status.screen_share_status = ScreenShareStatus::Shared;
-    player_0_status.followers = Some(vec![players[1].clone(), players[3].clone()]);
-
-    player_3_status.voice_activity = 0.5;
-    player_4_status.mic_status = MicStatus::Muted;
-    player_4_status.in_current_project = false;
-
-    vec![
-        PlayerWithCallStatus::new(players[0].clone(), player_0_status),
-        PlayerWithCallStatus::new(players[1].clone(), player_1_status),
-        PlayerWithCallStatus::new(players[2].clone(), player_2_status),
-        PlayerWithCallStatus::new(players[3].clone(), player_3_status),
-        PlayerWithCallStatus::new(players[4].clone(), player_4_status),
-    ]
-}
-
-pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
-    vec![
-        Notification::new_icon_message(
-            "notif-1",
-            "You were mentioned in a note.",
-            DateTime::parse_from_rfc3339("2023-11-02T11:59:57Z")
-                .unwrap()
-                .naive_local(),
-            Icon::AtSign,
-            Arc::new(|_, _| {}),
-        ),
-        Notification::new_actor_with_actions(
-            "notif-2",
-            "as-cii sent you a contact request.",
-            DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
-            [
-                NotificationAction::new(
-                    Button::new("Decline"),
-                    "Decline Request",
-                    (Some(Icon::XCircle), "Declined"),
-                ),
-                NotificationAction::new(
-                    Button::new("Accept").variant(crate::ButtonVariant::Filled),
-                    "Accept Request",
-                    (Some(Icon::Check), "Accepted"),
-                ),
-            ],
-        ),
-        Notification::new_icon_message(
-            "notif-3",
-            "You were mentioned #design.",
-            DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            Icon::MessageBubbles,
-            Arc::new(|_, _| {}),
-        ),
-        Notification::new_actor_with_actions(
-            "notif-4",
-            "as-cii sent you a contact request.",
-            DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
-            [
-                NotificationAction::new(
-                    Button::new("Decline"),
-                    "Decline Request",
-                    (Some(Icon::XCircle), "Declined"),
-                ),
-                NotificationAction::new(
-                    Button::new("Accept").variant(crate::ButtonVariant::Filled),
-                    "Accept Request",
-                    (Some(Icon::Check), "Accepted"),
-                ),
-            ],
-        ),
-        Notification::new_icon_message(
-            "notif-5",
-            "You were mentioned in a note.",
-            DateTime::parse_from_rfc3339("2023-10-28T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            Icon::AtSign,
-            Arc::new(|_, _| {}),
-        ),
-        Notification::new_actor_with_actions(
-            "notif-6",
-            "as-cii sent you a contact request.",
-            DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
-            [
-                NotificationAction::new(
-                    Button::new("Decline"),
-                    "Decline Request",
-                    (Some(Icon::XCircle), "Declined"),
-                ),
-                NotificationAction::new(
-                    Button::new("Accept").variant(crate::ButtonVariant::Filled),
-                    "Accept Request",
-                    (Some(Icon::Check), "Accepted"),
-                ),
-            ],
-        ),
-        Notification::new_icon_message(
-            "notif-7",
-            "You were mentioned in a note.",
-            DateTime::parse_from_rfc3339("2022-10-14T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            Icon::AtSign,
-            Arc::new(|_, _| {}),
-        ),
-        Notification::new_actor_with_actions(
-            "notif-8",
-            "as-cii sent you a contact request.",
-            DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
-            [
-                NotificationAction::new(
-                    Button::new("Decline"),
-                    "Decline Request",
-                    (Some(Icon::XCircle), "Declined"),
-                ),
-                NotificationAction::new(
-                    Button::new("Accept").variant(crate::ButtonVariant::Filled),
-                    "Accept Request",
-                    (Some(Icon::Check), "Accepted"),
-                ),
-            ],
-        ),
-        Notification::new_icon_message(
-            "notif-9",
-            "You were mentioned in a note.",
-            DateTime::parse_from_rfc3339("2021-02-02T12:09:07Z")
-                .unwrap()
-                .naive_local(),
-            Icon::AtSign,
-            Arc::new(|_, _| {}),
-        ),
-        Notification::new_actor_with_actions(
-            "notif-10",
-            "as-cii sent you a contact request.",
-            DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z")
-                .unwrap()
-                .naive_local(),
-            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
-            [
-                NotificationAction::new(
-                    Button::new("Decline"),
-                    "Decline Request",
-                    (Some(Icon::XCircle), "Declined"),
-                ),
-                NotificationAction::new(
-                    Button::new("Accept").variant(crate::ButtonVariant::Filled),
-                    "Accept Request",
-                    (Some(Icon::Check), "Accepted"),
-                ),
-            ],
-        ),
-    ]
-}
-
-pub fn static_project_panel_project_items<V>() -> Vec<ListEntry<V>> {
-    vec![
-        ListEntry::new("zed", Label::new("zed"))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(0)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new(".cargo", Label::new(".cargo"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new(".config", Label::new(".config"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new(".git", Label::new(".git").color(TextColor::Hidden))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new(".cargo", Label::new(".cargo"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new(".idea", Label::new(".idea").color(TextColor::Hidden))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new("assets", Label::new("assets"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new(
-            "cargo-target",
-            Label::new("cargo-target").color(TextColor::Hidden),
-        )
-        .left_icon(Icon::Folder.into())
-        .indent_level(1),
-        ListEntry::new("crates", Label::new("crates"))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(1)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new("activity_indicator", Label::new("activity_indicator"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2),
-        ListEntry::new("ai", Label::new("ai"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2),
-        ListEntry::new("audio", Label::new("audio"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2),
-        ListEntry::new("auto_update", Label::new("auto_update"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2),
-        ListEntry::new("breadcrumbs", Label::new("breadcrumbs"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2),
-        ListEntry::new("call", Label::new("call"))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2),
-        ListEntry::new("sqlez", Label::new("sqlez").color(TextColor::Modified))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2)
-            .toggle(Toggle::Toggled(false)),
-        ListEntry::new("gpui2", Label::new("gpui2"))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(2)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new("src", Label::new("src"))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(3)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new("derive_element.rs", Label::new("derive_element.rs"))
-            .left_icon(Icon::FileRust.into())
-            .indent_level(4),
-        ListEntry::new(
-            "storybook",
-            Label::new("storybook").color(TextColor::Modified),
-        )
-        .left_icon(Icon::FolderOpen.into())
-        .indent_level(1)
-        .toggle(Toggle::Toggled(true)),
-        ListEntry::new("docs", Label::new("docs").color(TextColor::Default))
-            .left_icon(Icon::Folder.into())
-            .indent_level(2)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new("src", Label::new("src").color(TextColor::Modified))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(3)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new("ui", Label::new("ui").color(TextColor::Modified))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(4)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new(
-            "component",
-            Label::new("component").color(TextColor::Created),
-        )
-        .left_icon(Icon::FolderOpen.into())
-        .indent_level(5)
-        .toggle(Toggle::Toggled(true)),
-        ListEntry::new(
-            "facepile.rs",
-            Label::new("facepile.rs").color(TextColor::Default),
-        )
-        .left_icon(Icon::FileRust.into())
-        .indent_level(6),
-        ListEntry::new(
-            "follow_group.rs",
-            Label::new("follow_group.rs").color(TextColor::Default),
-        )
-        .left_icon(Icon::FileRust.into())
-        .indent_level(6),
-        ListEntry::new(
-            "list_item.rs",
-            Label::new("list_item.rs").color(TextColor::Created),
-        )
-        .left_icon(Icon::FileRust.into())
-        .indent_level(6),
-        ListEntry::new("tab.rs", Label::new("tab.rs").color(TextColor::Default))
-            .left_icon(Icon::FileRust.into())
-            .indent_level(6),
-        ListEntry::new("target", Label::new("target").color(TextColor::Hidden))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new(".dockerignore", Label::new(".dockerignore"))
-            .left_icon(Icon::FileGeneric.into())
-            .indent_level(1),
-        ListEntry::new(
-            ".DS_Store",
-            Label::new(".DS_Store").color(TextColor::Hidden),
-        )
-        .left_icon(Icon::FileGeneric.into())
-        .indent_level(1),
-        ListEntry::new("Cargo.lock", Label::new("Cargo.lock"))
-            .left_icon(Icon::FileLock.into())
-            .indent_level(1),
-        ListEntry::new("Cargo.toml", Label::new("Cargo.toml"))
-            .left_icon(Icon::FileToml.into())
-            .indent_level(1),
-        ListEntry::new("Dockerfile", Label::new("Dockerfile"))
-            .left_icon(Icon::FileGeneric.into())
-            .indent_level(1),
-        ListEntry::new("Procfile", Label::new("Procfile"))
-            .left_icon(Icon::FileGeneric.into())
-            .indent_level(1),
-        ListEntry::new("README.md", Label::new("README.md"))
-            .left_icon(Icon::FileDoc.into())
-            .indent_level(1),
-    ]
-}
-
-pub fn static_project_panel_single_items<V>() -> Vec<ListEntry<V>> {
-    vec![
-        ListEntry::new("todo.md", Label::new("todo.md"))
-            .left_icon(Icon::FileDoc.into())
-            .indent_level(0),
-        ListEntry::new("README.md", Label::new("README.md"))
-            .left_icon(Icon::FileDoc.into())
-            .indent_level(0),
-        ListEntry::new("config.json", Label::new("config.json"))
-            .left_icon(Icon::FileGeneric.into())
-            .indent_level(0),
-    ]
-}
-
-pub fn static_collab_panel_current_call<V>() -> Vec<ListEntry<V>> {
-    vec![
-        ListEntry::new("as-cii", Label::new("as-cii"))
-            .left_avatar("http://github.com/as-cii.png?s=50"),
-        ListEntry::new("nathansobo", Label::new("nathansobo"))
-            .left_avatar("http://github.com/nathansobo.png?s=50"),
-        ListEntry::new("maxbrunsfeld", Label::new("maxbrunsfeld"))
-            .left_avatar("http://github.com/maxbrunsfeld.png?s=50"),
-    ]
-}
-
-pub fn static_collab_panel_channels<V>() -> Vec<ListEntry<V>> {
-    vec![
-        ListEntry::new("zed", Label::new("zed"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(0),
-        ListEntry::new("community", Label::new("community"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(1),
-        ListEntry::new("dashboards", Label::new("dashboards"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("feedback", Label::new("feedback"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new(
-            "teams-in-channels-alpha",
-            Label::new("teams-in-channels-alpha"),
-        )
-        .left_icon(Icon::Hash.into())
-        .size(ListEntrySize::Medium)
-        .indent_level(2),
-        ListEntry::new("current-projects", Label::new("current-projects"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(1),
-        ListEntry::new("codegen", Label::new("codegen"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("gpui2", Label::new("gpui2"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("livestreaming", Label::new("livestreaming"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("open-source", Label::new("open-source"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("replace", Label::new("replace"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("semantic-index", Label::new("semantic-index"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("vim", Label::new("vim"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new("web-tech", Label::new("web-tech"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-    ]
-}
-
-pub fn example_editor_actions() -> Vec<PaletteItem> {
-    vec![
-        PaletteItem::new("New File").key_binding(KeyBinding::new(binding("cmd-n"))),
-        PaletteItem::new("Open File").key_binding(KeyBinding::new(binding("cmd-o"))),
-        PaletteItem::new("Save File").key_binding(KeyBinding::new(binding("cmd-s"))),
-        PaletteItem::new("Cut").key_binding(KeyBinding::new(binding("cmd-x"))),
-        PaletteItem::new("Copy").key_binding(KeyBinding::new(binding("cmd-c"))),
-        PaletteItem::new("Paste").key_binding(KeyBinding::new(binding("cmd-v"))),
-        PaletteItem::new("Undo").key_binding(KeyBinding::new(binding("cmd-z"))),
-        PaletteItem::new("Redo").key_binding(KeyBinding::new(binding("cmd-shift-z"))),
-        PaletteItem::new("Find").key_binding(KeyBinding::new(binding("cmd-f"))),
-        PaletteItem::new("Replace").key_binding(KeyBinding::new(binding("cmd-r"))),
-        PaletteItem::new("Jump to Line"),
-        PaletteItem::new("Select All"),
-        PaletteItem::new("Deselect All"),
-        PaletteItem::new("Switch Document"),
-        PaletteItem::new("Insert Line Below"),
-        PaletteItem::new("Insert Line Above"),
-        PaletteItem::new("Move Line Up"),
-        PaletteItem::new("Move Line Down"),
-        PaletteItem::new("Toggle Comment"),
-        PaletteItem::new("Delete Line"),
-    ]
-}
-
-pub fn empty_editor_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
-    EditorPane::new(
-        cx,
-        static_tabs_example(),
-        PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
-        vec![],
-        empty_buffer_example(),
-    )
-}
-
-pub fn empty_buffer_example() -> Buffer {
-    Buffer::new("empty-buffer").set_rows(Some(BufferRows::default()))
-}
-
-pub fn hello_world_rust_editor_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
-    EditorPane::new(
-        cx,
-        static_tabs_example(),
-        PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
-        vec![Symbol(vec![
-            HighlightedText {
-                text: "fn ".into(),
-                color: cx.theme().syntax_color("keyword"),
-            },
-            HighlightedText {
-                text: "main".into(),
-                color: cx.theme().syntax_color("function"),
-            },
-        ])],
-        hello_world_rust_buffer_example(cx),
-    )
-}
-
-pub fn hello_world_rust_buffer_example(cx: &AppContext) -> Buffer {
-    Buffer::new("hello-world-rust-buffer")
-        .set_title("hello_world.rs".to_string())
-        .set_path("src/hello_world.rs".to_string())
-        .set_language("rust".to_string())
-        .set_rows(Some(BufferRows {
-            show_line_numbers: true,
-            rows: hello_world_rust_buffer_rows(cx),
-        }))
-}
-
-pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
-    let show_line_number = true;
-
-    vec![
-        BufferRow {
-            line_number: 1,
-            code_action: false,
-            current: true,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![
-                    HighlightedText {
-                        text: "fn ".into(),
-                        color: cx.theme().syntax_color("keyword"),
-                    },
-                    HighlightedText {
-                        text: "main".into(),
-                        color: cx.theme().syntax_color("function"),
-                    },
-                    HighlightedText {
-                        text: "() {".into(),
-                        color: cx.theme().colors().text,
-                    },
-                ],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 2,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "    // Statements here are executed when the compiled binary is called."
-                        .into(),
-                    color: cx.theme().syntax_color("comment"),
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 3,
-            code_action: false,
-            current: false,
-            line: None,
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 4,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "    // Print text to the console.".into(),
-                    color: cx.theme().syntax_color("comment"),
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 5,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![
-                    HighlightedText {
-                        text: "    println!(".into(),
-                        color: cx.theme().colors().text,
-                    },
-                    HighlightedText {
-                        text: "\"Hello, world!\"".into(),
-                        color: cx.theme().syntax_color("string"),
-                    },
-                    HighlightedText {
-                        text: ");".into(),
-                        color: cx.theme().colors().text,
-                    },
-                ],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 6,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "}".into(),
-                    color: cx.theme().colors().text,
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-    ]
-}
-
-pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
-    EditorPane::new(
-        cx,
-        static_tabs_example(),
-        PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
-        vec![Symbol(vec![
-            HighlightedText {
-                text: "fn ".into(),
-                color: cx.theme().syntax_color("keyword"),
-            },
-            HighlightedText {
-                text: "main".into(),
-                color: cx.theme().syntax_color("function"),
-            },
-        ])],
-        hello_world_rust_buffer_with_status_example(cx),
-    )
-}
-
-pub fn hello_world_rust_buffer_with_status_example(cx: &AppContext) -> Buffer {
-    Buffer::new("hello-world-rust-buffer-with-status")
-        .set_title("hello_world.rs".to_string())
-        .set_path("src/hello_world.rs".to_string())
-        .set_language("rust".to_string())
-        .set_rows(Some(BufferRows {
-            show_line_numbers: true,
-            rows: hello_world_rust_with_status_buffer_rows(cx),
-        }))
-}
-
-pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
-    let show_line_number = true;
-
-    vec![
-        BufferRow {
-            line_number: 1,
-            code_action: false,
-            current: true,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![
-                    HighlightedText {
-                        text: "fn ".into(),
-                        color: cx.theme().syntax_color("keyword"),
-                    },
-                    HighlightedText {
-                        text: "main".into(),
-                        color: cx.theme().syntax_color("function"),
-                    },
-                    HighlightedText {
-                        text: "() {".into(),
-                        color: cx.theme().colors().text,
-                    },
-                ],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 2,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "// Statements here are executed when the compiled binary is called."
-                        .into(),
-                    color: cx.theme().syntax_color("comment"),
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::Modified,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 3,
-            code_action: false,
-            current: false,
-            line: None,
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 4,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "    // Print text to the console.".into(),
-                    color: cx.theme().syntax_color("comment"),
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 5,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![
-                    HighlightedText {
-                        text: "    println!(".into(),
-                        color: cx.theme().colors().text,
-                    },
-                    HighlightedText {
-                        text: "\"Hello, world!\"".into(),
-                        color: cx.theme().syntax_color("string"),
-                    },
-                    HighlightedText {
-                        text: ");".into(),
-                        color: cx.theme().colors().text,
-                    },
-                ],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 6,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "}".into(),
-                    color: cx.theme().colors().text,
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 7,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "".into(),
-                    color: cx.theme().colors().text,
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::Created,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 8,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "// Marshall and Nate were here".into(),
-                    color: cx.theme().syntax_color("comment"),
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::Created,
-            show_line_number,
-        },
-    ]
-}
-
-pub fn terminal_buffer(cx: &AppContext) -> Buffer {
-    Buffer::new("terminal")
-        .set_title(Some("zed — fish".into()))
-        .set_rows(Some(BufferRows {
-            show_line_numbers: false,
-            rows: terminal_buffer_rows(cx),
-        }))
-}
-
-pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
-    let show_line_number = false;
-
-    vec![
-        BufferRow {
-            line_number: 1,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![
-                    HighlightedText {
-                        text: "maxdeviant ".into(),
-                        color: cx.theme().syntax_color("keyword"),
-                    },
-                    HighlightedText {
-                        text: "in ".into(),
-                        color: cx.theme().colors().text,
-                    },
-                    HighlightedText {
-                        text: "profaned-capital ".into(),
-                        color: cx.theme().syntax_color("function"),
-                    },
-                    HighlightedText {
-                        text: "in ".into(),
-                        color: cx.theme().colors().text,
-                    },
-                    HighlightedText {
-                        text: "~/p/zed ".into(),
-                        color: cx.theme().syntax_color("function"),
-                    },
-                    HighlightedText {
-                        text: "on ".into(),
-                        color: cx.theme().colors().text,
-                    },
-                    HighlightedText {
-                        text: " gpui2-ui ".into(),
-                        color: cx.theme().syntax_color("keyword"),
-                    },
-                ],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-        BufferRow {
-            line_number: 2,
-            code_action: false,
-            current: false,
-            line: Some(HighlightedLine {
-                highlighted_texts: vec![HighlightedText {
-                    text: "λ ".into(),
-                    color: cx.theme().syntax_color("string"),
-                }],
-            }),
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number,
-        },
-    ]
-}

crates/ui2/src/story.rs 🔗

@@ -1,43 +0,0 @@
-use gpui::Div;
-
-use crate::prelude::*;
-
-pub struct Story {}
-
-impl Story {
-    pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
-        div()
-            .size_full()
-            .flex()
-            .flex_col()
-            .pt_2()
-            .px_4()
-            .bg(cx.theme().colors().background)
-    }
-
-    pub fn title<V: 'static>(
-        cx: &mut ViewContext<V>,
-        title: impl Into<SharedString>,
-    ) -> impl Element<V> {
-        div()
-            .text_xl()
-            .text_color(cx.theme().colors().text)
-            .child(title.into())
-    }
-
-    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
-        Self::title(cx, std::any::type_name::<T>())
-    }
-
-    pub fn label<V: 'static>(
-        cx: &mut ViewContext<V>,
-        label: impl Into<SharedString>,
-    ) -> impl Element<V> {
-        div()
-            .mt_4()
-            .mb_2()
-            .text_xs()
-            .text_color(cx.theme().colors().text)
-            .child(label.into())
-    }
-}

crates/ui2/src/styled_ext.rs 🔗

@@ -1,9 +1,9 @@
-use gpui::{Styled, ViewContext};
+use gpui::{Styled, WindowContext};
 use theme2::ActiveTheme;
 
 use crate::{ElevationIndex, UITextSize};
 
-fn elevated<E: Styled, V: 'static>(this: E, cx: &mut ViewContext<V>, index: ElevationIndex) -> E {
+fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
     this.bg(cx.theme().colors().elevated_surface_background)
         .z_index(index.z_index())
         .rounded_lg()
@@ -65,7 +65,7 @@ pub trait StyledExt: Styled + Sized {
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
     /// Example Elements: Title Bar, Panel, Tab Bar, Editor
-    fn elevation_1<V: 'static>(self, cx: &mut ViewContext<V>) -> Self {
+    fn elevation_1(self, cx: &mut WindowContext) -> Self {
         elevated(self, cx, ElevationIndex::Surface)
     }
 
@@ -74,12 +74,10 @@ pub trait StyledExt: Styled + Sized {
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
     /// Examples: Notifications, Palettes, Detached/Floating Windows, Detached/Floating Panels
-    fn elevation_2<V: 'static>(self, cx: &mut ViewContext<V>) -> Self {
+    fn elevation_2(self, cx: &mut WindowContext) -> Self {
         elevated(self, cx, ElevationIndex::ElevatedSurface)
     }
 
-    // There is no elevation 3, as the third elevation level is reserved for wash layers. See [`Elevation`](ui2::Elevation).
-
     /// Modal Surfaces are used for elements that should appear above all other UI elements and are located above the wash layer. This is the maximum elevation at which UI elements can be rendered in their default state.
     ///
     /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
@@ -89,7 +87,7 @@ pub trait StyledExt: Styled + Sized {
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
     /// Examples: Settings Modal, Channel Management, Wizards/Setup UI, Dialogs
-    fn elevation_4<V: 'static>(self, cx: &mut ViewContext<V>) -> Self {
+    fn elevation_3(self, cx: &mut WindowContext) -> Self {
         elevated(self, cx, ElevationIndex::ModalSurface)
     }
 }

crates/ui2/src/styles.rs 🔗

@@ -0,0 +1,7 @@
+mod color;
+mod elevation;
+mod typography;
+
+pub use color::*;
+pub use elevation::*;
+pub use typography::*;

crates/ui2/src/styles/color.rs 🔗

@@ -0,0 +1,44 @@
+use gpui::{Hsla, WindowContext};
+use theme2::ActiveTheme;
+
+#[derive(Default, PartialEq, Copy, Clone)]
+pub enum Color {
+    #[default]
+    Default,
+    Accent,
+    Created,
+    Deleted,
+    Disabled,
+    Error,
+    Hidden,
+    Info,
+    Modified,
+    Muted,
+    Placeholder,
+    Player(u32),
+    Selected,
+    Success,
+    Warning,
+}
+
+impl Color {
+    pub fn color(&self, cx: &WindowContext) -> Hsla {
+        match self {
+            Color::Default => cx.theme().colors().text,
+            Color::Muted => cx.theme().colors().text_muted,
+            Color::Created => cx.theme().status().created,
+            Color::Modified => cx.theme().status().modified,
+            Color::Deleted => cx.theme().status().deleted,
+            Color::Disabled => cx.theme().colors().text_disabled,
+            Color::Hidden => cx.theme().status().hidden,
+            Color::Info => cx.theme().status().info,
+            Color::Placeholder => cx.theme().colors().text_placeholder,
+            Color::Accent => cx.theme().colors().text_accent,
+            Color::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor,
+            Color::Error => cx.theme().status().error,
+            Color::Selected => cx.theme().colors().text_accent,
+            Color::Success => cx.theme().status().success,
+            Color::Warning => cx.theme().status().warning,
+        }
+    }
+}

crates/ui2/src/elevation.md → crates/ui2/src/styles/docs/elevation.md 🔗

@@ -1,27 +1,10 @@
-TODO: Originally sourced from Material Design 3, Rewrite to be more Zed specific
-
 # Elevation
 
-Zed applies elevation to all surfaces and components, which are categorized into levels.
-
-Elevation accomplishes the following:
-- Allows surfaces to move in front of or behind others, such as content scrolling beneath app top bars.
-- Reflects spatial relationships, for instance, how a floating action button’s shadow intimates its disconnection from a collection of cards.
-- Directs attention to structures at the highest elevation, like a temporary dialog arising in front of other surfaces.
-
-Elevations are the initial elevation values assigned to components by default.
-
-Components may transition to a higher elevation in some cases, like user interations.
-
-On such occasions, components transition to predetermined dynamic elevation offsets. These are the typical elevations to which components move when they are not at rest.
-
-## Understanding Elevation
-
 Elevation can be thought of as the physical closeness of an element to the user. Elements with lower elevations are physically further away from the user on the z-axis and appear to be underneath elements with higher elevations.
 
 Material Design 3 has a some great visualizations of elevation that may be helpful to understanding the mental modal of elevation. [Material Design – Elevation](https://m3.material.io/styles/elevation/overview)
 
-## Elevation
+## Elevation Levels
 
 1. App Background (e.x.: Workspace, system window)
 1. UI Surface (e.x.: Title Bar, Panel, Tab Bar)
@@ -59,27 +42,3 @@ Modal Surfaces are used for elements that should appear above all other UI eleme
 Elements rendered at this layer have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
 
 If the element does not have this behavior, it should be rendered at the Elevated Surface layer.
-
-## Layer
-Each elevation that can contain elements has its own set of layers that are nested within the elevations.
-
-1. TBD (Z -1 layer)
-1. Element (Text, button, surface, etc)
-1. Elevated Element (Popover, Context Menu, Tooltip)
-999. Dragged Element -> Highest Elevation
-
-Dragged elements jump to the highest elevation the app can render. An active drag should _always_ be the most foreground element in the app at any time.
-
-🚧 Work in Progress 🚧
-
-## Element
-Each elevation that can contain elements has it's own set of layers:
-
-1. Effects
-1. Background
-1. Tint
-1. Highlight
-1. Content
-1. Overlay
-
-🚧 Work in Progress 🚧

crates/ui2/src/elevation.rs → crates/ui2/src/styles/elevation.rs 🔗

@@ -1,7 +1,7 @@
 use gpui::{hsla, point, px, BoxShadow};
 use smallvec::{smallvec, SmallVec};
 
-#[doc = include_str!("elevation.md")]
+#[doc = include_str!("docs/elevation.md")]
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Elevation {
     ElevationIndex(ElevationIndex),
@@ -25,8 +25,8 @@ impl ElevationIndex {
             ElevationIndex::Background => 0,
             ElevationIndex::Surface => 100,
             ElevationIndex::ElevatedSurface => 200,
-            ElevationIndex::Wash => 300,
-            ElevationIndex::ModalSurface => 400,
+            ElevationIndex::Wash => 250,
+            ElevationIndex::ModalSurface => 300,
             ElevationIndex::DraggedElement => 900,
         }
     }
@@ -50,7 +50,7 @@ impl ElevationIndex {
                     spread_radius: px(0.),
                 },
                 BoxShadow {
-                    color: hsla(0., 0., 0., 0.16),
+                    color: hsla(0., 0., 0., 0.20),
                     offset: point(px(3.), px(1.)),
                     blur_radius: px(12.),
                     spread_radius: px(0.),

crates/ui2/src/styles/typography.rs 🔗

@@ -0,0 +1,27 @@
+use gpui::{rems, Rems};
+
+#[derive(Debug, Default, Clone)]
+pub enum UITextSize {
+    /// The default size for UI text.
+    ///
+    /// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
+    ///
+    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
+    #[default]
+    Default,
+    /// The small size for UI text.
+    ///
+    /// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
+    ///
+    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
+    Small,
+}
+
+impl UITextSize {
+    pub fn rems(self) -> Rems {
+        match self {
+            Self::Default => rems(0.875),
+            Self::Small => rems(0.75),
+        }
+    }
+}

crates/ui2/src/to_extract.rs 🔗

@@ -1,47 +0,0 @@
-mod assistant_panel;
-mod breadcrumb;
-mod buffer;
-mod buffer_search;
-mod chat_panel;
-mod collab_panel;
-mod command_palette;
-mod copilot;
-mod editor_pane;
-mod language_selector;
-mod multi_buffer;
-mod notifications_panel;
-mod panes;
-mod project_panel;
-mod recent_projects;
-mod status_bar;
-mod tab_bar;
-mod terminal;
-mod theme_selector;
-mod title_bar;
-mod toolbar;
-mod traffic_lights;
-mod workspace;
-
-pub use assistant_panel::*;
-pub use breadcrumb::*;
-pub use buffer::*;
-pub use buffer_search::*;
-pub use chat_panel::*;
-pub use collab_panel::*;
-pub use command_palette::*;
-pub use copilot::*;
-pub use editor_pane::*;
-pub use language_selector::*;
-pub use multi_buffer::*;
-pub use notifications_panel::*;
-pub use panes::*;
-pub use project_panel::*;
-pub use recent_projects::*;
-pub use status_bar::*;
-pub use tab_bar::*;
-pub use terminal::*;
-pub use theme_selector::*;
-pub use title_bar::*;
-pub use toolbar::*;
-pub use traffic_lights::*;
-pub use workspace::*;

crates/ui2/src/to_extract/assistant_panel.rs 🔗

@@ -1,97 +0,0 @@
-use crate::prelude::*;
-use crate::{Icon, IconButton, Label, Panel, PanelSide};
-use gpui::{prelude::*, rems, AbsoluteLength, RenderOnce};
-
-#[derive(RenderOnce)]
-pub struct AssistantPanel {
-    id: ElementId,
-    current_side: PanelSide,
-}
-
-impl<V: 'static> Component<V> for AssistantPanel {
-    type Rendered = Panel<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        Panel::new(self.id.clone(), cx)
-            .children(vec![div()
-                .flex()
-                .flex_col()
-                .h_full()
-                .px_2()
-                .gap_2()
-                // Header
-                .child(
-                    div()
-                        .flex()
-                        .justify_between()
-                        .gap_2()
-                        .child(
-                            div()
-                                .flex()
-                                .child(IconButton::new("menu", Icon::Menu))
-                                .child(Label::new("New Conversation")),
-                        )
-                        .child(
-                            div()
-                                .flex()
-                                .items_center()
-                                .gap_px()
-                                .child(IconButton::new("split_message", Icon::SplitMessage))
-                                .child(IconButton::new("quote", Icon::Quote))
-                                .child(IconButton::new("magic_wand", Icon::MagicWand))
-                                .child(IconButton::new("plus", Icon::Plus))
-                                .child(IconButton::new("maximize", Icon::Maximize)),
-                        ),
-                )
-                // Chat Body
-                .child(
-                    div()
-                        .id("chat-body")
-                        .w_full()
-                        .flex()
-                        .flex_col()
-                        .gap_3()
-                        .overflow_y_scroll()
-                        .child(Label::new("Is this thing on?")),
-                )
-                .into_any()])
-            .side(self.current_side)
-            .width(AbsoluteLength::Rems(rems(32.)))
-    }
-}
-
-impl AssistantPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            current_side: PanelSide::default(),
-        }
-    }
-
-    pub fn side(mut self, side: PanelSide) -> Self {
-        self.current_side = side;
-        self
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-    pub struct AssistantPanelStory;
-
-    impl Render<Self> for AssistantPanelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, AssistantPanel>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(AssistantPanel::new("assistant-panel"))
-        }
-    }
-}

crates/ui2/src/to_extract/breadcrumb.rs 🔗

@@ -1,118 +0,0 @@
-use crate::{h_stack, prelude::*, HighlightedText};
-use gpui::{prelude::*, Div, Stateful};
-use std::path::PathBuf;
-
-#[derive(Clone)]
-pub struct Symbol(pub Vec<HighlightedText>);
-
-#[derive(RenderOnce)]
-pub struct Breadcrumb {
-    path: PathBuf,
-    symbols: Vec<Symbol>,
-}
-
-impl<V: 'static> Component<V> for Breadcrumb {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view_state: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let symbols_len = self.symbols.len();
-        h_stack()
-            .id("breadcrumb")
-            .px_1()
-            .text_ui_sm()
-            .text_color(cx.theme().colors().text_muted)
-            .rounded_md()
-            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
-            .active(|style| style.bg(cx.theme().colors().ghost_element_active))
-            .child(SharedString::from(
-                self.path.clone().to_str().unwrap().to_string(),
-            ))
-            .child(if !self.symbols.is_empty() {
-                self.render_separator(cx)
-            } else {
-                div()
-            })
-            .child(
-                div().flex().children(
-                    self.symbols
-                        .iter()
-                        .enumerate()
-                        // TODO: Could use something like `intersperse` here instead.
-                        .flat_map(|(ix, symbol)| {
-                            let mut items =
-                                vec![div().flex().children(symbol.0.iter().map(|segment| {
-                                    div().child(segment.text.clone()).text_color(segment.color)
-                                }))];
-
-                            let is_last_segment = ix == symbols_len - 1;
-                            if !is_last_segment {
-                                items.push(self.render_separator(cx));
-                            }
-
-                            items
-                        })
-                        .collect::<Vec<_>>(),
-                ),
-            )
-    }
-}
-
-impl Breadcrumb {
-    pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
-        Self { path, symbols }
-    }
-
-    fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
-        div()
-            .child(" › ")
-            .text_color(cx.theme().colors().text_muted)
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::Render;
-    use std::str::FromStr;
-
-    pub struct BreadcrumbStory;
-
-    impl Render<Self> for BreadcrumbStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Breadcrumb>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Breadcrumb::new(
-                    PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
-                    vec![
-                        Symbol(vec![
-                            HighlightedText {
-                                text: "impl ".into(),
-                                color: cx.theme().syntax_color("keyword"),
-                            },
-                            HighlightedText {
-                                text: "BreadcrumbStory".into(),
-                                color: cx.theme().syntax_color("function"),
-                            },
-                        ]),
-                        Symbol(vec![
-                            HighlightedText {
-                                text: "fn ".into(),
-                                color: cx.theme().syntax_color("keyword"),
-                            },
-                            HighlightedText {
-                                text: "render".into(),
-                                color: cx.theme().syntax_color("function"),
-                            },
-                        ]),
-                    ],
-                ))
-        }
-    }
-}

crates/ui2/src/to_extract/buffer.rs 🔗

@@ -1,281 +0,0 @@
-use gpui::{Div, Hsla, RenderOnce, WindowContext};
-
-use crate::prelude::*;
-use crate::{h_stack, v_stack, Icon, IconElement};
-
-#[derive(Default, PartialEq, Copy, Clone)]
-pub struct PlayerCursor {
-    color: Hsla,
-    index: usize,
-}
-
-#[derive(Default, PartialEq, Clone)]
-pub struct HighlightedText {
-    pub text: SharedString,
-    pub color: Hsla,
-}
-
-#[derive(Default, PartialEq, Clone)]
-pub struct HighlightedLine {
-    pub highlighted_texts: Vec<HighlightedText>,
-}
-
-#[derive(Default, PartialEq, Clone)]
-pub struct BufferRow {
-    pub line_number: usize,
-    pub code_action: bool,
-    pub current: bool,
-    pub line: Option<HighlightedLine>,
-    pub cursors: Option<Vec<PlayerCursor>>,
-    pub status: GitStatus,
-    pub show_line_number: bool,
-}
-
-#[derive(Clone)]
-pub struct BufferRows {
-    pub show_line_numbers: bool,
-    pub rows: Vec<BufferRow>,
-}
-
-impl Default for BufferRows {
-    fn default() -> Self {
-        Self {
-            show_line_numbers: true,
-            rows: vec![BufferRow {
-                line_number: 1,
-                code_action: false,
-                current: true,
-                line: None,
-                cursors: None,
-                status: GitStatus::None,
-                show_line_number: true,
-            }],
-        }
-    }
-}
-
-impl BufferRow {
-    pub fn new(line_number: usize) -> Self {
-        Self {
-            line_number,
-            code_action: false,
-            current: false,
-            line: None,
-            cursors: None,
-            status: GitStatus::None,
-            show_line_number: true,
-        }
-    }
-
-    pub fn set_line(mut self, line: Option<HighlightedLine>) -> Self {
-        self.line = line;
-        self
-    }
-
-    pub fn set_cursors(mut self, cursors: Option<Vec<PlayerCursor>>) -> Self {
-        self.cursors = cursors;
-        self
-    }
-
-    pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self {
-        if let Some(cursors) = &mut self.cursors {
-            cursors.push(cursor);
-        } else {
-            self.cursors = Some(vec![cursor]);
-        }
-        self
-    }
-
-    pub fn set_status(mut self, status: GitStatus) -> Self {
-        self.status = status;
-        self
-    }
-
-    pub fn set_show_line_number(mut self, show_line_number: bool) -> Self {
-        self.show_line_number = show_line_number;
-        self
-    }
-
-    pub fn set_code_action(mut self, code_action: bool) -> Self {
-        self.code_action = code_action;
-        self
-    }
-
-    pub fn set_current(mut self, current: bool) -> Self {
-        self.current = current;
-        self
-    }
-}
-
-#[derive(RenderOnce, Clone)]
-pub struct Buffer {
-    id: ElementId,
-    rows: Option<BufferRows>,
-    readonly: bool,
-    language: Option<String>,
-    title: Option<String>,
-    path: Option<String>,
-}
-
-impl<V: 'static> Component<V> for Buffer {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let rows = self.render_rows(cx);
-
-        v_stack()
-            .flex_1()
-            .w_full()
-            .h_full()
-            .bg(cx.theme().colors().editor_background)
-            .children(rows)
-    }
-}
-
-impl Buffer {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            rows: Some(BufferRows::default()),
-            readonly: false,
-            language: None,
-            title: Some("untitled".to_string()),
-            path: None,
-        }
-    }
-
-    pub fn set_title<T: Into<Option<String>>>(mut self, title: T) -> Self {
-        self.title = title.into();
-        self
-    }
-
-    pub fn set_path<P: Into<Option<String>>>(mut self, path: P) -> Self {
-        self.path = path.into();
-        self
-    }
-
-    pub fn set_readonly(mut self, readonly: bool) -> Self {
-        self.readonly = readonly;
-        self
-    }
-
-    pub fn set_rows<R: Into<Option<BufferRows>>>(mut self, rows: R) -> Self {
-        self.rows = rows.into();
-        self
-    }
-
-    pub fn set_language<L: Into<Option<String>>>(mut self, language: L) -> Self {
-        self.language = language.into();
-        self
-    }
-
-    fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Element<V> {
-        let line_background = if row.current {
-            cx.theme().colors().editor_active_line_background
-        } else {
-            cx.theme().styles.system.transparent
-        };
-
-        let line_number_color = if row.current {
-            cx.theme().colors().text
-        } else {
-            cx.theme().syntax_color("comment")
-        };
-
-        h_stack()
-            .bg(line_background)
-            .w_full()
-            .gap_2()
-            .px_1()
-            .child(
-                h_stack()
-                    .w_4()
-                    .h_full()
-                    .px_0p5()
-                    .when(row.code_action, |c| {
-                        div().child(IconElement::new(Icon::Bolt))
-                    }),
-            )
-            .when(row.show_line_number, |this| {
-                this.child(
-                    h_stack().justify_end().px_0p5().w_3().child(
-                        div()
-                            .text_color(line_number_color)
-                            .child(SharedString::from(row.line_number.to_string())),
-                    ),
-                )
-            })
-            .child(div().mx_0p5().w_1().h_full().bg(row.status.hsla(cx)))
-            .children(row.line.map(|line| {
-                div()
-                    .flex()
-                    .children(line.highlighted_texts.iter().map(|highlighted_text| {
-                        div()
-                            .text_color(highlighted_text.color)
-                            .child(highlighted_text.text.clone())
-                    }))
-            }))
-    }
-
-    fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Element<V>> {
-        match &self.rows {
-            Some(rows) => rows
-                .rows
-                .iter()
-                .map(|row| Self::render_row(row.clone(), cx))
-                .collect(),
-            None => vec![],
-        }
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        let rows = self.render_rows(cx);
-
-        v_stack()
-            .flex_1()
-            .w_full()
-            .h_full()
-            .bg(cx.theme().colors().editor_background)
-            .children(rows)
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{
-        empty_buffer_example, hello_world_rust_buffer_example,
-        hello_world_rust_buffer_with_status_example, Story,
-    };
-    use gpui::{rems, Div, Render};
-
-    pub struct BufferStory;
-
-    impl Render<Self> for BufferStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Buffer>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
-                .child(Story::label(cx, "Hello World (Rust)"))
-                .child(
-                    div()
-                        .w(rems(64.))
-                        .h_96()
-                        .child(hello_world_rust_buffer_example(cx)),
-                )
-                .child(Story::label(cx, "Hello World (Rust) with Status"))
-                .child(
-                    div()
-                        .w(rems(64.))
-                        .h_96()
-                        .child(hello_world_rust_buffer_with_status_example(cx)),
-                )
-        }
-    }
-}

crates/ui2/src/to_extract/buffer_search.rs 🔗

@@ -1,45 +0,0 @@
-use crate::prelude::*;
-use crate::{h_stack, Icon, IconButton, Input, TextColor};
-use gpui::{Div, Render, RenderOnce, View, VisualContext};
-
-#[derive(Clone)]
-pub struct BufferSearch {
-    is_replace_open: bool,
-}
-
-impl BufferSearch {
-    pub fn new() -> Self {
-        Self {
-            is_replace_open: false,
-        }
-    }
-
-    fn toggle_replace(&mut self, cx: &mut ViewContext<Self>) {
-        self.is_replace_open = !self.is_replace_open;
-
-        cx.notify();
-    }
-
-    pub fn view(cx: &mut WindowContext) -> View<Self> {
-        cx.build_view(|cx| Self::new())
-    }
-}
-
-impl Render<Self> for BufferSearch {
-    type Element = Div<Self>;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        h_stack()
-            .bg(cx.theme().colors().toolbar_background)
-            .p_2()
-            .child(
-                h_stack().child(Input::new("Search")).child(
-                    IconButton::<Self>::new("replace", Icon::Replace)
-                        .when(self.is_replace_open, |this| this.color(TextColor::Accent))
-                        .on_click(|buffer_search, cx| {
-                            buffer_search.toggle_replace(cx);
-                        }),
-                ),
-            )
-    }
-}

crates/ui2/src/to_extract/chat_panel.rs 🔗

@@ -1,158 +0,0 @@
-use crate::{prelude::*, Icon, IconButton, Input, Label};
-use chrono::NaiveDateTime;
-use gpui::{prelude::*, Div, Stateful};
-
-#[derive(RenderOnce)]
-pub struct ChatPanel {
-    element_id: ElementId,
-    messages: Vec<ChatMessage>,
-}
-
-impl<V: 'static> Component<V> for ChatPanel {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .id(self.element_id.clone())
-            .flex()
-            .flex_col()
-            .justify_between()
-            .h_full()
-            .px_2()
-            .gap_2()
-            // Header
-            .child(
-                div()
-                    .flex()
-                    .justify_between()
-                    .py_2()
-                    .child(div().flex().child(Label::new("#design")))
-                    .child(
-                        div()
-                            .flex()
-                            .items_center()
-                            .gap_px()
-                            .child(IconButton::new("file", Icon::File))
-                            .child(IconButton::new("audio_on", Icon::AudioOn)),
-                    ),
-            )
-            .child(
-                div()
-                    .flex()
-                    .flex_col()
-                    // Chat Body
-                    .child(
-                        div()
-                            .id("chat-body")
-                            .w_full()
-                            .flex()
-                            .flex_col()
-                            .gap_3()
-                            .overflow_y_scroll()
-                            .children(self.messages),
-                    )
-                    // Composer
-                    .child(div().flex().my_2().child(Input::new("Message #design"))),
-            )
-    }
-}
-
-impl ChatPanel {
-    pub fn new(element_id: impl Into<ElementId>) -> Self {
-        Self {
-            element_id: element_id.into(),
-            messages: Vec::new(),
-        }
-    }
-
-    pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
-        self.messages = messages;
-        self
-    }
-}
-
-#[derive(RenderOnce)]
-pub struct ChatMessage {
-    author: String,
-    text: String,
-    sent_at: NaiveDateTime,
-}
-
-impl<V: 'static> Component<V> for ChatMessage {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .flex()
-            .flex_col()
-            .child(
-                div()
-                    .flex()
-                    .gap_2()
-                    .child(Label::new(self.author.clone()))
-                    .child(
-                        Label::new(self.sent_at.format("%m/%d/%Y").to_string())
-                            .color(TextColor::Muted),
-                    ),
-            )
-            .child(div().child(Label::new(self.text.clone())))
-    }
-}
-
-impl ChatMessage {
-    pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
-        Self {
-            author,
-            text,
-            sent_at,
-        }
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use chrono::DateTime;
-    use gpui::{Div, Render};
-
-    use crate::{Panel, Story};
-
-    use super::*;
-
-    pub struct ChatPanelStory;
-
-    impl Render<Self> for ChatPanelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, ChatPanel>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    Panel::new("chat-panel-1-outer", cx)
-                        .child(ChatPanel::new("chat-panel-1-inner")),
-                )
-                .child(Story::label(cx, "With Mesages"))
-                .child(Panel::new("chat-panel-2-outer", cx).child(
-                    ChatPanel::new("chat-panel-2-inner").messages(vec![
-                        ChatMessage::new(
-                            "osiewicz".to_string(),
-                            "is this thing on?".to_string(),
-                            DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
-                                .unwrap()
-                                .naive_local(),
-                        ),
-                        ChatMessage::new(
-                            "maxdeviant".to_string(),
-                            "Reading you loud and clear!".to_string(),
-                            DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
-                                .unwrap()
-                                .naive_local(),
-                        ),
-                    ]),
-                ))
-        }
-    }
-}

crates/ui2/src/to_extract/collab_panel.rs 🔗

@@ -1,118 +0,0 @@
-use crate::{
-    prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon,
-    List, ListHeader, Toggle,
-};
-use gpui::{prelude::*, Div, Stateful};
-
-#[derive(RenderOnce)]
-pub struct CollabPanel {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for CollabPanel {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        v_stack()
-            .id(self.id.clone())
-            .h_full()
-            .bg(cx.theme().colors().surface_background)
-            .child(
-                v_stack()
-                    .id("crdb")
-                    .w_full()
-                    .overflow_y_scroll()
-                    .child(
-                        div()
-                            .pb_1()
-                            .border_color(cx.theme().colors().border)
-                            .border_b()
-                            .child(
-                                List::new()
-                                    .header(
-                                        ListHeader::new("CRDB")
-                                            .left_icon(Icon::Hash.into())
-                                            .toggle(Toggle::Toggled(true)),
-                                    )
-                                    .toggle(Toggle::Toggled(true))
-                                    .children(static_collab_panel_current_call()),
-                            ),
-                    )
-                    .child(
-                        v_stack().id("channels").py_1().child(
-                            List::new()
-                                .header(ListHeader::new("CHANNELS").toggle(Toggle::Toggled(true)))
-                                .empty_message("No channels yet. Add a channel to get started.")
-                                .toggle(Toggle::Toggled(true))
-                                .children(static_collab_panel_channels()),
-                        ),
-                    )
-                    .child(
-                        v_stack().id("contacts-online").py_1().child(
-                            List::new()
-                                .header(
-                                    ListHeader::new("CONTACTS – ONLINE")
-                                        .toggle(Toggle::Toggled(true)),
-                                )
-                                .toggle(Toggle::Toggled(true))
-                                .children(static_collab_panel_current_call()),
-                        ),
-                    )
-                    .child(
-                        v_stack().id("contacts-offline").py_1().child(
-                            List::new()
-                                .header(
-                                    ListHeader::new("CONTACTS – OFFLINE")
-                                        .toggle(Toggle::Toggled(false)),
-                                )
-                                .toggle(Toggle::Toggled(false))
-                                .children(static_collab_panel_current_call()),
-                        ),
-                    ),
-            )
-            .child(
-                div()
-                    .h_7()
-                    .px_2()
-                    .border_t()
-                    .border_color(cx.theme().colors().border)
-                    .flex()
-                    .items_center()
-                    .child(
-                        div()
-                            .text_ui_sm()
-                            .text_color(cx.theme().colors().text_placeholder)
-                            .child("Find..."),
-                    ),
-            )
-    }
-}
-
-impl CollabPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct CollabPanelStory;
-
-    impl Render<Self> for CollabPanelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, CollabPanel>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(CollabPanel::new("collab-panel"))
-        }
-    }
-}

crates/ui2/src/to_extract/command_palette.rs 🔗

@@ -1,53 +0,0 @@
-use crate::prelude::*;
-use crate::{example_editor_actions, OrderMethod, Palette};
-
-#[derive(RenderOnce)]
-pub struct CommandPalette {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for CommandPalette {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div().id(self.id.clone()).child(
-            Palette::new("palette")
-                .items(example_editor_actions())
-                .placeholder("Execute a command...")
-                .empty_string("No items found.")
-                .default_order(OrderMethod::Ascending),
-        )
-    }
-}
-
-impl CommandPalette {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-}
-
-use gpui::{Div, RenderOnce, Stateful};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use gpui::{Div, Render};
-
-    use crate::Story;
-
-    use super::*;
-
-    pub struct CommandPaletteStory;
-
-    impl Render<Self> for CommandPaletteStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, CommandPalette>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(CommandPalette::new("command-palette"))
-        }
-    }
-}

crates/ui2/src/to_extract/copilot.rs 🔗

@@ -1,49 +0,0 @@
-use crate::{prelude::*, Button, Label, Modal, TextColor};
-
-#[derive(RenderOnce)]
-pub struct CopilotModal {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for CopilotModal {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div().id(self.id.clone()).child(
-                Modal::new("some-id")
-                    .title("Connect Copilot to Zed")
-                    .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted))
-                    .primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
-            )
-    }
-}
-
-impl CopilotModal {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-}
-
-use gpui::{Div, RenderOnce, Stateful};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct CopilotModalStory;
-
-    impl Render<Self> for CopilotModalStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, CopilotModal>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(CopilotModal::new("copilot-modal"))
-        }
-    }
-}

crates/ui2/src/to_extract/editor_pane.rs 🔗

@@ -1,77 +0,0 @@
-use std::path::PathBuf;
-
-use gpui::{Div, Render, RenderOnce, View, VisualContext};
-
-use crate::prelude::*;
-use crate::{
-    hello_world_rust_editor_with_status_example, v_stack, Breadcrumb, Buffer, BufferSearch, Icon,
-    IconButton, Symbol, Tab, TabBar, TextColor, Toolbar,
-};
-
-#[derive(Clone)]
-pub struct EditorPane {
-    tabs: Vec<Tab>,
-    path: PathBuf,
-    symbols: Vec<Symbol>,
-    buffer: Buffer,
-    buffer_search: View<BufferSearch>,
-    is_buffer_search_open: bool,
-}
-
-impl EditorPane {
-    pub fn new(
-        cx: &mut ViewContext<Self>,
-        tabs: Vec<Tab>,
-        path: PathBuf,
-        symbols: Vec<Symbol>,
-        buffer: Buffer,
-    ) -> Self {
-        Self {
-            tabs,
-            path,
-            symbols,
-            buffer,
-            buffer_search: BufferSearch::view(cx),
-            is_buffer_search_open: false,
-        }
-    }
-
-    pub fn toggle_buffer_search(&mut self, cx: &mut ViewContext<Self>) {
-        self.is_buffer_search_open = !self.is_buffer_search_open;
-
-        cx.notify();
-    }
-
-    pub fn view(cx: &mut WindowContext) -> View<Self> {
-        cx.build_view(|cx| hello_world_rust_editor_with_status_example(cx))
-    }
-}
-
-impl Render<Self> for EditorPane {
-    type Element = Div<Self>;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        v_stack()
-            .w_full()
-            .h_full()
-            .flex_1()
-            .child(TabBar::new("editor-pane-tabs", self.tabs.clone()).can_navigate((false, true)))
-            .child(
-                Toolbar::new()
-                    .left_item(Breadcrumb::new(self.path.clone(), self.symbols.clone()))
-                    .right_items(vec![
-                        IconButton::<Self>::new("toggle_inlay_hints", Icon::InlayHint),
-                        IconButton::<Self>::new("buffer_search", Icon::MagnifyingGlass)
-                            .when(self.is_buffer_search_open, |this| {
-                                this.color(TextColor::Accent)
-                            })
-                            .on_click(|editor: &mut Self, cx| {
-                                editor.toggle_buffer_search(cx);
-                            }),
-                        IconButton::new("inline_assist", Icon::MagicWand),
-                    ]),
-            )
-            .children(Some(self.buffer_search.clone()).filter(|_| self.is_buffer_search_open))
-            .child(self.buffer.clone())
-    }
-}

crates/ui2/src/to_extract/language_selector.rs 🔗

@@ -1,83 +0,0 @@
-use crate::prelude::*;
-use crate::{OrderMethod, Palette, PaletteItem};
-
-#[derive(RenderOnce)]
-pub struct LanguageSelector {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for LanguageSelector {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div().id(self.id.clone()).child(
-            Palette::new("palette")
-                .items(vec![
-                    PaletteItem::new("C"),
-                    PaletteItem::new("C++"),
-                    PaletteItem::new("CSS"),
-                    PaletteItem::new("Elixir"),
-                    PaletteItem::new("Elm"),
-                    PaletteItem::new("ERB"),
-                    PaletteItem::new("Rust (current)"),
-                    PaletteItem::new("Scheme"),
-                    PaletteItem::new("TOML"),
-                    PaletteItem::new("TypeScript"),
-                ])
-                .placeholder("Select a language...")
-                .empty_string("No matches")
-                .default_order(OrderMethod::Ascending),
-        )
-    }
-}
-
-impl LanguageSelector {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div().id(self.id.clone()).child(
-            Palette::new("palette")
-                .items(vec![
-                    PaletteItem::new("C"),
-                    PaletteItem::new("C++"),
-                    PaletteItem::new("CSS"),
-                    PaletteItem::new("Elixir"),
-                    PaletteItem::new("Elm"),
-                    PaletteItem::new("ERB"),
-                    PaletteItem::new("Rust (current)"),
-                    PaletteItem::new("Scheme"),
-                    PaletteItem::new("TOML"),
-                    PaletteItem::new("TypeScript"),
-                ])
-                .placeholder("Select a language...")
-                .empty_string("No matches")
-                .default_order(OrderMethod::Ascending),
-        )
-    }
-}
-
-use gpui::{Div, RenderOnce, Stateful};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct LanguageSelectorStory;
-
-    impl Render<Self> for LanguageSelectorStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, LanguageSelector>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(LanguageSelector::new("language-selector"))
-        }
-    }
-}

crates/ui2/src/to_extract/multi_buffer.rs 🔗

@@ -1,68 +0,0 @@
-use crate::prelude::*;
-use crate::{v_stack, Buffer, Icon, IconButton, Label};
-
-#[derive(RenderOnce)]
-pub struct MultiBuffer {
-    buffers: Vec<Buffer>,
-}
-
-impl<V: 'static> Component<V> for MultiBuffer {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        v_stack()
-            .w_full()
-            .h_full()
-            .flex_1()
-            .children(self.buffers.clone().into_iter().map(|buffer| {
-                v_stack()
-                    .child(
-                        div()
-                            .flex()
-                            .items_center()
-                            .justify_between()
-                            .p_4()
-                            .bg(cx.theme().colors().editor_subheader_background)
-                            .child(Label::new("main.rs"))
-                            .child(IconButton::new("arrow_up_right", Icon::ArrowUpRight)),
-                    )
-                    .child(buffer)
-            }))
-    }
-}
-
-impl MultiBuffer {
-    pub fn new(buffers: Vec<Buffer>) -> Self {
-        Self { buffers }
-    }
-}
-
-use gpui::{Div, RenderOnce};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{hello_world_rust_buffer_example, Story};
-    use gpui::{Div, Render};
-
-    pub struct MultiBufferStory;
-
-    impl Render<Self> for MultiBufferStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, MultiBuffer>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(MultiBuffer::new(vec![
-                    hello_world_rust_buffer_example(cx),
-                    hello_world_rust_buffer_example(cx),
-                    hello_world_rust_buffer_example(cx),
-                    hello_world_rust_buffer_example(cx),
-                    hello_world_rust_buffer_example(cx),
-                ]))
-        }
-    }
-}

crates/ui2/src/to_extract/notifications_panel.rs 🔗

@@ -1,379 +0,0 @@
-use crate::{
-    h_stack, prelude::*, static_new_notification_items_2, utils::naive_format_distance_from_now,
-    v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle,
-    ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator,
-};
-use gpui::{prelude::*, Div, Stateful};
-
-#[derive(RenderOnce)]
-pub struct NotificationsPanel {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for NotificationsPanel {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .id(self.id.clone())
-            .flex()
-            .flex_col()
-            .size_full()
-            .bg(cx.theme().colors().surface_background)
-            .child(
-                ListHeader::new("Notifications").meta(Some(ListHeaderMeta::Tools(vec![
-                    Icon::AtSign,
-                    Icon::BellOff,
-                    Icon::MailOpen,
-                ]))),
-            )
-            .child(ListSeparator::new())
-            .child(
-                v_stack()
-                    .id("notifications-panel-scroll-view")
-                    .py_1()
-                    .overflow_y_scroll()
-                    .flex_1()
-                    .child(
-                        div()
-                            .mx_2()
-                            .p_1()
-                            // TODO: Add cursor style
-                            // .cursor(Cursor::IBeam)
-                            .bg(cx.theme().colors().element_background)
-                            .border()
-                            .border_color(cx.theme().colors().border_variant)
-                            .child(
-                                Label::new("Search...")
-                                    .color(TextColor::Placeholder)
-                                    .line_height_style(LineHeightStyle::UILabel),
-                            ),
-                    )
-                    .child(v_stack().px_1().children(static_new_notification_items_2())),
-            )
-    }
-}
-
-impl NotificationsPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-}
-
-pub struct NotificationAction<V: 'static> {
-    button: ButtonOrIconButton<V>,
-    tooltip: SharedString,
-    /// Shows after action is chosen
-    ///
-    /// For example, if the action is "Accept" the taken message could be:
-    ///
-    /// - `(None,"Accepted")` - "Accepted"
-    ///
-    /// - `(Some(Icon::Check),"Accepted")` - ✓ "Accepted"
-    taken_message: (Option<Icon>, SharedString),
-}
-
-impl<V: 'static> NotificationAction<V> {
-    pub fn new(
-        button: impl Into<ButtonOrIconButton<V>>,
-        tooltip: impl Into<SharedString>,
-        (icon, taken_message): (Option<Icon>, impl Into<SharedString>),
-    ) -> Self {
-        Self {
-            button: button.into(),
-            tooltip: tooltip.into(),
-            taken_message: (icon, taken_message.into()),
-        }
-    }
-}
-
-pub enum ActorOrIcon {
-    Actor(PublicPlayer),
-    Icon(Icon),
-}
-
-pub struct NotificationMeta<V: 'static> {
-    items: Vec<(Option<Icon>, SharedString, Option<ClickHandler<V>>)>,
-}
-
-struct NotificationHandlers<V: 'static> {
-    click: Option<ClickHandler<V>>,
-}
-
-impl<V: 'static> Default for NotificationHandlers<V> {
-    fn default() -> Self {
-        Self { click: None }
-    }
-}
-
-#[derive(RenderOnce)]
-pub struct Notification<V: 'static> {
-    id: ElementId,
-    slot: ActorOrIcon,
-    message: SharedString,
-    date_received: NaiveDateTime,
-    meta: Option<NotificationMeta<V>>,
-    actions: Option<[NotificationAction<V>; 2]>,
-    unread: bool,
-    new: bool,
-    action_taken: Option<NotificationAction<V>>,
-    handlers: NotificationHandlers<V>,
-}
-
-impl<V: 'static> Component<V> for Notification<V> {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .relative()
-            .id(self.id.clone())
-            .p_1()
-            .flex()
-            .flex_col()
-            .w_full()
-            .children(
-                Some(
-                    div()
-                        .absolute()
-                        .left(px(3.0))
-                        .top_3()
-                        .z_index(2)
-                        .child(UnreadIndicator::new()),
-                )
-                .filter(|_| self.unread),
-            )
-            .child(
-                v_stack()
-                    .z_index(1)
-                    .gap_1()
-                    .w_full()
-                    .child(
-                        h_stack()
-                            .w_full()
-                            .gap_2()
-                            .child(self.render_slot(cx))
-                            .child(div().flex_1().child(Label::new(self.message.clone()))),
-                    )
-                    .child(
-                        h_stack()
-                            .justify_between()
-                            .child(
-                                h_stack()
-                                    .gap_1()
-                                    .child(
-                                        Label::new(naive_format_distance_from_now(
-                                            self.date_received,
-                                            true,
-                                            true,
-                                        ))
-                                        .color(TextColor::Muted),
-                                    )
-                                    .child(self.render_meta_items(cx)),
-                            )
-                            .child(match (self.actions, self.action_taken) {
-                                // Show nothing
-                                (None, _) => div(),
-                                // Show the taken_message
-                                (Some(_), Some(action_taken)) => h_stack()
-                                    .children(action_taken.taken_message.0.map(|icon| {
-                                        IconElement::new(icon).color(crate::TextColor::Muted)
-                                    }))
-                                    .child(
-                                        Label::new(action_taken.taken_message.1.clone())
-                                            .color(TextColor::Muted),
-                                    ),
-                                // Show the actions
-                                (Some(actions), None) => {
-                                    h_stack().children(actions.map(|action| match action.button {
-                                        ButtonOrIconButton::Button(button) => {
-                                            button.render_into_any()
-                                        }
-                                        ButtonOrIconButton::IconButton(icon_button) => {
-                                            icon_button.render_into_any()
-                                        }
-                                    }))
-                                }
-                            }),
-                    ),
-            )
-    }
-}
-
-impl<V> Notification<V> {
-    fn new(
-        id: ElementId,
-        message: SharedString,
-        date_received: NaiveDateTime,
-        slot: ActorOrIcon,
-        click_action: Option<ClickHandler<V>>,
-    ) -> Self {
-        let handlers = if click_action.is_some() {
-            NotificationHandlers {
-                click: click_action,
-            }
-        } else {
-            NotificationHandlers::default()
-        };
-
-        Self {
-            id,
-            date_received,
-            message,
-            meta: None,
-            slot,
-            actions: None,
-            unread: true,
-            new: false,
-            action_taken: None,
-            handlers,
-        }
-    }
-
-    /// Creates a new notification with an actor slot.
-    ///
-    /// Requires a click action.
-    pub fn new_actor_message(
-        id: impl Into<ElementId>,
-        message: impl Into<SharedString>,
-        date_received: NaiveDateTime,
-        actor: PublicPlayer,
-        click_action: ClickHandler<V>,
-    ) -> Self {
-        Self::new(
-            id.into(),
-            message.into(),
-            date_received,
-            ActorOrIcon::Actor(actor),
-            Some(click_action),
-        )
-    }
-
-    /// Creates a new notification with an icon slot.
-    ///
-    /// Requires a click action.
-    pub fn new_icon_message(
-        id: impl Into<ElementId>,
-        message: impl Into<SharedString>,
-        date_received: NaiveDateTime,
-        icon: Icon,
-        click_action: ClickHandler<V>,
-    ) -> Self {
-        Self::new(
-            id.into(),
-            message.into(),
-            date_received,
-            ActorOrIcon::Icon(icon),
-            Some(click_action),
-        )
-    }
-
-    /// Creates a new notification with an actor slot
-    /// and a Call To Action row.
-    ///
-    /// Cannot take a click action due to required actions.
-    pub fn new_actor_with_actions(
-        id: impl Into<ElementId>,
-        message: impl Into<SharedString>,
-        date_received: NaiveDateTime,
-        actor: PublicPlayer,
-        actions: [NotificationAction<V>; 2],
-    ) -> Self {
-        Self::new(
-            id.into(),
-            message.into(),
-            date_received,
-            ActorOrIcon::Actor(actor),
-            None,
-        )
-        .actions(actions)
-    }
-
-    /// Creates a new notification with an icon slot
-    /// and a Call To Action row.
-    ///
-    /// Cannot take a click action due to required actions.
-    pub fn new_icon_with_actions(
-        id: impl Into<ElementId>,
-        message: impl Into<SharedString>,
-        date_received: NaiveDateTime,
-        icon: Icon,
-        actions: [NotificationAction<V>; 2],
-    ) -> Self {
-        Self::new(
-            id.into(),
-            message.into(),
-            date_received,
-            ActorOrIcon::Icon(icon),
-            None,
-        )
-        .actions(actions)
-    }
-
-    fn on_click(mut self, handler: ClickHandler<V>) -> Self {
-        self.handlers.click = Some(handler);
-        self
-    }
-
-    pub fn actions(mut self, actions: [NotificationAction<V>; 2]) -> Self {
-        self.actions = Some(actions);
-        self
-    }
-
-    pub fn meta(mut self, meta: NotificationMeta<V>) -> Self {
-        self.meta = Some(meta);
-        self
-    }
-
-    fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
-        if let Some(meta) = &self.meta {
-            h_stack().children(
-                meta.items
-                    .iter()
-                    .map(|(icon, text, _)| {
-                        let mut meta_el = div();
-                        if let Some(icon) = icon {
-                            meta_el = meta_el.child(IconElement::new(icon.clone()));
-                        }
-                        meta_el.child(Label::new(text.clone()).color(TextColor::Muted))
-                    })
-                    .collect::<Vec<_>>(),
-            )
-        } else {
-            div()
-        }
-    }
-
-    fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
-        match &self.slot {
-            ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render_into_any(),
-            ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render_into_any(),
-        }
-    }
-}
-
-use chrono::NaiveDateTime;
-use gpui::{px, Styled};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{Panel, Story};
-    use gpui::{Div, Render};
-
-    pub struct NotificationsPanelStory;
-
-    impl Render<Self> for NotificationsPanelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, NotificationsPanel>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    Panel::new("panel", cx).child(NotificationsPanel::new("notifications_panel")),
-                )
-        }
-    }
-}

crates/ui2/src/to_extract/panes.rs 🔗

@@ -1,139 +0,0 @@
-use gpui::{
-    hsla, red, AnyElement, Div, ElementId, ExternalPaths, Hsla, Length, RenderOnce, Size, Stateful,
-    View,
-};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-
-#[derive(Default, PartialEq)]
-pub enum SplitDirection {
-    #[default]
-    Horizontal,
-    Vertical,
-}
-
-#[derive(RenderOnce)]
-pub struct Pane<V: 'static> {
-    id: ElementId,
-    size: Size<Length>,
-    fill: Hsla,
-    children: SmallVec<[AnyElement<V>; 2]>,
-}
-
-impl<V: 'static> Component<V> for Pane<V> {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .id(self.id.clone())
-            .flex()
-            .flex_initial()
-            .bg(self.fill)
-            .w(self.size.width)
-            .h(self.size.height)
-            .relative()
-            .child(div().z_index(0).size_full().children(self.children))
-            .child(
-                div()
-                    .z_index(1)
-                    .id("drag-target")
-                    .drag_over::<ExternalPaths>(|d| d.bg(red()))
-                    .on_drop(|_, files: View<ExternalPaths>, cx| {
-                        eprintln!("dropped files! {:?}", files.read(cx));
-                    })
-                    .absolute()
-                    .inset_0(),
-            )
-    }
-}
-
-impl<V: 'static> Pane<V> {
-    pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
-        // Fill is only here for debugging purposes, remove before release
-
-        Self {
-            id: id.into(),
-            size,
-            fill: hsla(0.3, 0.3, 0.3, 1.),
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn fill(mut self, fill: Hsla) -> Self {
-        self.fill = fill;
-        self
-    }
-}
-
-impl<V: 'static> ParentElement<V> for Pane<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
-        &mut self.children
-    }
-}
-
-#[derive(RenderOnce)]
-pub struct PaneGroup<V: 'static> {
-    groups: Vec<PaneGroup<V>>,
-    panes: Vec<Pane<V>>,
-    split_direction: SplitDirection,
-}
-
-impl<V: 'static> Component<V> for PaneGroup<V> {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        if !self.panes.is_empty() {
-            let el = div()
-                .flex()
-                .flex_1()
-                .gap_px()
-                .w_full()
-                .h_full()
-                .children(self.panes.into_iter().map(|pane| pane.render(view, cx)));
-
-            if self.split_direction == SplitDirection::Horizontal {
-                return el;
-            } else {
-                return el.flex_col();
-            }
-        }
-
-        if !self.groups.is_empty() {
-            let el = div()
-                .flex()
-                .flex_1()
-                .gap_px()
-                .w_full()
-                .h_full()
-                .bg(cx.theme().colors().editor_background)
-                .children(self.groups.into_iter().map(|group| group.render(view, cx)));
-
-            if self.split_direction == SplitDirection::Horizontal {
-                return el;
-            } else {
-                return el.flex_col();
-            }
-        }
-
-        unreachable!()
-    }
-}
-
-impl<V: 'static> PaneGroup<V> {
-    pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
-        Self {
-            groups,
-            panes: Vec::new(),
-            split_direction,
-        }
-    }
-
-    pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
-        Self {
-            groups: Vec::new(),
-            panes,
-            split_direction,
-        }
-    }
-}

crates/ui2/src/to_extract/project_panel.rs 🔗

@@ -1,117 +0,0 @@
-use crate::{
-    prelude::*, static_project_panel_project_items, static_project_panel_single_items, Input, List,
-    ListHeader,
-};
-use gpui::prelude::*;
-use gpui::Div;
-use gpui::Stateful;
-
-#[derive(RenderOnce)]
-pub struct ProjectPanel {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for ProjectPanel {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .id(self.id.clone())
-            .flex()
-            .flex_col()
-            .size_full()
-            .bg(cx.theme().colors().surface_background)
-            .child(
-                div()
-                    .id("project-panel-contents")
-                    .w_full()
-                    .flex()
-                    .flex_col()
-                    .overflow_y_scroll()
-                    .child(
-                        List::new()
-                            .header(ListHeader::new("FILES"))
-                            .empty_message("No files in directory")
-                            .children(static_project_panel_single_items()),
-                    )
-                    .child(
-                        List::new()
-                            .header(ListHeader::new("PROJECT"))
-                            .empty_message("No folders in directory")
-                            .children(static_project_panel_project_items()),
-                    ),
-            )
-            .child(
-                Input::new("Find something...")
-                    .value("buffe".to_string())
-                    .state(InteractionState::Focused),
-            )
-    }
-}
-
-impl ProjectPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div()
-            .id(self.id.clone())
-            .flex()
-            .flex_col()
-            .size_full()
-            .bg(cx.theme().colors().surface_background)
-            .child(
-                div()
-                    .id("project-panel-contents")
-                    .w_full()
-                    .flex()
-                    .flex_col()
-                    .overflow_y_scroll()
-                    .child(
-                        List::new()
-                            .header(ListHeader::new("FILES"))
-                            .empty_message("No files in directory")
-                            .children(static_project_panel_single_items()),
-                    )
-                    .child(
-                        List::new()
-                            .header(ListHeader::new("PROJECT"))
-                            .empty_message("No folders in directory")
-                            .children(static_project_panel_project_items()),
-                    ),
-            )
-            .child(
-                Input::new("Find something...")
-                    .value("buffe".to_string())
-                    .state(InteractionState::Focused),
-            )
-    }
-}
-
-use gpui::ElementId;
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::{Panel, Story};
-    use gpui::{Div, Render};
-
-    pub struct ProjectPanelStory;
-
-    impl Render<Self> for ProjectPanelStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, ProjectPanel>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    Panel::new("project-panel-outer", cx)
-                        .child(ProjectPanel::new("project-panel-inner")),
-                )
-        }
-    }
-}

crates/ui2/src/to_extract/recent_projects.rs 🔗

@@ -1,58 +0,0 @@
-use crate::prelude::*;
-use crate::{OrderMethod, Palette, PaletteItem};
-
-#[derive(RenderOnce)]
-pub struct RecentProjects {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for RecentProjects {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div().id(self.id.clone()).child(
-            Palette::new("palette")
-                .items(vec![
-                    PaletteItem::new("zed").sublabel(SharedString::from("~/projects/zed")),
-                    PaletteItem::new("saga").sublabel(SharedString::from("~/projects/saga")),
-                    PaletteItem::new("journal").sublabel(SharedString::from("~/journal")),
-                    PaletteItem::new("dotfiles").sublabel(SharedString::from("~/dotfiles")),
-                    PaletteItem::new("zed.dev").sublabel(SharedString::from("~/projects/zed.dev")),
-                    PaletteItem::new("laminar").sublabel(SharedString::from("~/projects/laminar")),
-                ])
-                .placeholder("Recent Projects...")
-                .empty_string("No matches")
-                .default_order(OrderMethod::Ascending),
-        )
-    }
-}
-
-impl RecentProjects {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-}
-
-use gpui::{Div, RenderOnce, Stateful};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct RecentProjectsStory;
-
-    impl Render<Self> for RecentProjectsStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, RecentProjects>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(RecentProjects::new("recent-projects"))
-        }
-    }
-}

crates/ui2/src/to_extract/status_bar.rs 🔗

@@ -1,201 +0,0 @@
-use std::sync::Arc;
-
-use gpui::{Div, RenderOnce};
-
-use crate::prelude::*;
-use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace};
-
-#[derive(Default, PartialEq)]
-pub enum Tool {
-    #[default]
-    ProjectPanel,
-    CollaborationPanel,
-    Terminal,
-    Assistant,
-    Feedback,
-    Diagnostics,
-}
-
-struct ToolGroup {
-    active_index: Option<usize>,
-    tools: Vec<Tool>,
-}
-
-impl Default for ToolGroup {
-    fn default() -> Self {
-        ToolGroup {
-            active_index: None,
-            tools: vec![],
-        }
-    }
-}
-
-#[derive(RenderOnce)]
-#[view = "Workspace"]
-pub struct StatusBar {
-    left_tools: Option<ToolGroup>,
-    right_tools: Option<ToolGroup>,
-    bottom_tools: Option<ToolGroup>,
-}
-
-impl Component<Workspace> for StatusBar {
-    type Rendered = Div<Workspace>;
-
-    fn render(self, view: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Self::Rendered {
-        div()
-            .py_0p5()
-            .px_1()
-            .flex()
-            .items_center()
-            .justify_between()
-            .w_full()
-            .bg(cx.theme().colors().status_bar_background)
-            .child(self.left_tools(view, cx))
-            .child(self.right_tools(view, cx))
-    }
-}
-
-impl StatusBar {
-    pub fn new() -> Self {
-        Self {
-            left_tools: None,
-            right_tools: None,
-            bottom_tools: None,
-        }
-    }
-
-    pub fn left_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
-        self.left_tools = {
-            let mut tools = vec![tool];
-            tools.extend(self.left_tools.take().unwrap_or_default().tools);
-            Some(ToolGroup {
-                active_index,
-                tools,
-            })
-        };
-        self
-    }
-
-    pub fn right_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
-        self.right_tools = {
-            let mut tools = vec![tool];
-            tools.extend(self.left_tools.take().unwrap_or_default().tools);
-            Some(ToolGroup {
-                active_index,
-                tools,
-            })
-        };
-        self
-    }
-
-    pub fn bottom_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
-        self.bottom_tools = {
-            let mut tools = vec![tool];
-            tools.extend(self.left_tools.take().unwrap_or_default().tools);
-            Some(ToolGroup {
-                active_index,
-                tools,
-            })
-        };
-        self
-    }
-
-    fn left_tools(&self, workspace: &mut Workspace, cx: &WindowContext) -> impl Element<Workspace> {
-        div()
-            .flex()
-            .items_center()
-            .gap_1()
-            .child(
-                IconButton::<Workspace>::new("project_panel", Icon::FileTree)
-                    .when(workspace.is_project_panel_open(), |this| {
-                        this.color(TextColor::Accent)
-                    })
-                    .on_click(|workspace: &mut Workspace, cx| {
-                        workspace.toggle_project_panel(cx);
-                    }),
-            )
-            .child(
-                IconButton::<Workspace>::new("collab_panel", Icon::Hash)
-                    .when(workspace.is_collab_panel_open(), |this| {
-                        this.color(TextColor::Accent)
-                    })
-                    .on_click(|workspace: &mut Workspace, cx| {
-                        workspace.toggle_collab_panel();
-                    }),
-            )
-            .child(ToolDivider::new())
-            .child(IconButton::new("diagnostics", Icon::XCircle))
-    }
-
-    fn right_tools(
-        &self,
-        workspace: &mut Workspace,
-        cx: &WindowContext,
-    ) -> impl Element<Workspace> {
-        div()
-            .flex()
-            .items_center()
-            .gap_2()
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .gap_1()
-                    .child(Button::new("116:25"))
-                    .child(
-                        Button::<Workspace>::new("Rust").on_click(Arc::new(|workspace, cx| {
-                            workspace.toggle_language_selector(cx);
-                        })),
-                    ),
-            )
-            .child(ToolDivider::new())
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .gap_1()
-                    .child(
-                        IconButton::new("copilot", Icon::Copilot)
-                            .on_click(|_, _| println!("Copilot clicked.")),
-                    )
-                    .child(
-                        IconButton::new("envelope", Icon::Envelope)
-                            .on_click(|_, _| println!("Send Feedback clicked.")),
-                    ),
-            )
-            .child(ToolDivider::new())
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .gap_1()
-                    .child(
-                        IconButton::<Workspace>::new("terminal", Icon::Terminal)
-                            .when(workspace.is_terminal_open(), |this| {
-                                this.color(TextColor::Accent)
-                            })
-                            .on_click(|workspace: &mut Workspace, cx| {
-                                workspace.toggle_terminal(cx);
-                            }),
-                    )
-                    .child(
-                        IconButton::<Workspace>::new("chat_panel", Icon::MessageBubbles)
-                            .when(workspace.is_chat_panel_open(), |this| {
-                                this.color(TextColor::Accent)
-                            })
-                            .on_click(|workspace: &mut Workspace, cx| {
-                                workspace.toggle_chat_panel(cx);
-                            }),
-                    )
-                    .child(
-                        IconButton::<Workspace>::new("assistant_panel", Icon::Ai)
-                            .when(workspace.is_assistant_panel_open(), |this| {
-                                this.color(TextColor::Accent)
-                            })
-                            .on_click(|workspace: &mut Workspace, cx| {
-                                workspace.toggle_assistant_panel(cx);
-                            }),
-                    ),
-            )
-    }
-}

crates/ui2/src/to_extract/tab_bar.rs 🔗

@@ -1,156 +0,0 @@
-use crate::{prelude::*, Icon, IconButton, Tab};
-use gpui::prelude::*;
-use gpui::Div;
-use gpui::Stateful;
-
-#[derive(RenderOnce)]
-pub struct TabBar {
-    id: ElementId,
-    /// Backwards, Forwards
-    can_navigate: (bool, bool),
-    tabs: Vec<Tab>,
-}
-
-impl<V: 'static> Component<V> for TabBar {
-    type Rendered = Stateful<V, Div<V>>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let (can_navigate_back, can_navigate_forward) = self.can_navigate;
-
-        div()
-            .group("tab_bar")
-            .id(self.id.clone())
-            .w_full()
-            .flex()
-            .bg(cx.theme().colors().tab_bar_background)
-            // Left Side
-            .child(
-                div()
-                    .relative()
-                    .px_1()
-                    .flex()
-                    .flex_none()
-                    .gap_2()
-                    // Nav Buttons
-                    .child(
-                        div()
-                            .right_0()
-                            .flex()
-                            .items_center()
-                            .gap_px()
-                            .child(
-                                IconButton::new("arrow_left", Icon::ArrowLeft)
-                                    .state(InteractionState::Enabled.if_enabled(can_navigate_back)),
-                            )
-                            .child(
-                                IconButton::new("arrow_right", Icon::ArrowRight).state(
-                                    InteractionState::Enabled.if_enabled(can_navigate_forward),
-                                ),
-                            ),
-                    ),
-            )
-            .child(
-                div().w_0().flex_1().h_full().child(
-                    div()
-                        .id("tabs")
-                        .flex()
-                        .overflow_x_scroll()
-                        .children(self.tabs.clone()),
-                ),
-            )
-            // Right Side
-            .child(
-                div()
-                    // We only use absolute here since we don't
-                    // have opacity or `hidden()` yet
-                    .absolute()
-                    .neg_top_7()
-                    .px_1()
-                    .flex()
-                    .flex_none()
-                    .gap_2()
-                    .group_hover("tab_bar", |this| this.top_0())
-                    // Nav Buttons
-                    .child(
-                        div()
-                            .flex()
-                            .items_center()
-                            .gap_px()
-                            .child(IconButton::new("plus", Icon::Plus))
-                            .child(IconButton::new("split", Icon::Split)),
-                    ),
-            )
-    }
-}
-
-impl TabBar {
-    pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
-        Self {
-            id: id.into(),
-            can_navigate: (false, false),
-            tabs,
-        }
-    }
-
-    pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
-        self.can_navigate = can_navigate;
-        self
-    }
-}
-
-use gpui::ElementId;
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-
-    pub struct TabBarStory;
-
-    impl Render<Self> for TabBarStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, TabBar>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(TabBar::new(
-                    "tab-bar",
-                    vec![
-                        Tab::new(1)
-                            .title("Cargo.toml".to_string())
-                            .current(false)
-                            .git_status(GitStatus::Modified),
-                        Tab::new(2)
-                            .title("Channels Panel".to_string())
-                            .current(false),
-                        Tab::new(3)
-                            .title("channels_panel.rs".to_string())
-                            .current(true)
-                            .git_status(GitStatus::Modified),
-                        Tab::new(4)
-                            .title("workspace.rs".to_string())
-                            .current(false)
-                            .git_status(GitStatus::Modified),
-                        Tab::new(5)
-                            .title("icon_button.rs".to_string())
-                            .current(false),
-                        Tab::new(6)
-                            .title("storybook.rs".to_string())
-                            .current(false)
-                            .git_status(GitStatus::Created),
-                        Tab::new(7).title("theme.rs".to_string()).current(false),
-                        Tab::new(8)
-                            .title("theme_registry.rs".to_string())
-                            .current(false),
-                        Tab::new(9)
-                            .title("styleable_helpers.rs".to_string())
-                            .current(false),
-                    ],
-                ))
-        }
-    }
-}

crates/ui2/src/to_extract/terminal.rs 🔗

@@ -1,166 +0,0 @@
-use crate::prelude::*;
-use crate::{Icon, IconButton, Pane, Tab};
-use gpui::{relative, rems, Div, RenderOnce, Size};
-
-#[derive(RenderOnce)]
-pub struct Terminal;
-
-impl<V: 'static> Component<V> for Terminal {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let can_navigate_back = true;
-        let can_navigate_forward = false;
-
-        div()
-            .flex()
-            .flex_col()
-            .w_full()
-            .child(
-                // Terminal Tabs.
-                div()
-                    .w_full()
-                    .flex()
-                    .bg(cx.theme().colors().surface_background)
-                    .child(
-                        div().px_1().flex().flex_none().gap_2().child(
-                            div()
-                                .flex()
-                                .items_center()
-                                .gap_px()
-                                .child(
-                                    IconButton::new("arrow_left", Icon::ArrowLeft).state(
-                                        InteractionState::Enabled.if_enabled(can_navigate_back),
-                                    ),
-                                )
-                                .child(IconButton::new("arrow_right", Icon::ArrowRight).state(
-                                    InteractionState::Enabled.if_enabled(can_navigate_forward),
-                                )),
-                        ),
-                    )
-                    .child(
-                        div().w_0().flex_1().h_full().child(
-                            div()
-                                .flex()
-                                .child(
-                                    Tab::new(1)
-                                        .title("zed — fish".to_string())
-                                        .icon(Icon::Terminal)
-                                        .close_side(IconSide::Right)
-                                        .current(true),
-                                )
-                                .child(
-                                    Tab::new(2)
-                                        .title("zed — fish".to_string())
-                                        .icon(Icon::Terminal)
-                                        .close_side(IconSide::Right)
-                                        .current(false),
-                                ),
-                        ),
-                    ),
-            )
-            // Terminal Pane.
-            .child(
-                Pane::new(
-                    "terminal",
-                    Size {
-                        width: relative(1.).into(),
-                        height: rems(36.).into(),
-                    },
-                )
-                .child(crate::static_data::terminal_buffer(cx)),
-            )
-    }
-}
-
-impl Terminal {
-    pub fn new() -> Self {
-        Self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        let can_navigate_back = true;
-        let can_navigate_forward = false;
-
-        div()
-            .flex()
-            .flex_col()
-            .w_full()
-            .child(
-                // Terminal Tabs.
-                div()
-                    .w_full()
-                    .flex()
-                    .bg(cx.theme().colors().surface_background)
-                    .child(
-                        div().px_1().flex().flex_none().gap_2().child(
-                            div()
-                                .flex()
-                                .items_center()
-                                .gap_px()
-                                .child(
-                                    IconButton::new("arrow_left", Icon::ArrowLeft).state(
-                                        InteractionState::Enabled.if_enabled(can_navigate_back),
-                                    ),
-                                )
-                                .child(IconButton::new("arrow_right", Icon::ArrowRight).state(
-                                    InteractionState::Enabled.if_enabled(can_navigate_forward),
-                                )),
-                        ),
-                    )
-                    .child(
-                        div().w_0().flex_1().h_full().child(
-                            div()
-                                .flex()
-                                .child(
-                                    Tab::new(1)
-                                        .title("zed — fish".to_string())
-                                        .icon(Icon::Terminal)
-                                        .close_side(IconSide::Right)
-                                        .current(true),
-                                )
-                                .child(
-                                    Tab::new(2)
-                                        .title("zed — fish".to_string())
-                                        .icon(Icon::Terminal)
-                                        .close_side(IconSide::Right)
-                                        .current(false),
-                                ),
-                        ),
-                    ),
-            )
-            // Terminal Pane.
-            .child(
-                Pane::new(
-                    "terminal",
-                    Size {
-                        width: relative(1.).into(),
-                        height: rems(36.).into(),
-                    },
-                )
-                .child(crate::static_data::terminal_buffer(cx)),
-            )
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-    use gpui::{Div, Render};
-    pub struct TerminalStory;
-
-    impl Render<Self> for TerminalStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Terminal>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Terminal::new())
-        }
-    }
-}

crates/ui2/src/to_extract/theme_selector.rs 🔗

@@ -1,66 +0,0 @@
-use crate::prelude::*;
-use crate::{OrderMethod, Palette, PaletteItem};
-
-#[derive(RenderOnce)]
-pub struct ThemeSelector {
-    id: ElementId,
-}
-
-impl<V: 'static> Component<V> for ThemeSelector {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let cx: &mut ViewContext<V> = cx;
-        div().child(
-            Palette::new(self.id.clone())
-                .items(vec![
-                    PaletteItem::new("One Dark"),
-                    PaletteItem::new("Rosé Pine"),
-                    PaletteItem::new("Rosé Pine Moon"),
-                    PaletteItem::new("Sandcastle"),
-                    PaletteItem::new("Solarized Dark"),
-                    PaletteItem::new("Summercamp"),
-                    PaletteItem::new("Atelier Cave Light"),
-                    PaletteItem::new("Atelier Dune Light"),
-                    PaletteItem::new("Atelier Estuary Light"),
-                    PaletteItem::new("Atelier Forest Light"),
-                    PaletteItem::new("Atelier Heath Light"),
-                ])
-                .placeholder("Select Theme...")
-                .empty_string("No matches")
-                .default_order(OrderMethod::Ascending),
-        )
-    }
-}
-
-impl ThemeSelector {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
-}
-
-use gpui::{Div, RenderOnce};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use gpui::{Div, Render};
-
-    use crate::Story;
-
-    use super::*;
-
-    pub struct ThemeSelectorStory;
-
-    impl Render<Self> for ThemeSelectorStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, ThemeSelector>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(ThemeSelector::new("theme-selector"))
-        }
-    }
-}

crates/ui2/src/to_extract/title_bar.rs 🔗

@@ -1,218 +0,0 @@
-use std::sync::atomic::AtomicBool;
-use std::sync::Arc;
-
-use gpui::{Div, Render, RenderOnce, View, VisualContext};
-
-use crate::prelude::*;
-use crate::settings::user_settings;
-use crate::{
-    Avatar, Button, Icon, IconButton, MicStatus, PlayerStack, PlayerWithCallStatus,
-    ScreenShareStatus, TextColor, ToolDivider, TrafficLights,
-};
-
-#[derive(Clone)]
-pub struct Livestream {
-    pub players: Vec<PlayerWithCallStatus>,
-    pub channel: Option<String>, // projects
-                                 // windows
-}
-
-#[derive(Clone)]
-pub struct TitleBar {
-    /// If the window is active from the OS's perspective.
-    is_active: Arc<AtomicBool>,
-    livestream: Option<Livestream>,
-    mic_status: MicStatus,
-    is_deafened: bool,
-    screen_share_status: ScreenShareStatus,
-}
-
-impl TitleBar {
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        let is_active = Arc::new(AtomicBool::new(true));
-        let active = is_active.clone();
-
-        // cx.observe_window_activation(move |_, is_active, cx| {
-        //     active.store(is_active, std::sync::atomic::Ordering::SeqCst);
-        //     cx.notify();
-        // })
-        // .detach();
-
-        Self {
-            is_active,
-            livestream: None,
-            mic_status: MicStatus::Unmuted,
-            is_deafened: false,
-            screen_share_status: ScreenShareStatus::NotShared,
-        }
-    }
-
-    pub fn set_livestream(mut self, livestream: Option<Livestream>) -> Self {
-        self.livestream = livestream;
-        self
-    }
-
-    pub fn is_mic_muted(&self) -> bool {
-        self.mic_status == MicStatus::Muted
-    }
-
-    pub fn toggle_mic_status(&mut self, cx: &mut ViewContext<Self>) {
-        self.mic_status = self.mic_status.inverse();
-
-        // Undeafen yourself when unmuting the mic while deafened.
-        if self.is_deafened && self.mic_status == MicStatus::Unmuted {
-            self.is_deafened = false;
-        }
-
-        cx.notify();
-    }
-
-    pub fn toggle_deafened(&mut self, cx: &mut ViewContext<Self>) {
-        self.is_deafened = !self.is_deafened;
-        self.mic_status = MicStatus::Muted;
-
-        cx.notify()
-    }
-
-    pub fn toggle_screen_share_status(&mut self, cx: &mut ViewContext<Self>) {
-        self.screen_share_status = self.screen_share_status.inverse();
-
-        cx.notify();
-    }
-
-    pub fn view(cx: &mut WindowContext, livestream: Option<Livestream>) -> View<Self> {
-        cx.build_view(|cx| Self::new(cx).set_livestream(livestream))
-    }
-}
-
-impl Render<Self> for TitleBar {
-    type Element = Div<Self>;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        let settings = user_settings(cx);
-
-        // let has_focus = cx.window_is_active();
-        let has_focus = true;
-
-        let player_list = if let Some(livestream) = &self.livestream {
-            livestream.players.clone().into_iter()
-        } else {
-            vec![].into_iter()
-        };
-
-        div()
-            .flex()
-            .items_center()
-            .justify_between()
-            .w_full()
-            .bg(cx.theme().colors().background)
-            .py_1()
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .h_full()
-                    .gap_4()
-                    .px_2()
-                    .child(TrafficLights::new().window_has_focus(has_focus))
-                    // === Project Info === //
-                    .child(
-                        div()
-                            .flex()
-                            .items_center()
-                            .gap_1()
-                            .when(*settings.titlebar.show_project_owner, |this| {
-                                this.child(Button::new("iamnbutler"))
-                            })
-                            .child(Button::new("zed"))
-                            .child(Button::new("nate/gpui2-ui-components")),
-                    )
-                    .children(player_list.map(|p| PlayerStack::new(p)))
-                    .child(IconButton::new("plus", Icon::Plus)),
-            )
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .child(
-                        div()
-                            .px_2()
-                            .flex()
-                            .items_center()
-                            .gap_1()
-                            .child(IconButton::new("folder_x", Icon::FolderX))
-                            .child(IconButton::new("exit", Icon::Exit)),
-                    )
-                    .child(ToolDivider::new())
-                    .child(
-                        div()
-                            .px_2()
-                            .flex()
-                            .items_center()
-                            .gap_1()
-                            .child(
-                                IconButton::<TitleBar>::new("toggle_mic_status", Icon::Mic)
-                                    .when(self.is_mic_muted(), |this| this.color(TextColor::Error))
-                                    .on_click(|title_bar: &mut TitleBar, cx| {
-                                        title_bar.toggle_mic_status(cx)
-                                    }),
-                            )
-                            .child(
-                                IconButton::<TitleBar>::new("toggle_deafened", Icon::AudioOn)
-                                    .when(self.is_deafened, |this| this.color(TextColor::Error))
-                                    .on_click(|title_bar: &mut TitleBar, cx| {
-                                        title_bar.toggle_deafened(cx)
-                                    }),
-                            )
-                            .child(
-                                IconButton::<TitleBar>::new("toggle_screen_share", Icon::Screen)
-                                    .when(
-                                        self.screen_share_status == ScreenShareStatus::Shared,
-                                        |this| this.color(TextColor::Accent),
-                                    )
-                                    .on_click(|title_bar: &mut TitleBar, cx| {
-                                        title_bar.toggle_screen_share_status(cx)
-                                    }),
-                            ),
-                    )
-                    .child(
-                        div().px_2().flex().items_center().child(
-                            Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                .shape(Shape::RoundedRectangle),
-                        ),
-                    ),
-            )
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use crate::Story;
-
-    pub struct TitleBarStory {
-        title_bar: View<TitleBar>,
-    }
-
-    impl TitleBarStory {
-        pub fn view(cx: &mut WindowContext) -> View<Self> {
-            cx.build_view(|cx| Self {
-                title_bar: TitleBar::view(cx, None),
-            })
-        }
-    }
-
-    impl Render<Self> for TitleBarStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-            Story::container(cx)
-                .child(Story::title_for::<_, TitleBar>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(self.title_bar.clone())
-        }
-    }
-}

crates/ui2/src/to_extract/toolbar.rs 🔗

@@ -1,130 +0,0 @@
-use gpui::{AnyElement, Div, RenderOnce};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-
-#[derive(Clone)]
-pub struct ToolbarItem {}
-
-#[derive(RenderOnce)]
-pub struct Toolbar<V: 'static> {
-    left_items: SmallVec<[AnyElement<V>; 2]>,
-    right_items: SmallVec<[AnyElement<V>; 2]>,
-}
-
-impl<V: 'static> Component<V> for Toolbar<V> {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .bg(cx.theme().colors().toolbar_background)
-            .p_2()
-            .flex()
-            .justify_between()
-            .child(div().flex().children(self.left_items))
-            .child(div().flex().children(self.right_items))
-    }
-}
-
-impl<V: 'static> Toolbar<V> {
-    pub fn new() -> Self {
-        Self {
-            left_items: SmallVec::new(),
-            right_items: SmallVec::new(),
-        }
-    }
-
-    pub fn left_item(mut self, child: impl RenderOnce<V>) -> Self
-    where
-        Self: Sized,
-    {
-        self.left_items.push(child.render_into_any());
-        self
-    }
-
-    pub fn left_items(mut self, iter: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
-    where
-        Self: Sized,
-    {
-        self.left_items
-            .extend(iter.into_iter().map(|item| item.render_into_any()));
-        self
-    }
-
-    pub fn right_item(mut self, child: impl RenderOnce<V>) -> Self
-    where
-        Self: Sized,
-    {
-        self.right_items.push(child.render_into_any());
-        self
-    }
-
-    pub fn right_items(mut self, iter: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
-    where
-        Self: Sized,
-    {
-        self.right_items
-            .extend(iter.into_iter().map(|item| item.render_into_any()));
-        self
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use std::path::PathBuf;
-    use std::str::FromStr;
-
-    use gpui::{Div, Render};
-
-    use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol};
-
-    use super::*;
-
-    pub struct ToolbarStory;
-
-    impl Render<Self> for ToolbarStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, Toolbar<Self>>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(
-                    Toolbar::new()
-                        .left_item(Breadcrumb::new(
-                            PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
-                            vec![
-                                Symbol(vec![
-                                    HighlightedText {
-                                        text: "impl ".into(),
-                                        color: cx.theme().syntax_color("keyword"),
-                                    },
-                                    HighlightedText {
-                                        text: "ToolbarStory".into(),
-                                        color: cx.theme().syntax_color("function"),
-                                    },
-                                ]),
-                                Symbol(vec![
-                                    HighlightedText {
-                                        text: "fn ".into(),
-                                        color: cx.theme().syntax_color("keyword"),
-                                    },
-                                    HighlightedText {
-                                        text: "render".into(),
-                                        color: cx.theme().syntax_color("function"),
-                                    },
-                                ]),
-                            ],
-                        ))
-                        .right_items(vec![
-                            IconButton::new("toggle_inlay_hints", Icon::InlayHint),
-                            IconButton::new("buffer_search", Icon::MagnifyingGlass),
-                            IconButton::new("inline_assist", Icon::MagicWand),
-                        ]),
-                )
-        }
-    }
-}

crates/ui2/src/to_extract/traffic_lights.rs 🔗

@@ -1,109 +0,0 @@
-use crate::prelude::*;
-
-#[derive(Clone, Copy)]
-enum TrafficLightColor {
-    Red,
-    Yellow,
-    Green,
-}
-
-#[derive(RenderOnce)]
-struct TrafficLight {
-    color: TrafficLightColor,
-    window_has_focus: bool,
-}
-
-impl<V: 'static> Component<V> for TrafficLight {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let system_colors = &cx.theme().styles.system;
-
-        let fill = match (self.window_has_focus, self.color) {
-            (true, TrafficLightColor::Red) => system_colors.mac_os_traffic_light_red,
-            (true, TrafficLightColor::Yellow) => system_colors.mac_os_traffic_light_yellow,
-            (true, TrafficLightColor::Green) => system_colors.mac_os_traffic_light_green,
-            (false, _) => cx.theme().colors().element_background,
-        };
-
-        div().w_3().h_3().rounded_full().bg(fill)
-    }
-}
-
-impl TrafficLight {
-    fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
-        Self {
-            color,
-            window_has_focus,
-        }
-    }
-}
-
-#[derive(RenderOnce)]
-pub struct TrafficLights {
-    window_has_focus: bool,
-}
-
-impl<V: 'static> Component<V> for TrafficLights {
-    type Rendered = Div<V>;
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        div()
-            .flex()
-            .items_center()
-            .gap_2()
-            .child(TrafficLight::new(
-                TrafficLightColor::Red,
-                self.window_has_focus,
-            ))
-            .child(TrafficLight::new(
-                TrafficLightColor::Yellow,
-                self.window_has_focus,
-            ))
-            .child(TrafficLight::new(
-                TrafficLightColor::Green,
-                self.window_has_focus,
-            ))
-    }
-}
-
-impl TrafficLights {
-    pub fn new() -> Self {
-        Self {
-            window_has_focus: true,
-        }
-    }
-
-    pub fn window_has_focus(mut self, window_has_focus: bool) -> Self {
-        self.window_has_focus = window_has_focus;
-        self
-    }
-}
-
-use gpui::{Div, RenderOnce};
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use gpui::{Div, Render};
-
-    use crate::Story;
-
-    use super::*;
-
-    pub struct TrafficLightsStory;
-
-    impl Render<Self> for TrafficLightsStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            Story::container(cx)
-                .child(Story::title_for::<_, TrafficLights>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(TrafficLights::new())
-                .child(Story::label(cx, "Unfocused"))
-                .child(TrafficLights::new().window_has_focus(false))
-        }
-    }
-}

crates/ui2/src/to_extract/workspace.rs 🔗

@@ -1,398 +0,0 @@
-use std::sync::Arc;
-
-use chrono::DateTime;
-use gpui::{px, relative, Div, Render, RenderOnce, Size, View, VisualContext};
-use settings2::Settings;
-use theme2::ThemeSettings;
-
-use crate::prelude::*;
-use crate::{
-    static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, Checkbox,
-    CollabPanel, EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel,
-    PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
-    Toast, ToastOrigin,
-};
-
-#[derive(Clone)]
-pub struct Gpui2UiDebug {
-    pub in_livestream: bool,
-    pub enable_user_settings: bool,
-    pub show_toast: bool,
-}
-
-impl Default for Gpui2UiDebug {
-    fn default() -> Self {
-        Self {
-            in_livestream: false,
-            enable_user_settings: false,
-            show_toast: false,
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct Workspace {
-    title_bar: View<TitleBar>,
-    editor_1: View<EditorPane>,
-    show_project_panel: bool,
-    show_collab_panel: bool,
-    show_chat_panel: bool,
-    show_assistant_panel: bool,
-    show_notifications_panel: bool,
-    show_terminal: bool,
-    show_debug: bool,
-    show_language_selector: bool,
-    test_checkbox_selection: Selection,
-    debug: Gpui2UiDebug,
-}
-
-impl Workspace {
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        Self {
-            title_bar: TitleBar::view(cx, None),
-            editor_1: EditorPane::view(cx),
-            show_project_panel: true,
-            show_collab_panel: false,
-            show_chat_panel: false,
-            show_assistant_panel: false,
-            show_terminal: true,
-            show_language_selector: false,
-            show_debug: false,
-            show_notifications_panel: true,
-            test_checkbox_selection: Selection::Unselected,
-            debug: Gpui2UiDebug::default(),
-        }
-    }
-
-    pub fn is_project_panel_open(&self) -> bool {
-        self.show_project_panel
-    }
-
-    pub fn toggle_project_panel(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_project_panel = !self.show_project_panel;
-
-        self.show_collab_panel = false;
-
-        cx.notify();
-    }
-
-    pub fn is_collab_panel_open(&self) -> bool {
-        self.show_collab_panel
-    }
-
-    pub fn toggle_collab_panel(&mut self) {
-        self.show_collab_panel = !self.show_collab_panel;
-
-        self.show_project_panel = false;
-    }
-
-    pub fn is_terminal_open(&self) -> bool {
-        self.show_terminal
-    }
-
-    pub fn toggle_terminal(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_terminal = !self.show_terminal;
-
-        cx.notify();
-    }
-
-    pub fn is_chat_panel_open(&self) -> bool {
-        self.show_chat_panel
-    }
-
-    pub fn toggle_chat_panel(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_chat_panel = !self.show_chat_panel;
-
-        self.show_assistant_panel = false;
-        self.show_notifications_panel = false;
-
-        cx.notify();
-    }
-
-    pub fn is_notifications_panel_open(&self) -> bool {
-        self.show_notifications_panel
-    }
-
-    pub fn toggle_notifications_panel(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_notifications_panel = !self.show_notifications_panel;
-
-        self.show_chat_panel = false;
-        self.show_assistant_panel = false;
-
-        cx.notify();
-    }
-
-    pub fn is_assistant_panel_open(&self) -> bool {
-        self.show_assistant_panel
-    }
-
-    pub fn toggle_assistant_panel(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_assistant_panel = !self.show_assistant_panel;
-
-        self.show_chat_panel = false;
-        self.show_notifications_panel = false;
-
-        cx.notify();
-    }
-
-    pub fn is_language_selector_open(&self) -> bool {
-        self.show_language_selector
-    }
-
-    pub fn toggle_language_selector(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_language_selector = !self.show_language_selector;
-
-        cx.notify();
-    }
-
-    pub fn toggle_debug(&mut self, cx: &mut ViewContext<Self>) {
-        self.show_debug = !self.show_debug;
-
-        cx.notify();
-    }
-
-    pub fn debug_toggle_user_settings(&mut self, cx: &mut ViewContext<Self>) {
-        self.debug.enable_user_settings = !self.debug.enable_user_settings;
-
-        let mut theme_settings = ThemeSettings::get_global(cx).clone();
-
-        if self.debug.enable_user_settings {
-            theme_settings.ui_font_size = 18.0.into();
-        } else {
-            theme_settings.ui_font_size = 16.0.into();
-        }
-
-        ThemeSettings::override_global(theme_settings.clone(), cx);
-
-        cx.set_rem_size(theme_settings.ui_font_size);
-
-        cx.notify();
-    }
-
-    pub fn debug_toggle_livestream(&mut self, cx: &mut ViewContext<Self>) {
-        self.debug.in_livestream = !self.debug.in_livestream;
-
-        self.title_bar = TitleBar::view(
-            cx,
-            Some(static_livestream()).filter(|_| self.debug.in_livestream),
-        );
-
-        cx.notify();
-    }
-
-    pub fn debug_toggle_toast(&mut self, cx: &mut ViewContext<Self>) {
-        self.debug.show_toast = !self.debug.show_toast;
-
-        cx.notify();
-    }
-
-    pub fn view(cx: &mut WindowContext) -> View<Self> {
-        cx.build_view(|cx| Self::new(cx))
-    }
-}
-
-impl Render<Self> for Workspace {
-    type Element = Div<Self>;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        let root_group = PaneGroup::new_panes(
-            vec![Pane::new(
-                "pane-0",
-                Size {
-                    width: relative(1.).into(),
-                    height: relative(1.).into(),
-                },
-            )
-            .child(self.editor_1.clone())],
-            SplitDirection::Horizontal,
-        );
-        let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
-
-        div()
-            .relative()
-            .size_full()
-            .flex()
-            .flex_col()
-            .font(ui_font)
-            .gap_0()
-            .justify_start()
-            .items_start()
-            .text_color(cx.theme().colors().text)
-            .bg(cx.theme().colors().background)
-            .child(self.title_bar.clone())
-            .child(
-                div()
-                    .absolute()
-                    .top_12()
-                    .left_12()
-                    .z_index(99)
-                    .bg(cx.theme().colors().background)
-                    .child(
-                        Checkbox::new("test_checkbox", self.test_checkbox_selection).on_click(
-                            |selection, workspace: &mut Workspace, cx| {
-                                workspace.test_checkbox_selection = selection;
-
-                                cx.notify();
-                            },
-                        ),
-                    ),
-            )
-            .child(
-                div()
-                    .flex_1()
-                    .w_full()
-                    .flex()
-                    .flex_row()
-                    .overflow_hidden()
-                    .border_t()
-                    .border_b()
-                    .border_color(cx.theme().colors().border)
-                    .children(
-                        Some(
-                            Panel::new("project-panel-outer", cx)
-                                .side(PanelSide::Left)
-                                .child(ProjectPanel::new("project-panel-inner")),
-                        )
-                        .filter(|_| self.is_project_panel_open()),
-                    )
-                    .children(
-                        Some(
-                            Panel::new("collab-panel-outer", cx)
-                                .child(CollabPanel::new("collab-panel-inner"))
-                                .side(PanelSide::Left),
-                        )
-                        .filter(|_| self.is_collab_panel_open()),
-                    )
-                    // .child(NotificationToast::new(
-                    //     "maxbrunsfeld has requested to add you as a contact.".into(),
-                    // ))
-                    .child(
-                        v_stack()
-                            .flex_1()
-                            .h_full()
-                            .child(div().flex().flex_1().child(root_group))
-                            .children(
-                                Some(
-                                    Panel::new("terminal-panel", cx)
-                                        .child(Terminal::new())
-                                        .allowed_sides(PanelAllowedSides::BottomOnly)
-                                        .side(PanelSide::Bottom),
-                                )
-                                .filter(|_| self.is_terminal_open()),
-                            ),
-                    )
-                    .children(
-                        Some(
-                            Panel::new("chat-panel-outer", cx)
-                                .side(PanelSide::Right)
-                                .child(ChatPanel::new("chat-panel-inner").messages(vec![
-                                    ChatMessage::new(
-                                        "osiewicz".to_string(),
-                                        "is this thing on?".to_string(),
-                                        DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
-                                            .unwrap()
-                                            .naive_local(),
-                                    ),
-                                    ChatMessage::new(
-                                        "maxdeviant".to_string(),
-                                        "Reading you loud and clear!".to_string(),
-                                        DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
-                                            .unwrap()
-                                            .naive_local(),
-                                    ),
-                                ])),
-                        )
-                        .filter(|_| self.is_chat_panel_open()),
-                    )
-                    .children(
-                        Some(
-                            Panel::new("notifications-panel-outer", cx)
-                                .side(PanelSide::Right)
-                                .child(NotificationsPanel::new("notifications-panel-inner")),
-                        )
-                        .filter(|_| self.is_notifications_panel_open()),
-                    )
-                    .children(
-                        Some(
-                            Panel::new("assistant-panel-outer", cx)
-                                .child(AssistantPanel::new("assistant-panel-inner")),
-                        )
-                        .filter(|_| self.is_assistant_panel_open()),
-                    ),
-            )
-            .child(StatusBar::new())
-            .when(self.debug.show_toast, |this| {
-                this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
-            })
-            .children(
-                Some(
-                    div()
-                        .absolute()
-                        .top(px(50.))
-                        .left(px(640.))
-                        .z_index(8)
-                        .child(LanguageSelector::new("language-selector")),
-                )
-                .filter(|_| self.is_language_selector_open()),
-            )
-            .z_index(8)
-            // Debug
-            .child(
-                v_stack()
-                    .z_index(9)
-                    .absolute()
-                    .top_20()
-                    .left_1_4()
-                    .w_40()
-                    .gap_2()
-                    .when(self.show_debug, |this| {
-                        this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
-                            Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
-                        ))
-                        .child(
-                            Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
-                                |workspace, cx| workspace.debug_toggle_toast(cx),
-                            )),
-                        )
-                        .child(
-                            Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
-                                |workspace, cx| workspace.debug_toggle_livestream(cx),
-                            )),
-                        )
-                    })
-                    .child(
-                        Button::<Workspace>::new("Toggle Debug")
-                            .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
-                    ),
-            )
-    }
-}
-
-#[cfg(feature = "stories")]
-pub use stories::*;
-
-#[cfg(feature = "stories")]
-mod stories {
-    use super::*;
-    use gpui::VisualContext;
-
-    pub struct WorkspaceStory {
-        workspace: View<Workspace>,
-    }
-
-    impl WorkspaceStory {
-        pub fn view(cx: &mut WindowContext) -> View<Self> {
-            cx.build_view(|cx| Self {
-                workspace: Workspace::view(cx),
-            })
-        }
-    }
-
-    impl Render<Self> for WorkspaceStory {
-        type Element = Div<Self>;
-
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            div().child(self.workspace.clone())
-        }
-    }
-}

crates/ui2/src/lib.rs → crates/ui2/src/ui2.rs 🔗

@@ -15,36 +15,12 @@
 #![allow(dead_code, unused_variables)]
 
 mod components;
-mod elevation;
 pub mod prelude;
-pub mod settings;
-mod static_data;
 mod styled_ext;
-mod to_extract;
+mod styles;
 pub mod utils;
 
 pub use components::*;
-use gpui::actions;
 pub use prelude::*;
-pub use static_data::*;
 pub use styled_ext::*;
-pub use to_extract::*;
-
-// This needs to be fully qualified with `crate::` otherwise we get a panic
-// at:
-//   thread '<unnamed>' panicked at crates/gpui2/src/platform/mac/platform.rs:66:81:
-//   called `Option::unwrap()` on a `None` value
-//
-// AFAICT this is something to do with conflicting names between crates and modules that
-// interfaces with declaring the `ClassDecl`.
-pub use crate::settings::*;
-
-#[cfg(feature = "stories")]
-mod story;
-#[cfg(feature = "stories")]
-pub use story::*;
-actions!(NoAction);
-
-pub fn binding(key: &str) -> gpui::KeyBinding {
-    gpui::KeyBinding::new(key, NoAction {}, None)
-}
+pub use styles::*;

crates/workspace2/src/dock.rs 🔗

@@ -8,9 +8,7 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 use theme2::ActiveTheme;
-use ui::{
-    h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Label, ListEntry, Tooltip,
-};
+use ui::{h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Tooltip};
 
 pub enum PanelEvent {
     ChangePosition,
@@ -476,8 +474,8 @@ impl Dock {
     }
 }
 
-impl Render<Self> for Dock {
-    type Element = Div<Self>;
+impl Render for Dock {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         if let Some(entry) = self.visible_entry() {
@@ -662,8 +660,8 @@ impl PanelButtons {
 // }
 
 // here be kittens
-impl Render<Self> for PanelButtons {
-    type Element = Div<Self>;
+impl Render for PanelButtons {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         // todo!()
@@ -688,30 +686,31 @@ impl Render<Self> for PanelButtons {
                 let name = entry.panel.persistent_name();
                 let panel = entry.panel.clone();
 
-                let mut button: IconButton<Self> = if i == active_index && is_open {
+                let mut button: IconButton = if i == active_index && is_open {
                     let action = dock.toggle_action();
                     let tooltip: SharedString =
                         format!("Close {} dock", dock.position.to_label()).into();
                     IconButton::new(name, icon)
                         .state(InteractionState::Active)
                         .action(action.boxed_clone())
-                        .tooltip(move |_, cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
+                        .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
                 } else {
                     let action = entry.panel.toggle_action(cx);
 
                     IconButton::new(name, icon)
                         .action(action.boxed_clone())
-                        .tooltip(move |_, cx| Tooltip::for_action(name, &*action, cx))
+                        .tooltip(move |cx| Tooltip::for_action(name, &*action, cx))
                 };
 
                 Some(
                     menu_handle(name)
-                        .menu(move |_, cx| {
+                        .menu(move |cx| {
                             const POSITIONS: [DockPosition; 3] = [
                                 DockPosition::Left,
                                 DockPosition::Right,
                                 DockPosition::Bottom,
                             ];
+
                             ContextMenu::build(cx, |mut menu, cx| {
                                 for position in POSITIONS {
                                     if position != dock_position
@@ -719,10 +718,7 @@ impl Render<Self> for PanelButtons {
                                     {
                                         let panel = panel.clone();
                                         menu = menu.entry(
-                                            ListEntry::new(
-                                                SharedString::from(format!("dock-{position:?}")),
-                                                Label::new(format!("Dock {}", position.to_label())),
-                                            ),
+                                            format!("Dock {}", position.to_label()),
                                             move |_, cx| {
                                                 panel.set_position(position, cx);
                                             },
@@ -780,8 +776,8 @@ pub mod test {
         }
     }
 
-    impl Render<Self> for TestPanel {
-        type Element = Div<Self>;
+    impl Render for TestPanel {
+        type Element = Div;
 
         fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
             div()

crates/workspace2/src/item.rs 🔗

@@ -104,7 +104,7 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
     fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
         None
     }
-    fn tab_content<V: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<V>;
+    fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
 
     /// (model id, Item)
     fn for_each_project_item(
@@ -214,8 +214,8 @@ pub trait ItemHandle: 'static + Send {
     ) -> gpui::Subscription;
     fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
     fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
-    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane>;
-    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace>;
+    fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
     fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
     fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
@@ -307,11 +307,11 @@ impl<T: Item> ItemHandle for View<T> {
         self.read(cx).tab_description(detail, cx)
     }
 
-    fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane> {
+    fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
         self.read(cx).tab_content(detail, cx)
     }
 
-    fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace> {
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
         self.read(cx).tab_content(detail, cx)
     }
 

crates/workspace2/src/modal_layer.rs 🔗

@@ -71,8 +71,8 @@ impl ModalLayer {
     }
 }
 
-impl Render<Self> for ModalLayer {
-    type Element = Div<Self>;
+impl Render for ModalLayer {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let Some(active_modal) = &self.active_modal else {
@@ -97,11 +97,11 @@ impl Render<Self> for ModalLayer {
                         h_stack()
                             // needed to prevent mouse events leaking to the
                             // UI below. // todo! for gpui3.
-                            .on_any_mouse_down(|_, _, cx| cx.stop_propagation())
-                            .on_any_mouse_up(|_, _, cx| cx.stop_propagation())
-                            .on_mouse_down_out(|this: &mut Self, event, cx| {
+                            .on_any_mouse_down(|_, cx| cx.stop_propagation())
+                            .on_any_mouse_up(|_, cx| cx.stop_propagation())
+                            .on_mouse_down_out(cx.listener(|this, _, cx| {
                                 this.hide_modal(cx);
-                            })
+                            }))
                             .child(active_modal.modal.clone()),
                     ),
             )

crates/workspace2/src/notifications.rs 🔗

@@ -13,9 +13,9 @@ pub enum NotificationEvent {
     Dismiss,
 }
 
-pub trait Notification: EventEmitter<NotificationEvent> + Render<Self> {}
+pub trait Notification: EventEmitter<NotificationEvent> + Render {}
 
-impl<V: EventEmitter<NotificationEvent> + Render<Self>> Notification for V {}
+impl<V: EventEmitter<NotificationEvent> + Render> Notification for V {}
 
 pub trait NotificationHandle: Send {
     fn id(&self) -> EntityId;
@@ -198,7 +198,7 @@ pub mod simple_message_notification {
 
     enum NotificationMessage {
         Text(Cow<'static, str>),
-        Element(fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>),
+        Element(fn(TextStyle, &AppContext) -> AnyElement),
     }
 
     pub struct MessageNotification {
@@ -222,7 +222,7 @@ pub mod simple_message_notification {
         }
 
         pub fn new_element(
-            message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
+            message: fn(TextStyle, &AppContext) -> AnyElement,
         ) -> MessageNotification {
             Self {
                 message: NotificationMessage::Element(message),
@@ -253,8 +253,8 @@ pub mod simple_message_notification {
         // }
     }
 
-    impl Render<Self> for MessageNotification {
-        type Element = Div<Self>;
+    impl Render for MessageNotification {
+        type Element = Div;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             todo!()

crates/workspace2/src/pane.rs 🔗

@@ -26,7 +26,7 @@ use std::{
 };
 
 use ui::v_stack;
-use ui::{prelude::*, Icon, IconButton, IconElement, TextColor, Tooltip};
+use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip};
 use util::truncate_and_remove_front;
 
 #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
@@ -594,7 +594,7 @@ impl Pane {
         self.items.iter()
     }
 
-    pub fn items_of_type<T: Render<T>>(&self) -> impl '_ + Iterator<Item = View<T>> {
+    pub fn items_of_type<T: Render>(&self) -> impl '_ + Iterator<Item = View<T>> {
         self.items
             .iter()
             .filter_map(|item| item.to_any().downcast().ok())
@@ -1344,7 +1344,7 @@ impl Pane {
         item: &Box<dyn ItemHandle>,
         detail: usize,
         cx: &mut ViewContext<'_, Pane>,
-    ) -> impl RenderOnce<Self> {
+    ) -> impl RenderOnce {
         let label = item.tab_content(Some(detail), cx);
         let close_icon = || {
             let id = item.item_id();
@@ -1353,12 +1353,14 @@ impl Pane {
                 .id(item.item_id())
                 .invisible()
                 .group_hover("", |style| style.visible())
-                .child(IconButton::new("close_tab", Icon::Close).on_click(
-                    move |pane: &mut Self, cx| {
-                        pane.close_item_by_id(id, SaveIntent::Close, cx)
-                            .detach_and_log_err(cx);
-                    },
-                ))
+                .child(
+                    IconButton::new("close_tab", Icon::Close).on_click(cx.listener(
+                        move |pane, _, cx| {
+                            pane.close_item_by_id(id, SaveIntent::Close, cx)
+                                .detach_and_log_err(cx);
+                        },
+                    )),
+                )
         };
 
         let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
@@ -1383,9 +1385,9 @@ impl Pane {
             .id(item.item_id())
             .cursor_pointer()
             .when_some(item.tab_tooltip_text(cx), |div, text| {
-                div.tooltip(move |_, cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
+                div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
             })
-            .on_click(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx))
+            .on_click(cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)))
             // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
             // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
             // .on_drop(|_view, state: View<DraggedTab>, cx| {
@@ -1423,12 +1425,12 @@ impl Pane {
                             .then(|| {
                                 IconElement::new(Icon::ExclamationTriangle)
                                     .size(ui::IconSize::Small)
-                                    .color(TextColor::Warning)
+                                    .color(Color::Warning)
                             })
                             .or(item.is_dirty(cx).then(|| {
                                 IconElement::new(Icon::ExclamationTriangle)
                                     .size(ui::IconSize::Small)
-                                    .color(TextColor::Info)
+                                    .color(Color::Info)
                             })),
                     )
                     .children((!close_right).then(|| close_icon()))
@@ -1437,7 +1439,7 @@ impl Pane {
             )
     }
 
-    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl RenderOnce<Self> {
+    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl RenderOnce {
         div()
             .group("tab_bar")
             .id("tab_bar")
@@ -1891,17 +1893,25 @@ impl FocusableView for Pane {
     }
 }
 
-impl Render<Self> for Pane {
-    type Element = Focusable<Self, Div<Self>>;
+impl Render for Pane {
+    type Element = Focusable<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         v_stack()
             .key_context("Pane")
             .track_focus(&self.focus_handle)
-            .on_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx))
-            .on_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx))
-            .on_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx))
-            .on_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx))
+            .on_action(cx.listener(|pane: &mut Pane, _: &SplitLeft, cx| {
+                pane.split(SplitDirection::Left, cx)
+            }))
+            .on_action(
+                cx.listener(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)),
+            )
+            .on_action(cx.listener(|pane: &mut Pane, _: &SplitRight, cx| {
+                pane.split(SplitDirection::Right, cx)
+            }))
+            .on_action(cx.listener(|pane: &mut Pane, _: &SplitDown, cx| {
+                pane.split(SplitDirection::Down, cx)
+            }))
             //     cx.add_action(Pane::toggle_zoom);
             //     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
             //         pane.activate_item(action.0, true, true, cx);
@@ -1922,14 +1932,14 @@ impl Render<Self> for Pane {
             //     cx.add_async_action(Pane::close_items_to_the_right);
             //     cx.add_async_action(Pane::close_all_items);
             .size_full()
-            .on_action(|pane: &mut Self, action: &CloseActiveItem, cx| {
-                pane.close_active_item(action, cx)
-                    .map(|task| task.detach_and_log_err(cx));
-            })
+            .on_action(
+                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
+                    pane.close_active_item(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                }),
+            )
             .child(self.render_tab_bar(cx))
-            // .child(
-            //     div()
-            // ) /* todo!(toolbar) */
+            .child(self.toolbar.clone())
             .child(if let Some(item) = self.active_item() {
                 div().flex().flex_1().child(item.to_any())
             } else {
@@ -2947,8 +2957,8 @@ struct DraggedTab {
     title: String,
 }
 
-impl Render<Self> for DraggedTab {
-    type Element = Div<Self>;
+impl Render for DraggedTab {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div().w_8().h_4().bg(gpui::red())

crates/workspace2/src/pane_group.rs 🔗

@@ -132,7 +132,7 @@ impl PaneGroup {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> impl RenderOnce<Workspace> {
+    ) -> impl RenderOnce {
         self.root.render(
             project,
             0,
@@ -204,7 +204,7 @@ impl Member {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> impl RenderOnce<Workspace> {
+    ) -> impl RenderOnce {
         match self {
             Member::Pane(pane) => {
                 // todo!()
@@ -561,7 +561,7 @@ impl PaneAxis {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> Div<Workspace> {
+    ) -> Div {
         debug_assert!(self.members.len() == self.flexes.lock().len());
 
         div()

crates/workspace2/src/searchable.rs 🔗

@@ -1,7 +1,8 @@
 use std::{any::Any, sync::Arc};
 
 use gpui::{
-    AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WindowContext,
+    AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
+    WindowContext,
 };
 use project2::search::SearchQuery;
 
@@ -129,8 +130,7 @@ pub trait SearchableItemHandle: ItemHandle {
 // todo!("here is where we need to use AnyWeakView");
 impl<T: SearchableItem> SearchableItemHandle for View<T> {
     fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
-        // Box::new(self.downgrade())
-        todo!()
+        Box::new(self.downgrade())
     }
 
     fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
@@ -252,16 +252,15 @@ pub trait WeakSearchableItemHandle: WeakItemHandle {
     // fn into_any(self) -> AnyWeakView;
 }
 
-// todo!()
-// impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
-//     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
-//         Some(Box::new(self.upgrade(cx)?))
-//     }
+impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
+    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+        Some(Box::new(self.upgrade()?))
+    }
 
-//     // fn into_any(self) -> AnyView {
-//     //     self.into_any()
-//     // }
-// }
+    // fn into_any(self) -> AnyView {
+    //     self.into_any()
+    // }
+}
 
 impl PartialEq for Box<dyn WeakSearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {

crates/workspace2/src/status_bar.rs 🔗

@@ -9,7 +9,7 @@ use theme2::ActiveTheme;
 use ui::h_stack;
 use util::ResultExt;
 
-pub trait StatusItemView: Render<Self> {
+pub trait StatusItemView: Render {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -34,8 +34,8 @@ pub struct StatusBar {
     _observe_active_pane: Subscription,
 }
 
-impl Render<Self> for StatusBar {
-    type Element = Div<Self>;
+impl Render for StatusBar {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
@@ -53,14 +53,14 @@ impl Render<Self> for StatusBar {
 }
 
 impl StatusBar {
-    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl RenderOnce<Self> {
+    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl RenderOnce {
         h_stack()
             .items_center()
             .gap_2()
             .children(self.left_items.iter().map(|item| item.to_any()))
     }
 
-    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl RenderOnce<Self> {
+    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl RenderOnce {
         h_stack()
             .items_center()
             .gap_2()

crates/workspace2/src/toolbar.rs 🔗

@@ -1,13 +1,16 @@
 use crate::ItemHandle;
 use gpui::{
-    AnyView, Div, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext,
+    AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
+    ViewContext, WindowContext,
 };
+use theme2::ActiveTheme;
+use ui::{h_stack, v_stack, Button, Color, Icon, IconButton, Label};
 
 pub enum ToolbarItemEvent {
     ChangeLocation(ToolbarItemLocation),
 }
 
-pub trait ToolbarItemView: Render<Self> + EventEmitter<ToolbarItemEvent> {
+pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -39,8 +42,8 @@ trait ToolbarItemViewHandle: Send {
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum ToolbarItemLocation {
     Hidden,
-    PrimaryLeft { flex: Option<(f32, bool)> },
-    PrimaryRight { flex: Option<(f32, bool)> },
+    PrimaryLeft,
+    PrimaryRight,
     Secondary,
 }
 
@@ -51,11 +54,57 @@ pub struct Toolbar {
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
-impl Render<Self> for Toolbar {
-    type Element = Div<Self>;
+impl Toolbar {
+    fn left_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
+        self.items.iter().filter_map(|(item, location)| {
+            if *location == ToolbarItemLocation::PrimaryLeft {
+                Some(item.as_ref())
+            } else {
+                None
+            }
+        })
+    }
+
+    fn right_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
+        self.items.iter().filter_map(|(item, location)| {
+            if *location == ToolbarItemLocation::PrimaryRight {
+                Some(item.as_ref())
+            } else {
+                None
+            }
+        })
+    }
+}
+
+impl Render for Toolbar {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        todo!()
+        //dbg!(&self.items.len());
+        v_stack()
+            .border_b()
+            .border_color(cx.theme().colors().border_variant)
+            .bg(cx.theme().colors().toolbar_background)
+            .child(
+                h_stack()
+                    .justify_between()
+                    .child(
+                        // Toolbar left side
+                        h_stack()
+                            .p_1()
+                            .child(Button::new("crates"))
+                            .child(Label::new("/").color(Color::Muted))
+                            .child(Button::new("workspace2")),
+                    )
+                    // Toolbar right side
+                    .child(
+                        h_stack()
+                            .p_1()
+                            .child(IconButton::new("buffer-search", Icon::MagnifyingGlass))
+                            .child(IconButton::new("inline-assist", Icon::MagicWand)),
+                    ),
+            )
+            .children(self.items.iter().map(|(child, _)| child.to_any()))
     }
 }
 

crates/workspace2/src/workspace2.rs 🔗

@@ -411,7 +411,7 @@ pub enum Event {
 pub struct Workspace {
     window_self: WindowHandle<Self>,
     weak_self: WeakView<Self>,
-    workspace_actions: Vec<Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>>,
+    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
     zoomed: Option<AnyWeakView>,
     zoomed_position: Option<DockPosition>,
     center: PaneGroup,
@@ -3204,53 +3204,63 @@ impl Workspace {
         })
     }
 
-    fn actions(&self, div: Div<Self>) -> Div<Self> {
-        self.add_workspace_actions_listeners(div)
+    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
+        self.add_workspace_actions_listeners(div, cx)
             //     cx.add_async_action(Workspace::open);
             //     cx.add_async_action(Workspace::follow_next_collaborator);
             //     cx.add_async_action(Workspace::close);
-            .on_action(Self::close_inactive_items_and_panes)
-            .on_action(Self::close_all_items_and_panes)
+            .on_action(cx.listener(Self::close_inactive_items_and_panes))
+            .on_action(cx.listener(Self::close_all_items_and_panes))
             //     cx.add_global_action(Workspace::close_global);
             //     cx.add_global_action(restart);
-            .on_action(Self::save_all)
-            .on_action(Self::add_folder_to_project)
-            .on_action(|workspace, _: &Unfollow, cx| {
+            .on_action(cx.listener(Self::save_all))
+            .on_action(cx.listener(Self::add_folder_to_project))
+            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
                 let pane = workspace.active_pane().clone();
                 workspace.unfollow(&pane, cx);
-            })
-            .on_action(|workspace, action: &Save, cx| {
+            }))
+            .on_action(cx.listener(|workspace, action: &Save, cx| {
                 workspace
                     .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
                     .detach_and_log_err(cx);
-            })
-            .on_action(|workspace, _: &SaveAs, cx| {
+            }))
+            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
                 workspace
                     .save_active_item(SaveIntent::SaveAs, cx)
                     .detach_and_log_err(cx);
-            })
-            .on_action(|workspace, _: &ActivatePreviousPane, cx| {
+            }))
+            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
                 workspace.activate_previous_pane(cx)
-            })
-            .on_action(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx))
-            .on_action(|workspace, action: &ActivatePaneInDirection, cx| {
-                workspace.activate_pane_in_direction(action.0, cx)
-            })
-            .on_action(|workspace, action: &SwapPaneInDirection, cx| {
+            }))
+            .on_action(
+                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
+            )
+            .on_action(
+                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
+                    workspace.activate_pane_in_direction(action.0, cx)
+                }),
+            )
+            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
                 workspace.swap_pane_in_direction(action.0, cx)
-            })
-            .on_action(|this, e: &ToggleLeftDock, cx| {
+            }))
+            .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
                 this.toggle_dock(DockPosition::Left, cx);
-            })
-            .on_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
-                workspace.toggle_dock(DockPosition::Right, cx);
-            })
-            .on_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
-                workspace.toggle_dock(DockPosition::Bottom, cx);
-            })
-            .on_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
-                workspace.close_all_docks(cx);
-            })
+            }))
+            .on_action(
+                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
+                    workspace.toggle_dock(DockPosition::Right, cx);
+                }),
+            )
+            .on_action(
+                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
+                    workspace.toggle_dock(DockPosition::Bottom, cx);
+                }),
+            )
+            .on_action(
+                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
+                    workspace.close_all_docks(cx);
+                }),
+            )
         //     cx.add_action(Workspace::activate_pane_at_index);
         //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
         //         workspace.reopen_closed_item(cx).detach();
@@ -3346,22 +3356,24 @@ impl Workspace {
     ) -> &mut Self {
         let callback = Arc::new(callback);
 
-        self.workspace_actions.push(Box::new(move |div| {
+        self.workspace_actions.push(Box::new(move |div, cx| {
             let callback = callback.clone();
-            div.on_action(move |workspace, event, cx| (callback.clone())(workspace, event, cx))
+            div.on_action(
+                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
+            )
         }));
         self
     }
 
-    fn add_workspace_actions_listeners(&self, mut div: Div<Workspace>) -> Div<Workspace> {
+    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
         let mut div = div
-            .on_action(Self::close_inactive_items_and_panes)
-            .on_action(Self::close_all_items_and_panes)
-            .on_action(Self::add_folder_to_project)
-            .on_action(Self::save_all)
-            .on_action(Self::open);
+            .on_action(cx.listener(Self::close_inactive_items_and_panes))
+            .on_action(cx.listener(Self::close_all_items_and_panes))
+            .on_action(cx.listener(Self::add_folder_to_project))
+            .on_action(cx.listener(Self::save_all))
+            .on_action(cx.listener(Self::open));
         for action in self.workspace_actions.iter() {
-            div = (action)(div)
+            div = (action)(div, cx)
         }
         div
     }
@@ -3594,8 +3606,8 @@ impl FocusableView for Workspace {
     }
 }
 
-impl Render<Self> for Workspace {
-    type Element = Div<Self>;
+impl Render for Workspace {
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let mut context = KeyContext::default();
@@ -3611,7 +3623,7 @@ impl Render<Self> for Workspace {
 
         cx.set_rem_size(ui_font_size);
 
-        self.actions(div())
+        self.actions(div(), cx)
             .key_context(context)
             .relative()
             .size_full()

crates/zed/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.114.0"
+version = "0.115.0"
 publish = false
 
 [lib]

crates/zed2/Cargo.toml 🔗

@@ -36,7 +36,7 @@ db = { package = "db2", path = "../db2" }
 editor = { package="editor2", path = "../editor2" }
 # feedback = { path = "../feedback" }
 file_finder = { package="file_finder2", path = "../file_finder2" }
-# search = { path = "../search" }
+search = { package = "search2", path = "../search2" }
 fs = { package = "fs2", path = "../fs2" }
 fsevent = { path = "../fsevent" }
 go_to_line = { package = "go_to_line2", path = "../go_to_line2" }

crates/zed2/src/main.rs 🔗

@@ -211,7 +211,7 @@ fn main() {
         project_panel::init(Assets, cx);
         // channel::init(&client, user_store.clone(), cx);
         // diagnostics::init(cx);
-        // search::init(cx);
+        search::init(cx);
         // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
         // vim::init(cx);
         terminal_view::init(cx);
@@ -367,7 +367,6 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
             cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))?
                 .await
                 .log_err();
-        } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) {
             // todo!(welcome)
             //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
             //todo!()

crates/zed2/src/zed2.rs 🔗

@@ -98,8 +98,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                             // todo!()
                             //     let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
                             //     toolbar.add_item(breadcrumbs, cx);
-                            //     let buffer_search_bar = cx.add_view(BufferSearchBar::new);
-                            //     toolbar.add_item(buffer_search_bar.clone(), cx);
+                            let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
+                            toolbar.add_item(buffer_search_bar.clone(), cx);
                             //     let quick_action_bar = cx.add_view(|_| {
                             //         QuickActionBar::new(buffer_search_bar, workspace)
                             //     });

script/deploy-docs 🔗

@@ -0,0 +1,159 @@
+#!/bin/bash
+
+# Check if the script is run from the root of the repository
+if [ ! -f "Cargo.toml" ] || [ ! -d "crates/zed" ]; then
+    echo "Please run the script from the root of the repository."
+    exit 1
+fi
+
+# Set the environment variables
+TARGET_DIR="../zed-docs"
+PUSH_CHANGES=false
+CLEAN_FOLDERS=false
+
+# Parse command line arguments
+while getopts "pc" opt; do
+  case ${opt} in
+    p )
+      PUSH_CHANGES=true
+      ;;
+    c )
+      CLEAN_FOLDERS=true
+      ;;
+    \? )
+      echo "Invalid option: $OPTARG" 1>&2
+      exit 1
+      ;;
+  esac
+done
+
+# Check if the target documentation directory exists
+if [ ! -d "$TARGET_DIR" ]; then
+    # Prompt the user for input
+    read -p "Can't find ../zed-docs. Do you want to clone the repository (y/n)?" -n 1 -r
+    echo  # Move to a new line
+
+    if [[ $REPLY =~ ^[Yy]$ ]]; then
+        # Clone the repo if the user agrees
+        git clone https://github.com/zed-industries/zed-docs.git "$TARGET_DIR"
+    else
+        # Exit if the user does not agree to clone the repo
+        echo "Exiting without cloning the repository."
+        exit 1
+    fi
+else
+    # If the directory exists, pull the latest changes
+    pushd "$TARGET_DIR" > /dev/null
+    git pull
+    popd > /dev/null
+fi
+
+if "$CLEAN_FOLDERS"; then
+  echo "Cleaning ./doc and ./debug folders..."
+  rm -rf "$TARGET_DIR/doc"
+  rm -rf "$TARGET_DIR/debug"
+fi
+
+# Build the documentation
+CARGO_TARGET_DIR="$TARGET_DIR" cargo doc --workspace --no-deps --open \
+--exclude activity_indicator \
+--exclude ai \
+--exclude assistant \
+--exclude audio \
+--exclude auto_update \
+--exclude breadcrumbs \
+--exclude call \
+--exclude channel \
+--exclude cli \
+--exclude client \
+--exclude clock \
+--exclude collab \
+--exclude collab_ui \
+--exclude collections \
+--exclude command_palette \
+--exclude component_test \
+--exclude context_menu \
+--exclude copilot \
+--exclude copilot_button \
+--exclude db \
+--exclude diagnostics \
+--exclude drag_and_drop \
+--exclude editor \
+--exclude feature_flags \
+--exclude feedback \
+--exclude file_finder \
+--exclude fs \
+--exclude fsevent \
+--exclude fuzzy \
+--exclude git \
+--exclude go_to_line \
+--exclude gpui \
+--exclude gpui_macros \
+--exclude install_cli \
+--exclude journal \
+--exclude language \
+--exclude language_selector \
+--exclude language_tools \
+--exclude live_kit_client \
+--exclude live_kit_server \
+--exclude lsp \
+--exclude media \
+--exclude menu \
+--exclude multi_buffer \
+--exclude node_runtime \
+--exclude notifications \
+--exclude outline \
+--exclude picker \
+--exclude plugin \
+--exclude plugin_macros \
+--exclude plugin_runtime \
+--exclude prettier \
+--exclude project \
+--exclude project_panel \
+--exclude project_symbols \
+--exclude quick_action_bar \
+--exclude recent_projects \
+--exclude refineable \
+--exclude rich_text \
+--exclude rope \
+--exclude rpc \
+--exclude search \
+--exclude semantic_index \
+--exclude settings \
+--exclude snippet \
+--exclude sqlez \
+--exclude sqlez_macros \
+--exclude sum_tree \
+--exclude terminal \
+--exclude terminal_view \
+--exclude text \
+--exclude theme \
+--exclude theme_importer \
+--exclude theme_selector \
+--exclude util \
+--exclude vcs_menu \
+--exclude vim \
+--exclude welcome \
+--exclude xtask \
+--exclude zed \
+--exclude zed-actions
+
+if "$PUSH_CHANGES"; then
+    # Commit the changes and push
+    pushd "$TARGET_DIR" > /dev/null
+    # Check if there are any changes to commit
+    if git diff --quiet && git diff --staged --quiet; then
+        echo "No changes to the documentation."
+    else
+        # Staging the changes
+        git add .
+
+        # Creating a commit with the current datetime
+        DATETIME=$(date +"%Y-%m-%d %H:%M:%S")
+        git commit -m "Update docs – $DATETIME"
+
+        # Pushing the changes
+        git push
+    fi
+    popd > /dev/null
+fi