Merge branch 'main' into terminal-element

Mikayla created

Change summary

Cargo.lock                                                        |   42 
Cargo.toml                                                        |    4 
assets/keymaps/default.json                                       |    7 
crates/activity_indicator2/Cargo.toml                             |   28 
crates/activity_indicator2/src/activity_indicator.rs              |  333 
crates/assistant/src/assistant_panel.rs                           |   39 
crates/auto_update2/src/auto_update.rs                            |    2 
crates/breadcrumbs2/Cargo.toml                                    |   28 
crates/breadcrumbs2/src/breadcrumbs.rs                            |  204 
crates/call2/src/call2.rs                                         |   21 
crates/call2/src/shared_screen.rs                                 |   84 
crates/client/src/client.rs                                       |    2 
crates/client/src/telemetry.rs                                    |    1 
crates/collab/Cargo.toml                                          |    2 
crates/collab/src/db/queries/channels.rs                          |    7 
crates/collab/src/db/tests/channel_tests.rs                       |   14 
crates/collab/src/tests/integration_tests.rs                      |   25 
crates/collab2/src/db/queries/channels.rs                         |    7 
crates/collab2/src/db/tests/channel_tests.rs                      |   16 
crates/collab2/src/tests/integration_tests.rs                     |   25 
crates/collab2/src/tests/test_server.rs                           |    2 
crates/collab_ui2/src/collab_panel.rs                             |  904 
crates/collab_ui2/src/collab_panel/contact_finder.rs              |    2 
crates/collab_ui2/src/collab_titlebar_item.rs                     |  357 
crates/collab_ui2/src/notifications/incoming_call_notification.rs |   31 
crates/command_palette2/src/command_palette.rs                    |    2 
crates/diagnostics/src/diagnostics.rs                             |    8 
crates/diagnostics/src/items.rs                                   |    6 
crates/diagnostics2/src/diagnostics.rs                            |    6 
crates/diagnostics2/src/items.rs                                  |    6 
crates/diagnostics2/src/toolbar_controls.rs                       |    1 
crates/editor2/src/display_map/wrap_map.rs                        |    2 
crates/editor2/src/editor.rs                                      |  826 
crates/editor2/src/editor_tests.rs                                |  682 
crates/editor2/src/element.rs                                     | 1036 
crates/editor2/src/hover_popover.rs                               |    6 
crates/editor2/src/items.rs                                       |  744 
crates/editor2/src/link_go_to_definition.rs                       |   76 
crates/editor2/src/mouse_context_menu.rs                          |   98 
crates/editor2/src/selections_collection.rs                       |   51 
crates/editor2/src/test.rs                                        |    2 
crates/file_finder/src/file_finder.rs                             |   47 
crates/file_finder2/src/file_finder.rs                            |   43 
crates/gpui2/build.rs                                             |    2 
crates/gpui2/src/app.rs                                           |    4 
crates/gpui2/src/app/test_context.rs                              |   50 
crates/gpui2/src/elements/div.rs                                  |  127 
crates/gpui2/src/elements/img.rs                                  |  108 
crates/gpui2/src/elements/text.rs                                 |   60 
crates/gpui2/src/elements/uniform_list.rs                         |    5 
crates/gpui2/src/geometry.rs                                      |   36 
crates/gpui2/src/platform.rs                                      |    5 
crates/gpui2/src/platform/mac/metal_renderer.rs                   |  138 
crates/gpui2/src/platform/mac/shaders.metal                       |   52 
crates/gpui2/src/platform/test/platform.rs                        |    8 
crates/gpui2/src/platform/test/window.rs                          |   22 
crates/gpui2/src/scene.rs                                         |   66 
crates/gpui2/src/taffy.rs                                         |   49 
crates/gpui2/src/view.rs                                          |    8 
crates/gpui2/src/window.rs                                        |   57 
crates/language/src/language.rs                                   |   10 
crates/language2/src/language2.rs                                 |   10 
crates/lsp/src/lsp.rs                                             |   33 
crates/lsp2/src/lsp2.rs                                           |   33 
crates/project/src/project.rs                                     |   37 
crates/project/src/project_tests.rs                               |   93 
crates/project2/src/lsp_command.rs                                |    3 
crates/project2/src/project2.rs                                   |   56 
crates/project2/src/project_tests.rs                              |   93 
crates/project_panel/src/project_panel.rs                         |   14 
crates/project_panel2/src/project_panel.rs                        |   31 
crates/rpc2/src/rpc.rs                                            |    2 
crates/search2/src/buffer_search.rs                               |   14 
crates/search2/src/search.rs                                      |   19 
crates/search2/src/search_bar.rs                                  |   13 
crates/storybook2/src/stories.rs                                  |    2 
crates/storybook2/src/stories/auto_height_editor.rs               |   34 
crates/storybook2/src/stories/text.rs                             |   14 
crates/storybook2/src/story_selector.rs                           |    6 
crates/theme2/src/styles/stories/players.rs                       |   92 
crates/theme_selector2/src/theme_selector.rs                      |    8 
crates/ui2/src/components.rs                                      |   12 
crates/ui2/src/components/avatar.rs                               |   42 
crates/ui2/src/components/button.rs                               |  236 
crates/ui2/src/components/button/button.rs                        |  147 
crates/ui2/src/components/button/button_icon.rs                   |   84 
crates/ui2/src/components/button/button_like.rs                   |  350 
crates/ui2/src/components/button/icon_button.rs                   |  109 
crates/ui2/src/components/button2.rs                              |  405 
crates/ui2/src/components/context_menu.rs                         |  178 
crates/ui2/src/components/disclosure.rs                           |   64 
crates/ui2/src/components/icon_button.rs                          |  135 
crates/ui2/src/components/label.rs                                |   54 
crates/ui2/src/components/list.rs                                 |   66 
crates/ui2/src/components/list/list.rs                            |   60 
crates/ui2/src/components/list/list_header.rs                     |   61 
crates/ui2/src/components/list/list_item.rs                       |   51 
crates/ui2/src/components/popover_menu.rs                         |  231 
crates/ui2/src/components/right_click_menu.rs                     |  185 
crates/ui2/src/components/slot.rs                                 |   14 
crates/ui2/src/components/stories.rs                              |    4 
crates/ui2/src/components/stories/avatar.rs                       |    8 
crates/ui2/src/components/stories/button.rs                       |   81 
crates/ui2/src/components/stories/context_menu.rs                 |   42 
crates/ui2/src/components/stories/disclosure.rs                   |   20 
crates/ui2/src/components/stories/icon_button.rs                  |   20 
crates/ui2/src/components/stories/list.rs                         |    4 
crates/ui2/src/components/stories/list_header.rs                  |   33 
crates/ui2/src/components/toggle.rs                               |   41 
crates/ui2/src/prelude.rs                                         |    3 
crates/ui2/src/selectable.rs                                      |    8 
crates/welcome2/src/base_keymap_picker.rs                         |    6 
crates/workspace2/Cargo.toml                                      |   42 
crates/workspace2/src/dock.rs                                     |   20 
crates/workspace2/src/item.rs                                     |  664 
crates/workspace2/src/notifications.rs                            |   15 
crates/workspace2/src/pane.rs                                     |  324 
crates/workspace2/src/pane_group.rs                               |    4 
crates/workspace2/src/persistence.rs                              |    4 
crates/workspace2/src/persistence/model.rs                        |    4 
crates/workspace2/src/searchable.rs                               |    2 
crates/workspace2/src/status_bar.rs                               |   39 
crates/workspace2/src/toolbar.rs                                  |   16 
crates/workspace2/src/workspace2.rs                               | 1957 
crates/workspace2/src/workspace_settings.rs                       |    2 
crates/zed/src/languages/json.rs                                  |    1 
crates/zed/src/languages/php.rs                                   |    1 
crates/zed/src/languages/tailwind.rs                              |    6 
crates/zed/src/languages/typescript.rs                            |   13 
crates/zed/src/languages/yaml.rs                                  |    6 
crates/zed2/Cargo.toml                                            |    4 
crates/zed2/src/languages/json.rs                                 |    1 
crates/zed2/src/languages/php.rs                                  |    1 
crates/zed2/src/languages/tailwind.rs                             |    6 
crates/zed2/src/languages/typescript.rs                           |   13 
crates/zed2/src/languages/yaml.rs                                 |    6 
crates/zed2/src/main.rs                                           |    4 
crates/zed2/src/zed2.rs                                           |   20 
test.rs                                                           | 5618 -
139 files changed, 7,172 insertions(+), 11,618 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -19,6 +19,25 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "activity_indicator2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "auto_update2",
+ "editor2",
+ "futures 0.3.28",
+ "gpui2",
+ "language2",
+ "project2",
+ "settings2",
+ "smallvec",
+ "theme2",
+ "ui2",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "addr2line"
 version = "0.17.0"
@@ -1076,6 +1095,23 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "breadcrumbs2"
+version = "0.1.0"
+dependencies = [
+ "collections",
+ "editor2",
+ "gpui2",
+ "itertools 0.10.5",
+ "language2",
+ "project2",
+ "search2",
+ "settings2",
+ "theme2",
+ "ui2",
+ "workspace2",
+]
+
 [[package]]
 name = "bromberg_sl2"
 version = "0.6.0"
@@ -1669,7 +1705,7 @@ dependencies = [
 
 [[package]]
 name = "collab"
-version = "0.29.0"
+version = "0.29.1"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -9951,7 +9987,7 @@ dependencies = [
 [[package]]
 name = "tree-sitter"
 version = "0.20.10"
-source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1"
+source = "git+https://github.com/tree-sitter/tree-sitter?rev=b5f461a69bf3df7298b1903574d506179e6390b0#b5f461a69bf3df7298b1903574d506179e6390b0"
 dependencies = [
  "cc",
  "regex",
@@ -11697,6 +11733,7 @@ dependencies = [
 name = "zed2"
 version = "0.109.0"
 dependencies = [
+ "activity_indicator2",
  "ai2",
  "anyhow",
  "async-compression",
@@ -11706,6 +11743,7 @@ dependencies = [
  "audio2",
  "auto_update2",
  "backtrace",
+ "breadcrumbs2",
  "call2",
  "channel2",
  "chrono",

Cargo.toml πŸ”—

@@ -1,6 +1,7 @@
 [workspace]
 members = [
     "crates/activity_indicator",
+    "crates/activity_indicator2",
     "crates/ai",
     "crates/assistant",
     "crates/audio",
@@ -8,6 +9,7 @@ members = [
     "crates/auto_update",
     "crates/auto_update2",
     "crates/breadcrumbs",
+    "crates/breadcrumbs2",
     "crates/call",
     "crates/call2",
     "crates/channel",
@@ -200,7 +202,7 @@ tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", re
 tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
 
 [patch.crates-io]
-tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
+tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "b5f461a69bf3df7298b1903574d506179e6390b0" }
 async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
 
 # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457

assets/keymaps/default.json πŸ”—

@@ -530,12 +530,17 @@
       "alt-cmd-shift-c": "project_panel::CopyRelativePath",
       "f2": "project_panel::Rename",
       "enter": "project_panel::Rename",
-      "space": "project_panel::Open",
       "backspace": "project_panel::Delete",
       "alt-cmd-r": "project_panel::RevealInFinder",
       "alt-shift-f": "project_panel::NewSearchInDirectory"
     }
   },
+  {
+    "context": "ProjectPanel && not_editing",
+    "bindings": {
+      "space": "project_panel::Open"
+    }
+  },
   {
     "context": "CollabPanel && not_editing",
     "bindings": {

crates/activity_indicator2/Cargo.toml πŸ”—

@@ -0,0 +1,28 @@
+[package]
+name = "activity_indicator2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/activity_indicator.rs"
+doctest = false
+
+[dependencies]
+auto_update = { path = "../auto_update2", package = "auto_update2" }
+editor = { path = "../editor2", package = "editor2" }
+language = { path = "../language2", package = "language2" }
+gpui = { path = "../gpui2", package = "gpui2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+ui = { path = "../ui2", package = "ui2" }
+util = { path = "../util" }
+theme = { path = "../theme2", package = "theme2" }
+workspace = { path = "../workspace2", package = "workspace2" }
+
+anyhow.workspace = true
+futures.workspace = true
+smallvec.workspace = true
+
+[dev-dependencies]
+editor = { path = "../editor2", package = "editor2", features = ["test-support"] }

crates/activity_indicator2/src/activity_indicator.rs πŸ”—

@@ -0,0 +1,333 @@
+use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
+use editor::Editor;
+use futures::StreamExt;
+use gpui::{
+    actions, svg, AppContext, CursorStyle, Div, EventEmitter, InteractiveElement as _, Model,
+    ParentElement as _, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, View,
+    ViewContext, VisualContext as _,
+};
+use language::{LanguageRegistry, LanguageServerBinaryStatus};
+use project::{LanguageServerProgress, Project};
+use smallvec::SmallVec;
+use std::{cmp::Reverse, fmt::Write, sync::Arc};
+use ui::h_stack;
+use util::ResultExt;
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
+
+actions!(ShowErrorMessage);
+
+const DOWNLOAD_ICON: &str = "icons/download.svg";
+const WARNING_ICON: &str = "icons/warning.svg";
+
+pub enum Event {
+    ShowError { lsp_name: Arc<str>, error: String },
+}
+
+pub struct ActivityIndicator {
+    statuses: Vec<LspStatus>,
+    project: Model<Project>,
+    auto_updater: Option<Model<AutoUpdater>>,
+}
+
+struct LspStatus {
+    name: Arc<str>,
+    status: LanguageServerBinaryStatus,
+}
+
+struct PendingWork<'a> {
+    language_server_name: &'a str,
+    progress_token: &'a str,
+    progress: &'a LanguageServerProgress,
+}
+
+#[derive(Default)]
+struct Content {
+    icon: Option<&'static str>,
+    message: String,
+    on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
+}
+
+impl ActivityIndicator {
+    pub fn new(
+        workspace: &mut Workspace,
+        languages: Arc<LanguageRegistry>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> View<ActivityIndicator> {
+        let project = workspace.project().clone();
+        let auto_updater = AutoUpdater::get(cx);
+        let this = cx.build_view(|cx: &mut ViewContext<Self>| {
+            let mut status_events = languages.language_server_binary_statuses();
+            cx.spawn(|this, mut cx| async move {
+                while let Some((language, event)) = status_events.next().await {
+                    this.update(&mut cx, |this, cx| {
+                        this.statuses.retain(|s| s.name != language.name());
+                        this.statuses.push(LspStatus {
+                            name: language.name(),
+                            status: event,
+                        });
+                        cx.notify();
+                    })?;
+                }
+                anyhow::Ok(())
+            })
+            .detach();
+            cx.observe(&project, |_, _, cx| cx.notify()).detach();
+
+            if let Some(auto_updater) = auto_updater.as_ref() {
+                cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
+            }
+
+            // cx.observe_active_labeled_tasks(|_, cx| cx.notify())
+            //     .detach();
+
+            Self {
+                statuses: Default::default(),
+                project: project.clone(),
+                auto_updater,
+            }
+        });
+
+        cx.subscribe(&this, move |workspace, _, event, cx| match event {
+            Event::ShowError { lsp_name, error } => {
+                if let Some(buffer) = project
+                    .update(cx, |project, cx| project.create_buffer(error, None, cx))
+                    .log_err()
+                {
+                    buffer.update(cx, |buffer, cx| {
+                        buffer.edit(
+                            [(0..0, format!("Language server error: {}\n\n", lsp_name))],
+                            None,
+                            cx,
+                        );
+                    });
+                    workspace.add_item(
+                        Box::new(cx.build_view(|cx| {
+                            Editor::for_buffer(buffer, Some(project.clone()), cx)
+                        })),
+                        cx,
+                    );
+                }
+            }
+        })
+        .detach();
+        this
+    }
+
+    fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
+        self.statuses.retain(|status| {
+            if let LanguageServerBinaryStatus::Failed { error } = &status.status {
+                cx.emit(Event::ShowError {
+                    lsp_name: status.name.clone(),
+                    error: error.clone(),
+                });
+                false
+            } else {
+                true
+            }
+        });
+
+        cx.notify();
+    }
+
+    fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
+        if let Some(updater) = &self.auto_updater {
+            updater.update(cx, |updater, cx| {
+                updater.dismiss_error(cx);
+            });
+        }
+        cx.notify();
+    }
+
+    fn pending_language_server_work<'a>(
+        &self,
+        cx: &'a AppContext,
+    ) -> impl Iterator<Item = PendingWork<'a>> {
+        self.project
+            .read(cx)
+            .language_server_statuses()
+            .rev()
+            .filter_map(|status| {
+                if status.pending_work.is_empty() {
+                    None
+                } else {
+                    let mut pending_work = status
+                        .pending_work
+                        .iter()
+                        .map(|(token, progress)| PendingWork {
+                            language_server_name: status.name.as_str(),
+                            progress_token: token.as_str(),
+                            progress,
+                        })
+                        .collect::<SmallVec<[_; 4]>>();
+                    pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));
+                    Some(pending_work)
+                }
+            })
+            .flatten()
+    }
+
+    fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
+        // Show any language server has pending activity.
+        let mut pending_work = self.pending_language_server_work(cx);
+        if let Some(PendingWork {
+            language_server_name,
+            progress_token,
+            progress,
+        }) = pending_work.next()
+        {
+            let mut message = language_server_name.to_string();
+
+            message.push_str(": ");
+            if let Some(progress_message) = progress.message.as_ref() {
+                message.push_str(progress_message);
+            } else {
+                message.push_str(progress_token);
+            }
+
+            if let Some(percentage) = progress.percentage {
+                write!(&mut message, " ({}%)", percentage).unwrap();
+            }
+
+            let additional_work_count = pending_work.count();
+            if additional_work_count > 0 {
+                write!(&mut message, " + {} more", additional_work_count).unwrap();
+            }
+
+            return Content {
+                icon: None,
+                message,
+                on_click: None,
+            };
+        }
+
+        // Show any language server installation info.
+        let mut downloading = SmallVec::<[_; 3]>::new();
+        let mut checking_for_update = SmallVec::<[_; 3]>::new();
+        let mut failed = SmallVec::<[_; 3]>::new();
+        for status in &self.statuses {
+            let name = status.name.clone();
+            match status.status {
+                LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
+                LanguageServerBinaryStatus::Downloading => downloading.push(name),
+                LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
+                LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
+            }
+        }
+
+        if !downloading.is_empty() {
+            return Content {
+                icon: Some(DOWNLOAD_ICON),
+                message: format!(
+                    "Downloading {} language server{}...",
+                    downloading.join(", "),
+                    if downloading.len() > 1 { "s" } else { "" }
+                ),
+                on_click: None,
+            };
+        } else if !checking_for_update.is_empty() {
+            return Content {
+                icon: Some(DOWNLOAD_ICON),
+                message: format!(
+                    "Checking for updates to {} language server{}...",
+                    checking_for_update.join(", "),
+                    if checking_for_update.len() > 1 {
+                        "s"
+                    } else {
+                        ""
+                    }
+                ),
+                on_click: None,
+            };
+        } else if !failed.is_empty() {
+            return Content {
+                icon: Some(WARNING_ICON),
+                message: format!(
+                    "Failed to download {} language server{}. Click to show error.",
+                    failed.join(", "),
+                    if failed.len() > 1 { "s" } else { "" }
+                ),
+                on_click: Some(Arc::new(|this, cx| {
+                    this.show_error_message(&Default::default(), cx)
+                })),
+            };
+        }
+
+        // Show any application auto-update info.
+        if let Some(updater) = &self.auto_updater {
+            return match &updater.read(cx).status() {
+                AutoUpdateStatus::Checking => Content {
+                    icon: Some(DOWNLOAD_ICON),
+                    message: "Checking for Zed updates…".to_string(),
+                    on_click: None,
+                },
+                AutoUpdateStatus::Downloading => Content {
+                    icon: Some(DOWNLOAD_ICON),
+                    message: "Downloading Zed update…".to_string(),
+                    on_click: None,
+                },
+                AutoUpdateStatus::Installing => Content {
+                    icon: Some(DOWNLOAD_ICON),
+                    message: "Installing Zed update…".to_string(),
+                    on_click: None,
+                },
+                AutoUpdateStatus::Updated => Content {
+                    icon: None,
+                    message: "Click to restart and update Zed".to_string(),
+                    on_click: Some(Arc::new(|_, cx| {
+                        workspace::restart(&Default::default(), cx)
+                    })),
+                },
+                AutoUpdateStatus::Errored => Content {
+                    icon: Some(WARNING_ICON),
+                    message: "Auto update failed".to_string(),
+                    on_click: Some(Arc::new(|this, cx| {
+                        this.dismiss_error_message(&Default::default(), cx)
+                    })),
+                },
+                AutoUpdateStatus::Idle => Default::default(),
+            };
+        }
+
+        // todo!(show active tasks)
+        // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
+        //     return Content {
+        //         icon: None,
+        //         message: most_recent_active_task.to_string(),
+        //         on_click: None,
+        //     };
+        // }
+
+        Default::default()
+    }
+}
+
+impl EventEmitter<Event> for ActivityIndicator {}
+
+impl Render for ActivityIndicator {
+    type Element = Stateful<Div>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let content = self.content_to_render(cx);
+
+        let mut result = h_stack()
+            .id("activity-indicator")
+            .on_action(cx.listener(Self::show_error_message))
+            .on_action(cx.listener(Self::dismiss_error_message));
+
+        if let Some(on_click) = content.on_click {
+            result = result
+                .cursor(CursorStyle::PointingHand)
+                .on_click(cx.listener(move |this, _, cx| {
+                    on_click(this, cx);
+                }))
+        }
+
+        result
+            .children(content.icon.map(|icon| svg().path(icon)))
+            .child(SharedString::from(content.message))
+    }
+}
+
+impl StatusItemView for ActivityIndicator {
+    fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
+}

crates/assistant/src/assistant_panel.rs πŸ”—

@@ -1218,6 +1218,31 @@ impl View for AssistantPanel {
         let style = &theme.assistant;
         if let Some(api_key_editor) = self.api_key_editor.as_ref() {
             Flex::column()
+                .with_child(
+                    Text::new(
+                        "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
+                        style.api_key_prompt.text.clone(),
+                    ),
+                )
+                .with_child(
+                    Text::new(
+                        " - Having a subscription for another service like GitHub Copilot won't work.",
+                        style.api_key_prompt.text.clone(),
+                    ),
+                )
+                .with_child(
+                    Text::new(
+                        " - You can create a api key at: platform.openai.com/api-keys",
+                        style.api_key_prompt.text.clone(),
+                    ),
+                )
+                .with_child(
+                    Text::new(
+                        " ",
+                        style.api_key_prompt.text.clone(),
+                    )
+                    .aligned(),
+                )
                 .with_child(
                     Text::new(
                         "Paste your OpenAI API key and press Enter to use the assistant",
@@ -1231,6 +1256,20 @@ impl View for AssistantPanel {
                         .with_style(style.api_key_editor.container)
                         .aligned(),
                 )
+                .with_child(
+                    Text::new(
+                        " ",
+                        style.api_key_prompt.text.clone(),
+                    )
+                    .aligned(),
+                )
+                .with_child(
+                    Text::new(
+                        "Click on the Z button in the status bar to close this panel.",
+                        style.api_key_prompt.text.clone(),
+                    )
+                    .aligned(),
+                )
                 .contained()
                 .with_style(style.api_key_prompt.container)
                 .aligned()

crates/auto_update2/src/auto_update.rs πŸ”—

@@ -102,7 +102,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
     })
     .detach();
 
-    if let Some(version) = *ZED_APP_VERSION {
+    if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
         let auto_updater = cx.build_model(|cx| {
             let updater = AutoUpdater::new(version, http_client, server_url);
 

crates/breadcrumbs2/Cargo.toml πŸ”—

@@ -0,0 +1,28 @@
+[package]
+name = "breadcrumbs2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/breadcrumbs.rs"
+doctest = false
+
+[dependencies]
+collections = { path = "../collections" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+ui = { package = "ui2", path = "../ui2" }
+language = { package = "language2", path = "../language2" }
+project = { package = "project2", path = "../project2" }
+search = { package = "search2", path = "../search2" }
+settings = { package = "settings2", path = "../settings2" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+# outline = { path = "../outline" }
+itertools = "0.10"
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }

crates/breadcrumbs2/src/breadcrumbs.rs πŸ”—

@@ -0,0 +1,204 @@
+use gpui::{
+    Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
+    ViewContext, WeakView,
+};
+use itertools::Itertools;
+use theme::ActiveTheme;
+use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label};
+use workspace::{
+    item::{ItemEvent, ItemHandle},
+    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+pub enum Event {
+    UpdateLocation,
+}
+
+pub struct Breadcrumbs {
+    pane_focused: bool,
+    active_item: Option<Box<dyn ItemHandle>>,
+    subscription: Option<Subscription>,
+    _workspace: WeakView<Workspace>,
+}
+
+impl Breadcrumbs {
+    pub fn new(workspace: &Workspace) -> Self {
+        Self {
+            pane_focused: false,
+            active_item: Default::default(),
+            subscription: Default::default(),
+            _workspace: workspace.weak_handle(),
+        }
+    }
+}
+
+impl EventEmitter<Event> for Breadcrumbs {}
+impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
+
+impl Render for Breadcrumbs {
+    type Element = Component<ButtonLike>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let button = ButtonLike::new("breadcrumbs")
+            .style(ButtonStyle::Transparent)
+            .disabled(true);
+
+        let active_item = match &self.active_item {
+            Some(active_item) => active_item,
+            None => return button.into_element(),
+        };
+        let not_editor = active_item.downcast::<editor::Editor>().is_none();
+
+        let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) {
+            Some(breadcrumbs) => breadcrumbs,
+            None => return button.into_element(),
+        }
+        .into_iter()
+        .map(|breadcrumb| {
+            StyledText::new(breadcrumb.text)
+                .with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default())
+                .into_any()
+        });
+
+        let button = button.children(Itertools::intersperse_with(breadcrumbs, || {
+            Label::new(" β€Ί ").into_any_element()
+        }));
+
+        if not_editor || !self.pane_focused {
+            return button.into_element();
+        }
+
+        // let this = cx.view().downgrade();
+        button
+            .style(ButtonStyle::Filled)
+            .disabled(false)
+            .on_click(move |_, _cx| {
+                todo!("outline::toggle");
+                // this.update(cx, |this, cx| {
+                //     if let Some(workspace) = this.workspace.upgrade() {
+                //         workspace.update(cx, |_workspace, _cx| {
+                //             outline::toggle(workspace, &Default::default(), cx)
+                //         })
+                //     }
+                // })
+                // .ok();
+            })
+            .into_element()
+    }
+}
+
+// impl View for Breadcrumbs {
+//     fn ui_name() -> &'static str {
+//         "Breadcrumbs"
+//     }
+
+//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+//         let active_item = match &self.active_item {
+//             Some(active_item) => active_item,
+//             None => return Empty::new().into_any(),
+//         };
+//         let not_editor = active_item.downcast::<editor::Editor>().is_none();
+
+//         let theme = theme::current(cx).clone();
+//         let style = &theme.workspace.toolbar.breadcrumbs;
+
+//         let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
+//             Some(breadcrumbs) => breadcrumbs,
+//             None => return Empty::new().into_any(),
+//         }
+//         .into_iter()
+//         .map(|breadcrumb| {
+//             Text::new(
+//                 breadcrumb.text,
+//                 theme.workspace.toolbar.breadcrumbs.default.text.clone(),
+//             )
+//             .with_highlights(breadcrumb.highlights.unwrap_or_default())
+//             .into_any()
+//         });
+
+//         let crumbs = Flex::row()
+//             .with_children(Itertools::intersperse_with(breadcrumbs, || {
+//                 Label::new(" β€Ί ", style.default.text.clone()).into_any()
+//             }))
+//             .constrained()
+//             .with_height(theme.workspace.toolbar.breadcrumb_height)
+//             .contained();
+
+//         if not_editor || !self.pane_focused {
+//             return crumbs
+//                 .with_style(style.default.container)
+//                 .aligned()
+//                 .left()
+//                 .into_any();
+//         }
+
+//         MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
+//             let style = style.style_for(state);
+//             crumbs.with_style(style.container)
+//         })
+//         .on_click(MouseButton::Left, |_, this, cx| {
+//             if let Some(workspace) = this.workspace.upgrade(cx) {
+//                 workspace.update(cx, |workspace, cx| {
+//                     outline::toggle(workspace, &Default::default(), cx)
+//                 })
+//             }
+//         })
+//         .with_tooltip::<Breadcrumbs>(
+//             0,
+//             "Show symbol outline".to_owned(),
+//             Some(Box::new(outline::Toggle)),
+//             theme.tooltip.clone(),
+//             cx,
+//         )
+//         .aligned()
+//         .left()
+//         .into_any()
+//     }
+// }
+
+impl ToolbarItemView for Breadcrumbs {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) -> ToolbarItemLocation {
+        cx.notify();
+        self.active_item = None;
+        if let Some(item) = active_pane_item {
+            let this = cx.view().downgrade();
+            self.subscription = Some(item.subscribe_to_item_events(
+                cx,
+                Box::new(move |event, cx| {
+                    if let ItemEvent::UpdateBreadcrumbs = event {
+                        this.update(cx, |_, cx| {
+                            cx.emit(Event::UpdateLocation);
+                            cx.notify();
+                        })
+                        .ok();
+                    }
+                }),
+            ));
+            self.active_item = Some(item.boxed_clone());
+            item.breadcrumb_location(cx)
+        } else {
+            ToolbarItemLocation::Hidden
+        }
+    }
+
+    // fn location_for_event(
+    //     &self,
+    //     _: &Event,
+    //     current_location: ToolbarItemLocation,
+    //     cx: &AppContext,
+    // ) -> ToolbarItemLocation {
+    //     if let Some(active_item) = self.active_item.as_ref() {
+    //         active_item.breadcrumb_location(cx)
+    //     } else {
+    //         current_location
+    //     }
+    // }
+
+    fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
+        self.pane_focused = pane_focused;
+    }
+}

crates/call2/src/call2.rs πŸ”—

@@ -15,7 +15,7 @@ use collections::HashSet;
 use futures::{channel::oneshot, future::Shared, Future, FutureExt};
 use gpui::{
     AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel,
-    Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowHandle,
+    Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle,
 };
 pub use participant::ParticipantLocation;
 use postage::watch;
@@ -557,24 +557,17 @@ pub fn report_call_event_for_channel(
 
 pub struct Call {
     active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
-    parent_workspace: WeakView<Workspace>,
 }
 
 impl Call {
-    pub fn new(
-        parent_workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<'_, Workspace>,
-    ) -> Box<dyn CallHandler> {
+    pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
         let mut active_call = None;
         if cx.has_global::<Model<ActiveCall>>() {
             let call = cx.global::<Model<ActiveCall>>().clone();
             let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
             active_call = Some((call, subscriptions));
         }
-        Box::new(Self {
-            active_call,
-            parent_workspace,
-        })
+        Box::new(Self { active_call })
     }
     fn on_active_call_event(
         workspace: &mut Workspace,
@@ -597,6 +590,7 @@ impl CallHandler for Call {
     fn peer_state(
         &mut self,
         leader_id: PeerId,
+        project: &Model<Project>,
         cx: &mut ViewContext<Workspace>,
     ) -> Option<(bool, bool)> {
         let (call, _) = self.active_call.as_ref()?;
@@ -608,12 +602,7 @@ impl CallHandler for Call {
         match participant.location {
             ParticipantLocation::SharedProject { project_id } => {
                 leader_in_this_app = true;
-                leader_in_this_project = Some(project_id)
-                    == self
-                        .parent_workspace
-                        .update(cx, |this, cx| this.project().read(cx).remote_id())
-                        .log_err()
-                        .flatten();
+                leader_in_this_project = Some(project_id) == project.read(cx).remote_id();
             }
             ParticipantLocation::UnsharedProject => {
                 leader_in_this_app = true;

crates/call2/src/shared_screen.rs πŸ”—

@@ -3,10 +3,12 @@ use anyhow::Result;
 use client::{proto::PeerId, User};
 use futures::StreamExt;
 use gpui::{
-    div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render,
-    SharedString, Task, View, ViewContext, VisualContext, WindowContext,
+    div, img, AppContext, Div, Element, EventEmitter, FocusHandle, Focusable, FocusableView,
+    InteractiveElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
+    VisualContext, WindowContext,
 };
 use std::sync::{Arc, Weak};
+use ui::{h_stack, Icon, IconElement};
 use workspace::{item::Item, ItemNavHistory, WorkspaceId};
 
 pub enum Event {
@@ -16,8 +18,6 @@ pub enum Event {
 pub struct SharedScreen {
     track: Weak<RemoteVideoTrack>,
     frame: Option<Frame>,
-    // temporary addition just to render something interactive.
-    current_frame_id: usize,
     pub peer_id: PeerId,
     user: Arc<User>,
     nav_history: Option<ItemNavHistory>,
@@ -51,7 +51,6 @@ impl SharedScreen {
                 Ok(())
             }),
             focus: cx.focus_handle(),
-            current_frame_id: 0,
         }
     }
 }
@@ -65,50 +64,16 @@ impl FocusableView for SharedScreen {
     }
 }
 impl Render for SharedScreen {
-    type Element = Div;
+    type Element = Focusable<Div>;
+
     fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
-        let frame = self.frame.clone();
-        let frame_id = self.current_frame_id;
-        self.current_frame_id = self.current_frame_id.wrapping_add(1);
-        div().children(frame.map(|_| {
-            ui::Label::new(frame_id.to_string()).color(ui::Color::Error)
-            // img().data(Arc::new(ImageData::new(image::ImageBuffer::new(
-            //     frame.width() as u32,
-            //     frame.height() as u32,
-            // ))))
-        }))
+        div().track_focus(&self.focus).size_full().children(
+            self.frame
+                .as_ref()
+                .map(|frame| img(frame.image()).size_full()),
+        )
     }
 }
-// impl View for SharedScreen {
-//     fn ui_name() -> &'static str {
-//         "SharedScreen"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         enum Focus {}
-
-//         let frame = self.frame.clone();
-//         MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
-//             Canvas::new(move |bounds, _, _, cx| {
-//                 if let Some(frame) = frame.clone() {
-//                     let size = constrain_size_preserving_aspect_ratio(
-//                         bounds.size(),
-//                         vec2f(frame.width() as f32, frame.height() as f32),
-//                     );
-//                     let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
-//                     cx.scene().push_surface(gpui::platform::mac::Surface {
-//                         bounds: RectF::new(origin, size),
-//                         image_buffer: frame.image(),
-//                     });
-//                 }
-//             })
-//             .contained()
-//             .with_style(theme::current(cx).shared_screen)
-//         })
-//         .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
-//         .into_any()
-//     }
-// }
 
 impl Item for SharedScreen {
     fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
@@ -121,25 +86,14 @@ impl Item for SharedScreen {
     }
 
     fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement {
-        div().child("Shared screen").into_any()
-        // Flex::row()
-        //     .with_child(
-        //         Svg::new("icons/desktop.svg")
-        //             .with_color(style.label.text.color)
-        //             .constrained()
-        //             .with_width(style.type_icon_width)
-        //             .aligned()
-        //             .contained()
-        //             .with_margin_right(style.spacing),
-        //     )
-        //     .with_child(
-        //         Label::new(
-        //             format!("{}'s screen", self.user.github_login),
-        //             style.label.clone(),
-        //         )
-        //         .aligned(),
-        //     )
-        //     .into_any()
+        h_stack()
+            .gap_1()
+            .child(IconElement::new(Icon::Screen))
+            .child(SharedString::from(format!(
+                "{}'s screen",
+                self.user.github_login
+            )))
+            .into_any()
     }
 
     fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {

crates/client/src/client.rs πŸ”—

@@ -346,7 +346,7 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
     }
 }
 
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
 pub struct TelemetrySettings {
     pub diagnostics: bool,
     pub metrics: bool,

crates/client/src/telemetry.rs πŸ”—

@@ -350,6 +350,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
+        dbg!(telemetry_settings);
         self.report_clickhouse_event(event, telemetry_settings, true)
     }
 

crates/collab/Cargo.toml πŸ”—

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

crates/collab/src/db/queries/channels.rs πŸ”—

@@ -1220,6 +1220,13 @@ impl Database {
                 self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
                     .await?;
 
+                if new_parent
+                    .ancestors_including_self()
+                    .any(|id| id == channel.id)
+                {
+                    Err(anyhow!("cannot move a channel into one of its descendants"))?;
+                }
+
                 new_parent_path = new_parent.path();
                 new_parent_channel = Some(new_parent);
             } else {

crates/collab/src/db/tests/channel_tests.rs πŸ”—

@@ -450,6 +450,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
             (livestreaming_id, &[projects_id]),
         ],
     );
+
+    // Can't move a channel into its ancestor
+    db.move_channel(projects_id, Some(livestreaming_id), user_id)
+        .await
+        .unwrap_err();
+    let result = db.get_channels_for_user(user_id).await.unwrap();
+    assert_channel_tree(
+        result.channels,
+        &[
+            (zed_id, &[]),
+            (projects_id, &[]),
+            (livestreaming_id, &[projects_id]),
+        ],
+    );
 }
 
 test_both_dbs!(

crates/collab/src/tests/integration_tests.rs πŸ”—

@@ -3941,7 +3941,7 @@ async fn test_collaborating_with_diagnostics(
     // Ensure client B observes the new diagnostics.
     project_b.read_with(cx_b, |project, cx| {
         assert_eq!(
-            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
             &[(
                 ProjectPath {
                     worktree_id,
@@ -3961,14 +3961,14 @@ async fn test_collaborating_with_diagnostics(
     let project_c = client_c.build_remote_project(project_id, cx_c).await;
     let project_c_diagnostic_summaries =
         Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
-            project.diagnostic_summaries(cx).collect::<Vec<_>>()
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
         })));
     project_c.update(cx_c, |_, cx| {
         let summaries = project_c_diagnostic_summaries.clone();
         cx.subscribe(&project_c, {
             move |p, _, event, cx| {
                 if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
-                    *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
+                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
                 }
             }
         })
@@ -4018,7 +4018,7 @@ async fn test_collaborating_with_diagnostics(
     deterministic.run_until_parked();
     project_b.read_with(cx_b, |project, cx| {
         assert_eq!(
-            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
             [(
                 ProjectPath {
                     worktree_id,
@@ -4034,7 +4034,7 @@ async fn test_collaborating_with_diagnostics(
     });
     project_c.read_with(cx_c, |project, cx| {
         assert_eq!(
-            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
             [(
                 ProjectPath {
                     worktree_id,
@@ -4097,13 +4097,22 @@ async fn test_collaborating_with_diagnostics(
     );
     deterministic.run_until_parked();
     project_a.read_with(cx_a, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        assert_eq!(
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+            []
+        )
     });
     project_b.read_with(cx_b, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        assert_eq!(
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+            []
+        )
     });
     project_c.read_with(cx_c, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        assert_eq!(
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+            []
+        )
     });
 }
 

crates/collab2/src/db/queries/channels.rs πŸ”—

@@ -1220,6 +1220,13 @@ impl Database {
                 self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
                     .await?;
 
+                if new_parent
+                    .ancestors_including_self()
+                    .any(|id| id == channel.id)
+                {
+                    Err(anyhow!("cannot move a channel into one of its descendants"))?;
+                }
+
                 new_parent_path = new_parent.path();
                 new_parent_channel = Some(new_parent);
             } else {

crates/collab2/src/db/tests/channel_tests.rs πŸ”—

@@ -420,8 +420,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
         .await
         .unwrap();
 
-    // Dag is: zed - projects - livestreaming
-
     // Move to same parent should be a no-op
     assert!(db
         .move_channel(projects_id, Some(zed_id), user_id)
@@ -450,6 +448,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
             (livestreaming_id, &[projects_id]),
         ],
     );
+
+    // Can't move a channel into its ancestor
+    db.move_channel(projects_id, Some(livestreaming_id), user_id)
+        .await
+        .unwrap_err();
+    let result = db.get_channels_for_user(user_id).await.unwrap();
+    assert_channel_tree(
+        result.channels,
+        &[
+            (zed_id, &[]),
+            (projects_id, &[]),
+            (livestreaming_id, &[projects_id]),
+        ],
+    );
 }
 
 test_both_dbs!(

crates/collab2/src/tests/integration_tests.rs πŸ”—

@@ -3688,7 +3688,7 @@ async fn test_collaborating_with_diagnostics(
 
     project_b.read_with(cx_b, |project, cx| {
         assert_eq!(
-            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
             &[(
                 ProjectPath {
                     worktree_id,
@@ -3708,14 +3708,14 @@ async fn test_collaborating_with_diagnostics(
     let project_c = client_c.build_remote_project(project_id, cx_c).await;
     let project_c_diagnostic_summaries =
         Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
-            project.diagnostic_summaries(cx).collect::<Vec<_>>()
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
         })));
     project_c.update(cx_c, |_, cx| {
         let summaries = project_c_diagnostic_summaries.clone();
         cx.subscribe(&project_c, {
             move |p, _, event, cx| {
                 if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
-                    *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
+                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
                 }
             }
         })
@@ -3766,7 +3766,7 @@ async fn test_collaborating_with_diagnostics(
 
     project_b.read_with(cx_b, |project, cx| {
         assert_eq!(
-            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
             [(
                 ProjectPath {
                     worktree_id,
@@ -3783,7 +3783,7 @@ async fn test_collaborating_with_diagnostics(
 
     project_c.read_with(cx_c, |project, cx| {
         assert_eq!(
-            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
             [(
                 ProjectPath {
                     worktree_id,
@@ -3844,15 +3844,24 @@ async fn test_collaborating_with_diagnostics(
     executor.run_until_parked();
 
     project_a.read_with(cx_a, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        assert_eq!(
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+            []
+        )
     });
 
     project_b.read_with(cx_b, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        assert_eq!(
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+            []
+        )
     });
 
     project_c.read_with(cx_c, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+        assert_eq!(
+            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+            []
+        )
     });
 }
 

crates/collab2/src/tests/test_server.rs πŸ”—

@@ -221,7 +221,7 @@ impl TestServer {
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
-            call_factory: |_, _| Box::new(workspace::TestCallHandler),
+            call_factory: |_| Box::new(workspace::TestCallHandler),
         });
 
         cx.update(|cx| {

crates/collab_ui2/src/collab_panel.rs πŸ”—

@@ -17,8 +17,9 @@ mod contact_finder;
 //     Client, Contact, User, UserStore,
 // };
 use contact_finder::ContactFinder;
-use menu::Confirm;
+use menu::{Cancel, Confirm, SelectNext, SelectPrev};
 use rpc::proto;
+use theme::{ActiveTheme, ThemeSettings};
 // use context_menu::{ContextMenu, ContextMenuItem};
 // use db::kvp::KEY_VALUE_STORE;
 // use drag_and_drop::{DragAndDrop, Draggable};
@@ -151,10 +152,10 @@ actions!(
 //     ]
 // );
 
-// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-// struct ChannelMoveClipboard {
-//     channel_id: ChannelId,
-// }
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+struct ChannelMoveClipboard {
+    channel_id: ChannelId,
+}
 
 const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
 
@@ -168,17 +169,19 @@ use editor::Editor;
 use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
-    actions, div, img, prelude::*, serde_json, Action, AppContext, AsyncWindowContext, Div,
-    EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model,
-    ParentElement, PromptLevel, Render, RenderOnce, SharedString, Styled, Subscription, Task, View,
-    ViewContext, VisualContext, WeakView,
+    actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext,
+    AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle,
+    Focusable, FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent,
+    ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, ScrollHandle, SharedString,
+    Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
 };
-use project::Fs;
+use project::{Fs, Project};
 use serde_derive::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
+use ui::prelude::*;
 use ui::{
-    h_stack, v_stack, Avatar, Button, Color, Icon, IconButton, IconElement, Label, List,
-    ListHeader, ListItem, Toggle, Tooltip,
+    h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
+    Label, List, ListHeader, ListItem, Tooltip,
 };
 use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
@@ -230,26 +233,6 @@ pub fn init(cx: &mut AppContext) {
     //         },
     //     );
 
-    //     cx.add_action(
-    //         |panel: &mut CollabPanel,
-    //          action: &StartMoveChannelFor,
-    //          _: &mut ViewContext<CollabPanel>| {
-    //             panel.channel_clipboard = Some(ChannelMoveClipboard {
-    //                 channel_id: action.channel_id,
-    //             });
-    //         },
-    //     );
-
-    //     cx.add_action(
-    //         |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
-    //             if let Some(channel) = panel.selected_channel() {
-    //                 panel.channel_clipboard = Some(ChannelMoveClipboard {
-    //                     channel_id: channel.id,
-    //                 })
-    //             }
-    //         },
-    //     );
-
     //     cx.add_action(
     //         |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
     //             let Some(clipboard) = panel.channel_clipboard.take() else {
@@ -303,12 +286,12 @@ impl ChannelEditingState {
 }
 
 pub struct CollabPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
     fs: Arc<dyn Fs>,
     focus_handle: FocusHandle,
-    // channel_clipboard: Option<ChannelMoveClipboard>,
+    channel_clipboard: Option<ChannelMoveClipboard>,
     pending_serialization: Task<Option<()>>,
-    // context_menu: ViewHandle<ContextMenu>,
+    context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
     filter_editor: View<Editor>,
     channel_name_editor: View<Editor>,
     channel_editing_state: Option<ChannelEditingState>,
@@ -317,9 +300,9 @@ pub struct CollabPanel {
     channel_store: Model<ChannelStore>,
     user_store: Model<UserStore>,
     client: Arc<Client>,
-    // project: ModelHandle<Project>,
+    project: Model<Project>,
     match_candidates: Vec<StringMatchCandidate>,
-    // list_state: ListState<Self>,
+    scroll_handle: ScrollHandle,
     subscriptions: Vec<Subscription>,
     collapsed_sections: Vec<Section>,
     collapsed_channels: Vec<ChannelId>,
@@ -337,7 +320,7 @@ enum ChannelDragTarget {
 
 #[derive(Serialize, Deserialize)]
 struct SerializedCollabPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
     collapsed_channels: Option<Vec<u64>>,
 }
 
@@ -401,10 +384,6 @@ enum ListEntry {
     ContactPlaceholder,
 }
 
-// impl Entity for CollabPanel {
-//     type Event = Event;
-// }
-
 impl CollabPanel {
     pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
         cx.build_view(|cx| {
@@ -416,28 +395,28 @@ impl CollabPanel {
                 editor
             });
 
-            //             cx.subscribe(&filter_editor, |this, _, event, cx| {
-            //                 if let editor::Event::BufferEdited = event {
-            //                     let query = this.filter_editor.read(cx).text(cx);
-            //                     if !query.is_empty() {
-            //                         this.selection.take();
-            //                     }
-            //                     this.update_entries(true, cx);
-            //                     if !query.is_empty() {
-            //                         this.selection = this
-            //                             .entries
-            //                             .iter()
-            //                             .position(|entry| !matches!(entry, ListEntry::Header(_)));
-            //                     }
-            //                 } else if let editor::Event::Blurred = event {
-            //                     let query = this.filter_editor.read(cx).text(cx);
-            //                     if query.is_empty() {
-            //                         this.selection.take();
-            //                         this.update_entries(true, cx);
-            //                     }
-            //                 }
-            //             })
-            //             .detach();
+            cx.subscribe(&filter_editor, |this: &mut Self, _, event, cx| {
+                if let editor::EditorEvent::BufferEdited = event {
+                    let query = this.filter_editor.read(cx).text(cx);
+                    if !query.is_empty() {
+                        this.selection.take();
+                    }
+                    this.update_entries(true, cx);
+                    if !query.is_empty() {
+                        this.selection = this
+                            .entries
+                            .iter()
+                            .position(|entry| !matches!(entry, ListEntry::Header(_)));
+                    }
+                } else if let editor::EditorEvent::Blurred = event {
+                    let query = this.filter_editor.read(cx).text(cx);
+                    if query.is_empty() {
+                        this.selection.take();
+                        this.update_entries(true, cx);
+                    }
+                }
+            })
+            .detach();
 
             let channel_name_editor = cx.build_view(|cx| Editor::single_line(cx));
 
@@ -589,10 +568,10 @@ impl CollabPanel {
             let mut this = Self {
                 width: None,
                 focus_handle: cx.focus_handle(),
-                //                 channel_clipboard: None,
+                channel_clipboard: None,
                 fs: workspace.app_state().fs.clone(),
                 pending_serialization: Task::ready(None),
-                //                 context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
+                context_menu: None,
                 channel_name_editor,
                 filter_editor,
                 entries: Vec::default(),
@@ -600,16 +579,16 @@ impl CollabPanel {
                 selection: None,
                 channel_store: ChannelStore::global(cx),
                 user_store: workspace.user_store().clone(),
-                //                 project: workspace.project().clone(),
+                project: workspace.project().clone(),
                 subscriptions: Vec::default(),
                 match_candidates: Vec::default(),
+                scroll_handle: ScrollHandle::new(),
                 collapsed_sections: vec![Section::Offline],
                 collapsed_channels: Vec::default(),
                 workspace: workspace.weak_handle(),
                 client: workspace.app_state().client.clone(),
                 //                 context_menu_on_selected: true,
                 drag_target_channel: ChannelDragTarget::None,
-                //                 list_state,
             };
 
             this.update_entries(false, cx);
@@ -725,9 +704,9 @@ impl CollabPanel {
         let query = self.filter_editor.read(cx).text(cx);
         let executor = cx.background_executor().clone();
 
-        // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
-        let _old_entries = mem::take(&mut self.entries);
-        //         let mut scroll_to_top = false;
+        let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
+        let old_entries = mem::take(&mut self.entries);
+        let scroll_to_top = false;
 
         //         if let Some(room) = ActiveCall::global(cx).read(cx).room() {
         //             self.entries.push(ListEntry::Header(Section::ActiveCall));
@@ -1094,71 +1073,62 @@ impl CollabPanel {
             self.entries.push(ListEntry::ContactPlaceholder);
         }
 
-        //         if select_same_item {
-        //             if let Some(prev_selected_entry) = prev_selected_entry {
-        //                 self.selection.take();
-        //                 for (ix, entry) in self.entries.iter().enumerate() {
-        //                     if *entry == prev_selected_entry {
-        //                         self.selection = Some(ix);
-        //                         break;
-        //                     }
-        //                 }
-        //             }
-        //         } else {
-        //             self.selection = self.selection.and_then(|prev_selection| {
-        //                 if self.entries.is_empty() {
-        //                     None
-        //                 } else {
-        //                     Some(prev_selection.min(self.entries.len() - 1))
-        //                 }
-        //             });
-        //         }
-
-        //         let old_scroll_top = self.list_state.logical_scroll_top();
-
-        //         self.list_state.reset(self.entries.len());
+        if select_same_item {
+            if let Some(prev_selected_entry) = prev_selected_entry {
+                self.selection.take();
+                for (ix, entry) in self.entries.iter().enumerate() {
+                    if *entry == prev_selected_entry {
+                        self.selection = Some(ix);
+                        self.scroll_handle.scroll_to_item(ix);
+                        break;
+                    }
+                }
+            }
+        } else {
+            self.selection = self.selection.and_then(|prev_selection| {
+                if self.entries.is_empty() {
+                    None
+                } else {
+                    let ix = prev_selection.min(self.entries.len() - 1);
+                    self.scroll_handle.scroll_to_item(ix);
+                    Some(ix)
+                }
+            });
+        }
 
-        //         if scroll_to_top {
-        //             self.list_state.scroll_to(ListOffset::default());
-        //         } else {
-        //             // Attempt to maintain the same scroll position.
-        //             if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
-        //                 let new_scroll_top = self
-        //                     .entries
-        //                     .iter()
-        //                     .position(|entry| entry == old_top_entry)
-        //                     .map(|item_ix| ListOffset {
-        //                         item_ix,
-        //                         offset_in_item: old_scroll_top.offset_in_item,
-        //                     })
-        //                     .or_else(|| {
-        //                         let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
-        //                         let item_ix = self
-        //                             .entries
-        //                             .iter()
-        //                             .position(|entry| entry == entry_after_old_top)?;
-        //                         Some(ListOffset {
-        //                             item_ix,
-        //                             offset_in_item: 0.,
-        //                         })
-        //                     })
-        //                     .or_else(|| {
-        //                         let entry_before_old_top =
-        //                             old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
-        //                         let item_ix = self
-        //                             .entries
-        //                             .iter()
-        //                             .position(|entry| entry == entry_before_old_top)?;
-        //                         Some(ListOffset {
-        //                             item_ix,
-        //                             offset_in_item: 0.,
-        //                         })
-        //                     });
+        if scroll_to_top {
+            self.scroll_handle.scroll_to_item(0)
+        } else {
+            let (old_index, old_offset) = self.scroll_handle.logical_scroll_top();
+            // Attempt to maintain the same scroll position.
+            if let Some(old_top_entry) = old_entries.get(old_index) {
+                let (new_index, new_offset) = self
+                    .entries
+                    .iter()
+                    .position(|entry| entry == old_top_entry)
+                    .map(|item_ix| (item_ix, old_offset))
+                    .or_else(|| {
+                        let entry_after_old_top = old_entries.get(old_index + 1)?;
+                        let item_ix = self
+                            .entries
+                            .iter()
+                            .position(|entry| entry == entry_after_old_top)?;
+                        Some((item_ix, px(0.)))
+                    })
+                    .or_else(|| {
+                        let entry_before_old_top = old_entries.get(old_index.saturating_sub(1))?;
+                        let item_ix = self
+                            .entries
+                            .iter()
+                            .position(|entry| entry == entry_before_old_top)?;
+                        Some((item_ix, px(0.)))
+                    })
+                    .unwrap_or_else(|| (old_index, old_offset));
 
-        //                 self.list_state
-        //                     .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
-        //             }
-        //         }
+                self.scroll_handle
+                    .set_logical_scroll_top(new_index, new_offset);
+            }
+        }
 
         cx.notify();
     }
@@ -1685,280 +1655,238 @@ impl CollabPanel {
     //             .into_any()
     //     }
 
-    //     fn has_subchannels(&self, ix: usize) -> bool {
-    //         self.entries.get(ix).map_or(false, |entry| {
-    //             if let ListEntry::Channel { has_children, .. } = entry {
-    //                 *has_children
-    //             } else {
-    //                 false
-    //             }
-    //         })
-    //     }
-
-    //     fn deploy_channel_context_menu(
-    //         &mut self,
-    //         position: Option<Vector2F>,
-    //         channel: &Channel,
-    //         ix: usize,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.context_menu_on_selected = position.is_none();
-
-    //         let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
-    //             self.channel_store
-    //                 .read(cx)
-    //                 .channel_for_id(clipboard.channel_id)
-    //                 .map(|channel| channel.name.clone())
-    //         });
+    fn has_subchannels(&self, ix: usize) -> bool {
+        self.entries.get(ix).map_or(false, |entry| {
+            if let ListEntry::Channel { has_children, .. } = entry {
+                *has_children
+            } else {
+                false
+            }
+        })
+    }
 
-    //         self.context_menu.update(cx, |context_menu, cx| {
-    //             context_menu.set_position_mode(if self.context_menu_on_selected {
-    //                 OverlayPositionMode::Local
-    //             } else {
-    //                 OverlayPositionMode::Window
-    //             });
+    fn deploy_channel_context_menu(
+        &mut self,
+        position: Point<Pixels>,
+        channel_id: ChannelId,
+        ix: usize,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
+            self.channel_store
+                .read(cx)
+                .channel_for_id(clipboard.channel_id)
+                .map(|channel| channel.name.clone())
+        });
+        let this = cx.view().clone();
 
-    //             let mut items = Vec::new();
+        let context_menu = ContextMenu::build(cx, |mut context_menu, cx| {
+            if self.has_subchannels(ix) {
+                let expand_action_name = if self.is_channel_collapsed(channel_id) {
+                    "Expand Subchannels"
+                } else {
+                    "Collapse Subchannels"
+                };
+                context_menu = context_menu.entry(
+                    expand_action_name,
+                    cx.handler_for(&this, move |this, cx| {
+                        this.toggle_channel_collapsed(channel_id, cx)
+                    }),
+                );
+            }
 
-    //             let select_action_name = if self.selection == Some(ix) {
-    //                 "Unselect"
-    //             } else {
-    //                 "Select"
-    //             };
+            context_menu = context_menu
+                .entry(
+                    "Open Notes",
+                    cx.handler_for(&this, move |this, cx| {
+                        this.open_channel_notes(channel_id, cx)
+                    }),
+                )
+                .entry(
+                    "Open Chat",
+                    cx.handler_for(&this, move |this, cx| {
+                        this.join_channel_chat(channel_id, cx)
+                    }),
+                )
+                .entry(
+                    "Copy Channel Link",
+                    cx.handler_for(&this, move |this, cx| {
+                        this.copy_channel_link(channel_id, cx)
+                    }),
+                );
 
-    //             items.push(ContextMenuItem::action(
-    //                 select_action_name,
-    //                 ToggleSelectedIx { ix },
-    //             ));
+            if self.channel_store.read(cx).is_channel_admin(channel_id) {
+                context_menu = context_menu
+                    .separator()
+                    .entry(
+                        "New Subchannel",
+                        cx.handler_for(&this, move |this, cx| this.new_subchannel(channel_id, cx)),
+                    )
+                    .entry(
+                        "Rename",
+                        cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
+                    )
+                    .entry(
+                        "Move this channel",
+                        cx.handler_for(&this, move |this, cx| {
+                            this.start_move_channel(channel_id, cx)
+                        }),
+                    );
 
-    //             if self.has_subchannels(ix) {
-    //                 let expand_action_name = if self.is_channel_collapsed(channel.id) {
-    //                     "Expand Subchannels"
-    //                 } else {
-    //                     "Collapse Subchannels"
-    //                 };
-    //                 items.push(ContextMenuItem::action(
-    //                     expand_action_name,
-    //                     ToggleCollapse {
-    //                         location: channel.id,
-    //                     },
-    //                 ));
-    //             }
+                if let Some(channel_name) = clipboard_channel_name {
+                    context_menu = context_menu.separator().entry(
+                        format!("Move '#{}' here", channel_name),
+                        cx.handler_for(&this, move |this, cx| {
+                            this.move_channel_on_clipboard(channel_id, cx)
+                        }),
+                    );
+                }
 
-    //             items.push(ContextMenuItem::action(
-    //                 "Open Notes",
-    //                 OpenChannelNotes {
-    //                     channel_id: channel.id,
-    //                 },
-    //             ));
-
-    //             items.push(ContextMenuItem::action(
-    //                 "Open Chat",
-    //                 JoinChannelChat {
-    //                     channel_id: channel.id,
-    //                 },
-    //             ));
-
-    //             items.push(ContextMenuItem::action(
-    //                 "Copy Channel Link",
-    //                 CopyChannelLink {
-    //                     channel_id: channel.id,
-    //                 },
-    //             ));
-
-    //             if self.channel_store.read(cx).is_channel_admin(channel.id) {
-    //                 items.extend([
-    //                     ContextMenuItem::Separator,
-    //                     ContextMenuItem::action(
-    //                         "New Subchannel",
-    //                         NewChannel {
-    //                             location: channel.id,
-    //                         },
-    //                     ),
-    //                     ContextMenuItem::action(
-    //                         "Rename",
-    //                         RenameChannel {
-    //                             channel_id: channel.id,
-    //                         },
-    //                     ),
-    //                     ContextMenuItem::action(
-    //                         "Move this channel",
-    //                         StartMoveChannelFor {
-    //                             channel_id: channel.id,
-    //                         },
-    //                     ),
-    //                 ]);
-
-    //                 if let Some(channel_name) = clipboard_channel_name {
-    //                     items.push(ContextMenuItem::Separator);
-    //                     items.push(ContextMenuItem::action(
-    //                         format!("Move '#{}' here", channel_name),
-    //                         MoveChannel { to: channel.id },
-    //                     ));
-    //                 }
+                context_menu = context_menu
+                    .separator()
+                    .entry(
+                        "Invite Members",
+                        cx.handler_for(&this, move |this, cx| this.invite_members(channel_id, cx)),
+                    )
+                    .entry(
+                        "Manage Members",
+                        cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
+                    )
+                    .entry(
+                        "Delete",
+                        cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)),
+                    );
+            }
 
-    //                 items.extend([
-    //                     ContextMenuItem::Separator,
-    //                     ContextMenuItem::action(
-    //                         "Invite Members",
-    //                         InviteMembers {
-    //                             channel_id: channel.id,
-    //                         },
-    //                     ),
-    //                     ContextMenuItem::action(
-    //                         "Manage Members",
-    //                         ManageMembers {
-    //                             channel_id: channel.id,
-    //                         },
-    //                     ),
-    //                     ContextMenuItem::Separator,
-    //                     ContextMenuItem::action(
-    //                         "Delete",
-    //                         RemoveChannel {
-    //                             channel_id: channel.id,
-    //                         },
-    //                     ),
-    //                 ]);
-    //             }
+            context_menu
+        });
 
-    //             context_menu.show(
-    //                 position.unwrap_or_default(),
-    //                 if self.context_menu_on_selected {
-    //                     gpui::elements::AnchorCorner::TopRight
-    //                 } else {
-    //                     gpui::elements::AnchorCorner::BottomLeft
-    //                 },
-    //                 items,
-    //                 cx,
-    //             );
-    //         });
+        cx.focus_view(&context_menu);
+        let subscription =
+            cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
+                if this.context_menu.as_ref().is_some_and(|context_menu| {
+                    context_menu.0.focus_handle(cx).contains_focused(cx)
+                }) {
+                    cx.focus_self();
+                }
+                this.context_menu.take();
+                cx.notify();
+            });
+        self.context_menu = Some((context_menu, position, subscription));
 
-    //         cx.notify();
-    //     }
+        cx.notify();
+    }
 
-    //     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-    //         if self.take_editing_state(cx) {
-    //             cx.focus(&self.filter_editor);
-    //         } else {
-    //             self.filter_editor.update(cx, |editor, cx| {
-    //                 if editor.buffer().read(cx).len(cx) > 0 {
-    //                     editor.set_text("", cx);
-    //                 }
-    //             });
-    //         }
+    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+        if self.take_editing_state(cx) {
+            cx.focus_view(&self.filter_editor);
+        } else {
+            self.filter_editor.update(cx, |editor, cx| {
+                if editor.buffer().read(cx).len(cx) > 0 {
+                    editor.set_text("", cx);
+                }
+            });
+        }
 
-    //         self.update_entries(false, cx);
-    //     }
+        self.update_entries(false, cx);
+    }
 
-    //     fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-    //         let ix = self.selection.map_or(0, |ix| ix + 1);
-    //         if ix < self.entries.len() {
-    //             self.selection = Some(ix);
-    //         }
+    fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
+        let ix = self.selection.map_or(0, |ix| ix + 1);
+        if ix < self.entries.len() {
+            self.selection = Some(ix);
+        }
 
-    //         self.list_state.reset(self.entries.len());
-    //         if let Some(ix) = self.selection {
-    //             self.list_state.scroll_to(ListOffset {
-    //                 item_ix: ix,
-    //                 offset_in_item: 0.,
-    //             });
-    //         }
-    //         cx.notify();
-    //     }
+        if let Some(ix) = self.selection {
+            self.scroll_handle.scroll_to_item(ix)
+        }
+        cx.notify();
+    }
 
-    //     fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-    //         let ix = self.selection.take().unwrap_or(0);
-    //         if ix > 0 {
-    //             self.selection = Some(ix - 1);
-    //         }
+    fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
+        let ix = self.selection.take().unwrap_or(0);
+        if ix > 0 {
+            self.selection = Some(ix - 1);
+        }
 
-    //         self.list_state.reset(self.entries.len());
-    //         if let Some(ix) = self.selection {
-    //             self.list_state.scroll_to(ListOffset {
-    //                 item_ix: ix,
-    //                 offset_in_item: 0.,
-    //             });
-    //         }
-    //         cx.notify();
-    //     }
+        if let Some(ix) = self.selection {
+            self.scroll_handle.scroll_to_item(ix)
+        }
+        cx.notify();
+    }
 
     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
         if self.confirm_channel_edit(cx) {
             return;
         }
 
-        // if let Some(selection) = self.selection {
-        //     if let Some(entry) = self.entries.get(selection) {
-        //         match entry {
-        //             ListEntry::Header(section) => match section {
-        //                 Section::ActiveCall => Self::leave_call(cx),
-        //                 Section::Channels => self.new_root_channel(cx),
-        //                 Section::Contacts => self.toggle_contact_finder(cx),
-        //                 Section::ContactRequests
-        //                 | Section::Online
-        //                 | Section::Offline
-        //                 | Section::ChannelInvites => {
-        //                     self.toggle_section_expanded(*section, cx);
-        //                 }
-        //             },
-        //             ListEntry::Contact { contact, calling } => {
-        //                 if contact.online && !contact.busy && !calling {
-        //                     self.call(contact.user.id, Some(self.project.clone()), cx);
-        //                 }
-        //             }
-        //             ListEntry::ParticipantProject {
-        //                 project_id,
-        //                 host_user_id,
-        //                 ..
-        //             } => {
-        //                 if let Some(workspace) = self.workspace.upgrade(cx) {
-        //                     let app_state = workspace.read(cx).app_state().clone();
-        //                     workspace::join_remote_project(
-        //                         *project_id,
-        //                         *host_user_id,
-        //                         app_state,
-        //                         cx,
-        //                     )
-        //                     .detach_and_log_err(cx);
-        //                 }
-        //             }
-        //             ListEntry::ParticipantScreen { peer_id, .. } => {
-        //                 let Some(peer_id) = peer_id else {
-        //                     return;
-        //                 };
-        //                 if let Some(workspace) = self.workspace.upgrade(cx) {
-        //                     workspace.update(cx, |workspace, cx| {
-        //                         workspace.open_shared_screen(*peer_id, cx)
-        //                     });
-        //                 }
-        //             }
-        //             ListEntry::Channel { channel, .. } => {
-        //                 let is_active = maybe!({
-        //                     let call_channel = ActiveCall::global(cx)
-        //                         .read(cx)
-        //                         .room()?
-        //                         .read(cx)
-        //                         .channel_id()?;
-
-        //                     Some(call_channel == channel.id)
-        //                 })
-        //                 .unwrap_or(false);
-        //                 if is_active {
-        //                     self.open_channel_notes(
-        //                         &OpenChannelNotes {
-        //                             channel_id: channel.id,
-        //                         },
-        //                         cx,
-        //                     )
-        //                 } else {
-        //                     self.join_channel(channel.id, cx)
-        //                 }
-        //             }
-        //             ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
-        //             _ => {}
-        //         }
-        //     }
-        // }
+        if let Some(selection) = self.selection {
+            if let Some(entry) = self.entries.get(selection) {
+                match entry {
+                    ListEntry::Header(section) => match section {
+                        Section::ActiveCall => Self::leave_call(cx),
+                        Section::Channels => self.new_root_channel(cx),
+                        Section::Contacts => self.toggle_contact_finder(cx),
+                        Section::ContactRequests
+                        | Section::Online
+                        | Section::Offline
+                        | Section::ChannelInvites => {
+                            self.toggle_section_expanded(*section, cx);
+                        }
+                    },
+                    ListEntry::Contact { contact, calling } => {
+                        if contact.online && !contact.busy && !calling {
+                            self.call(contact.user.id, cx);
+                        }
+                    }
+                    // ListEntry::ParticipantProject {
+                    //     project_id,
+                    //     host_user_id,
+                    //     ..
+                    // } => {
+                    //     if let Some(workspace) = self.workspace.upgrade(cx) {
+                    //         let app_state = workspace.read(cx).app_state().clone();
+                    //         workspace::join_remote_project(
+                    //             *project_id,
+                    //             *host_user_id,
+                    //             app_state,
+                    //             cx,
+                    //         )
+                    //         .detach_and_log_err(cx);
+                    //     }
+                    // }
+                    // ListEntry::ParticipantScreen { peer_id, .. } => {
+                    //     let Some(peer_id) = peer_id else {
+                    //         return;
+                    //     };
+                    //     if let Some(workspace) = self.workspace.upgrade(cx) {
+                    //         workspace.update(cx, |workspace, cx| {
+                    //             workspace.open_shared_screen(*peer_id, cx)
+                    //         });
+                    //     }
+                    // }
+                    ListEntry::Channel { channel, .. } => {
+                        let is_active = maybe!({
+                            let call_channel = ActiveCall::global(cx)
+                                .read(cx)
+                                .room()?
+                                .read(cx)
+                                .channel_id()?;
+
+                            Some(call_channel == channel.id)
+                        })
+                        .unwrap_or(false);
+                        if is_active {
+                            self.open_channel_notes(channel.id, cx)
+                        } else {
+                            self.join_channel(channel.id, cx)
+                        }
+                    }
+                    ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
+                    _ => {}
+                }
+            }
+        }
     }
 
     fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
@@ -2025,33 +1953,33 @@ impl CollabPanel {
         self.update_entries(false, cx);
     }
 
-    //     fn collapse_selected_channel(
-    //         &mut self,
-    //         _: &CollapseSelectedChannel,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
-    //             return;
-    //         };
+    fn collapse_selected_channel(
+        &mut self,
+        _: &CollapseSelectedChannel,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
+            return;
+        };
 
-    //         if self.is_channel_collapsed(channel_id) {
-    //             return;
-    //         }
+        if self.is_channel_collapsed(channel_id) {
+            return;
+        }
 
-    //         self.toggle_channel_collapsed(channel_id, cx);
-    //     }
+        self.toggle_channel_collapsed(channel_id, cx);
+    }
 
-    //     fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
-    //         let Some(id) = self.selected_channel().map(|channel| channel.id) else {
-    //             return;
-    //         };
+    fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
+        let Some(id) = self.selected_channel().map(|channel| channel.id) else {
+            return;
+        };
 
-    //         if !self.is_channel_collapsed(id) {
-    //             return;
-    //         }
+        if !self.is_channel_collapsed(id) {
+            return;
+        }
 
-    //         self.toggle_channel_collapsed(id, cx)
-    //     }
+        self.toggle_channel_collapsed(id, cx)
+    }
 
     //     fn toggle_channel_collapsed_action(
     //         &mut self,
@@ -2080,11 +2008,11 @@ impl CollabPanel {
         self.collapsed_channels.binary_search(&channel_id).is_ok()
     }
 
-    //     fn leave_call(cx: &mut ViewContext<Self>) {
-    //         ActiveCall::global(cx)
-    //             .update(cx, |call, cx| call.hang_up(cx))
-    //             .detach_and_log_err(cx);
-    //     }
+    fn leave_call(cx: &mut ViewContext<Self>) {
+        ActiveCall::global(cx)
+            .update(cx, |call, cx| call.hang_up(cx))
+            .detach_and_log_err(cx);
+    }
 
     fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(workspace) = self.workspace.upgrade() {
@@ -2116,78 +2044,108 @@ impl CollabPanel {
         });
     }
 
-    //     fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
-    //         self.collapsed_channels
-    //             .retain(|channel| *channel != action.location);
-    //         self.channel_editing_state = Some(ChannelEditingState::Create {
-    //             location: Some(action.location.to_owned()),
-    //             pending_name: None,
-    //         });
-    //         self.update_entries(false, cx);
-    //         self.select_channel_editor();
-    //         cx.focus(self.channel_name_editor.as_any());
-    //         cx.notify();
-    //     }
+    fn new_subchannel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        self.collapsed_channels
+            .retain(|channel| *channel != channel_id);
+        self.channel_editing_state = Some(ChannelEditingState::Create {
+            location: Some(channel_id),
+            pending_name: None,
+        });
+        self.update_entries(false, cx);
+        self.select_channel_editor();
+        cx.focus_view(&self.channel_name_editor);
+        cx.notify();
+    }
 
-    //     fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
-    //         self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
-    //     }
+    fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        todo!();
+        // self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
+    }
 
-    //     fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
-    //         self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
-    //     }
+    fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        todo!();
+        // self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
+    }
 
-    //     fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
-    //         if let Some(channel) = self.selected_channel() {
-    //             self.remove_channel(channel.id, cx)
-    //         }
-    //     }
+    fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
+        if let Some(channel) = self.selected_channel() {
+            self.remove_channel(channel.id, cx)
+        }
+    }
 
-    //     fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
-    //         if let Some(channel) = self.selected_channel() {
-    //             self.rename_channel(
-    //                 &RenameChannel {
-    //                     channel_id: channel.id,
-    //                 },
-    //                 cx,
-    //             );
-    //         }
-    //     }
+    fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+        if let Some(channel) = self.selected_channel() {
+            self.rename_channel(channel.id, cx);
+        }
+    }
 
-    //     fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
-    //         let channel_store = self.channel_store.read(cx);
-    //         if !channel_store.is_channel_admin(action.channel_id) {
-    //             return;
-    //         }
-    //         if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
-    //             self.channel_editing_state = Some(ChannelEditingState::Rename {
-    //                 location: action.channel_id.to_owned(),
-    //                 pending_name: None,
-    //             });
-    //             self.channel_name_editor.update(cx, |editor, cx| {
-    //                 editor.set_text(channel.name.clone(), cx);
-    //                 editor.select_all(&Default::default(), cx);
-    //             });
-    //             cx.focus(self.channel_name_editor.as_any());
-    //             self.update_entries(false, cx);
-    //             self.select_channel_editor();
-    //         }
-    //     }
+    fn rename_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        let channel_store = self.channel_store.read(cx);
+        if !channel_store.is_channel_admin(channel_id) {
+            return;
+        }
+        if let Some(channel) = channel_store.channel_for_id(channel_id).cloned() {
+            self.channel_editing_state = Some(ChannelEditingState::Rename {
+                location: channel_id,
+                pending_name: None,
+            });
+            self.channel_name_editor.update(cx, |editor, cx| {
+                editor.set_text(channel.name.clone(), cx);
+                editor.select_all(&Default::default(), cx);
+            });
+            cx.focus_view(&self.channel_name_editor);
+            self.update_entries(false, cx);
+            self.select_channel_editor();
+        }
+    }
+
+    fn start_move_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        self.channel_clipboard = Some(ChannelMoveClipboard { channel_id });
+    }
+
+    fn start_move_selected_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        if let Some(channel) = self.selected_channel() {
+            self.channel_clipboard = Some(ChannelMoveClipboard {
+                channel_id: channel.id,
+            })
+        }
+    }
+
+    fn move_channel_on_clipboard(
+        &mut self,
+        to_channel_id: ChannelId,
+        cx: &mut ViewContext<CollabPanel>,
+    ) {
+        if let Some(clipboard) = self.channel_clipboard.take() {
+            self.channel_store.update(cx, |channel_store, cx| {
+                channel_store
+                    .move_channel(clipboard.channel_id, Some(to_channel_id), cx)
+                    .detach_and_log_err(cx)
+            })
+        }
+    }
 
-    fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
+    fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
         if let Some(workspace) = self.workspace.upgrade() {
             todo!();
             // ChannelView::open(action.channel_id, workspace, cx).detach();
         }
     }
 
-    //     fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
-    //         let Some(channel) = self.selected_channel() else {
-    //             return;
-    //         };
+    fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
+        let Some(channel) = self.selected_channel() else {
+            return;
+        };
+        let Some(bounds) = self
+            .selection
+            .and_then(|ix| self.scroll_handle.bounds_for_item(ix))
+        else {
+            return;
+        };
 
-    //         self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
-    //     }
+        self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx);
+        cx.stop_propagation();
+    }
 
     fn selected_channel(&self) -> Option<&Arc<Channel>> {
         self.selection

crates/collab_ui2/src/collab_panel/contact_finder.rs πŸ”—

@@ -185,7 +185,7 @@ impl PickerDelegate for ContactFinderDelegate {
             div()
                 .flex_1()
                 .justify_between()
-                .children(user.avatar.clone().map(|avatar| img().data(avatar)))
+                .children(user.avatar.clone().map(|avatar| img(avatar)))
                 .child(Label::new(user.github_login.clone()))
                 .children(icon_path.map(|icon_path| svg().path(icon_path))),
         )

crates/collab_ui2/src/collab_titlebar_item.rs πŸ”—

@@ -35,19 +35,19 @@ use gpui::{
     ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
     ViewContext, VisualContext, WeakView, WindowBounds,
 };
-use project::Project;
+use project::{Project, RepositoryEntry};
 use theme::ActiveTheme;
 use ui::{
-    h_stack, Avatar, Button, ButtonCommon, ButtonLike, ButtonVariant, Clickable, Color, IconButton,
-    IconElement, IconSize, KeyBinding, Tooltip,
+    h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
+    IconButton, IconElement, KeyBinding, Tooltip,
 };
 use util::ResultExt;
 use workspace::{notifications::NotifyResultExt, Workspace};
 
 use crate::face_pile::FacePile;
 
-// const MAX_PROJECT_NAME_LENGTH: usize = 40;
-// const MAX_BRANCH_NAME_LENGTH: usize = 40;
+const MAX_PROJECT_NAME_LENGTH: usize = 40;
+const MAX_BRANCH_NAME_LENGTH: usize = 40;
 
 // actions!(
 //     collab,
@@ -103,17 +103,18 @@ impl Render for CollabTitlebarItem {
             .update(cx, |this, cx| this.call_state().remote_participants(cx))
             .log_err()
             .flatten();
-        let mic_icon = if self
+        let is_muted = self
             .workspace
             .update(cx, |this, cx| this.call_state().is_muted(cx))
             .log_err()
             .flatten()
-            .unwrap_or_default()
-        {
-            ui::Icon::MicMute
-        } else {
-            ui::Icon::Mic
-        };
+            .unwrap_or_default();
+        let is_deafened = self
+            .workspace
+            .update(cx, |this, cx| this.call_state().is_deafened(cx))
+            .log_err()
+            .flatten()
+            .unwrap_or_default();
         let speakers_icon = if self
             .workspace
             .update(cx, |this, cx| this.call_state().is_deafened(cx))
@@ -149,53 +150,11 @@ impl Render for CollabTitlebarItem {
             .child(
                 h_stack()
                     .gap_1()
-                    // TODO - Add player menu
-                    .child(
-                        div()
-                            .border()
-                            .border_color(gpui::red())
-                            .id("project_owner_indicator")
-                            .child(
-                                Button::new("player")
-                                    .variant(ButtonVariant::Ghost)
-                                    .color(Some(Color::Player(0))),
-                            )
-                            .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
-                    )
-                    // TODO - Add project menu
-                    .child(
-                        div()
-                            .border()
-                            .border_color(gpui::red())
-                            .id("titlebar_project_menu_button")
-                            .child(Button::new("project_name").variant(ButtonVariant::Ghost))
-                            .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
-                    )
-                    // TODO - Add git menu
-                    .child(
-                        div()
-                            .border()
-                            .border_color(gpui::red())
-                            .id("titlebar_git_menu_button")
-                            .child(
-                                Button::new("branch_name")
-                                    .variant(ButtonVariant::Ghost)
-                                    .color(Some(Color::Muted)),
-                            )
-                            .tooltip(move |cx| {
-                                cx.build_view(|_| {
-                                    Tooltip::new("Recent Branches")
-                                        .key_binding(KeyBinding::new(gpui::KeyBinding::new(
-                                            "cmd-b",
-                                            // todo!() Replace with real action.
-                                            gpui::NoAction,
-                                            None,
-                                        )))
-                                        .meta("Only local branches shown")
-                                })
-                                .into()
-                            }),
-                    ),
+                    .when(is_in_room, |this| {
+                        this.children(self.render_project_owner(cx))
+                    })
+                    .child(self.render_project_name(cx))
+                    .children(self.render_project_branch(cx)),
             )
             .when_some(
                 users.zip(current_user.clone()),
@@ -236,62 +195,129 @@ impl Render for CollabTitlebarItem {
             .when(is_in_room, |this| {
                 this.child(
                     h_stack()
+                        .gap_1()
                         .child(
                             h_stack()
-                                .child(Button::new(if is_shared { "Unshare" } else { "Share" }))
-                                .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({
-                                    let workspace = workspace.clone();
-                                    move |_, cx| {
-                                        workspace
-                                            .update(cx, |this, cx| {
-                                                this.call_state().hang_up(cx).detach();
-                                            })
-                                            .log_err();
-                                    }
-                                })),
+                                .gap_1()
+                                .child(
+                                    Button::new(
+                                        "toggle_sharing",
+                                        if is_shared { "Unshare" } else { "Share" },
+                                    )
+                                    .style(ButtonStyle::Subtle),
+                                )
+                                .child(
+                                    IconButton::new("leave-call", ui::Icon::Exit)
+                                        .style(ButtonStyle::Subtle)
+                                        .on_click({
+                                            let workspace = workspace.clone();
+                                            move |_, cx| {
+                                                workspace
+                                                    .update(cx, |this, cx| {
+                                                        this.call_state().hang_up(cx).detach();
+                                                    })
+                                                    .log_err();
+                                            }
+                                        }),
+                                ),
                         )
                         .child(
                             h_stack()
-                                .child(IconButton::new("mute-microphone", mic_icon).on_click({
-                                    let workspace = workspace.clone();
-                                    move |_, cx| {
-                                        workspace
-                                            .update(cx, |this, cx| {
-                                                this.call_state().toggle_mute(cx);
-                                            })
-                                            .log_err();
-                                    }
-                                }))
-                                .child(IconButton::new("mute-sound", speakers_icon).on_click({
-                                    let workspace = workspace.clone();
-                                    move |_, cx| {
-                                        workspace
-                                            .update(cx, |this, cx| {
-                                                this.call_state().toggle_deafen(cx);
-                                            })
-                                            .log_err();
-                                    }
-                                }))
-                                .child(IconButton::new("screen-share", ui::Icon::Screen).on_click(
-                                    move |_, cx| {
-                                        workspace
-                                            .update(cx, |this, cx| {
-                                                this.call_state().toggle_screen_share(cx);
-                                            })
-                                            .log_err();
-                                    },
-                                ))
+                                .gap_1()
+                                .child(
+                                    IconButton::new(
+                                        "mute-microphone",
+                                        if is_muted {
+                                            ui::Icon::MicMute
+                                        } else {
+                                            ui::Icon::Mic
+                                        },
+                                    )
+                                    .style(ButtonStyle::Subtle)
+                                    .selected(is_muted)
+                                    .on_click({
+                                        let workspace = workspace.clone();
+                                        move |_, cx| {
+                                            workspace
+                                                .update(cx, |this, cx| {
+                                                    this.call_state().toggle_mute(cx);
+                                                })
+                                                .log_err();
+                                        }
+                                    }),
+                                )
+                                .child(
+                                    IconButton::new("mute-sound", speakers_icon)
+                                        .style(ButtonStyle::Subtle)
+                                        .selected(is_deafened.clone())
+                                        .tooltip(move |cx| {
+                                            Tooltip::with_meta(
+                                                "Deafen Audio",
+                                                None,
+                                                "Mic will be muted",
+                                                cx,
+                                            )
+                                        })
+                                        .on_click({
+                                            let workspace = workspace.clone();
+                                            move |_, cx| {
+                                                workspace
+                                                    .update(cx, |this, cx| {
+                                                        this.call_state().toggle_deafen(cx);
+                                                    })
+                                                    .log_err();
+                                            }
+                                        }),
+                                )
+                                .child(
+                                    IconButton::new("screen-share", ui::Icon::Screen)
+                                        .style(ButtonStyle::Subtle)
+                                        .on_click(move |_, cx| {
+                                            workspace
+                                                .update(cx, |this, cx| {
+                                                    this.call_state().toggle_screen_share(cx);
+                                                })
+                                                .log_err();
+                                        }),
+                                )
                                 .pl_2(),
                         ),
                 )
             })
-            .map(|this| {
+            .child(h_stack().px_1p5().map(|this| {
                 if let Some(user) = current_user {
                     this.when_some(user.avatar.clone(), |this, avatar| {
-                        this.child(ui::Avatar::data(avatar))
+                        // TODO: Finish implementing user menu popover
+                        //
+                        this.child(
+                            popover_menu("user-menu")
+                                .menu(|cx| ContextMenu::build(cx, |menu, _| menu.header("ADADA")))
+                                .trigger(
+                                    ButtonLike::new("user-menu")
+                                        .child(
+                                            h_stack().gap_0p5().child(Avatar::data(avatar)).child(
+                                                IconElement::new(Icon::ChevronDown)
+                                                    .color(Color::Muted),
+                                            ),
+                                        )
+                                        .style(ButtonStyle::Subtle)
+                                        .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
+                                )
+                                .anchor(gpui::AnchorCorner::TopRight),
+                        )
+                        // this.child(
+                        //     ButtonLike::new("user-menu")
+                        //         .child(
+                        //             h_stack().gap_0p5().child(Avatar::data(avatar)).child(
+                        //                 IconElement::new(Icon::ChevronDown).color(Color::Muted),
+                        //             ),
+                        //         )
+                        //         .style(ButtonStyle::Subtle)
+                        //         .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
+                        // )
                     })
                 } else {
-                    this.child(Button::new("Sign in").on_click(move |_, cx| {
+                    this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
                         let client = client.clone();
                         cx.spawn(move |mut cx| async move {
                             client
@@ -301,29 +327,8 @@ impl Render for CollabTitlebarItem {
                         })
                         .detach();
                     }))
-                    // Temporary, will be removed when the last part of button2 is merged
-                    .child(
-                        div().border().border_color(gpui::blue()).child(
-                            ButtonLike::new("test-button")
-                                .children([
-                                    Avatar::uri(
-                                        "https://avatars.githubusercontent.com/u/1714999?v=4",
-                                    )
-                                    .into_element()
-                                    .into_any(),
-                                    IconElement::new(ui::Icon::ChevronDown)
-                                        .size(IconSize::Small)
-                                        .into_element()
-                                        .into_any(),
-                                ])
-                                .on_click(move |event, _cx| {
-                                    dbg!(format!("clicked: {:?}", event.down.position));
-                                })
-                                .tooltip(|cx| Tooltip::text("Test tooltip", cx)),
-                        ),
-                    )
                 }
-            })
+            }))
     }
 }
 
@@ -442,6 +447,110 @@ impl CollabTitlebarItem {
         }
     }
 
+    // resolve if you are in a room -> render_project_owner
+    // render_project_owner -> resolve if you are in a room -> Option<foo>
+
+    pub fn render_project_owner(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
+        // TODO: We can't finish implementing this until project sharing works
+        // - [ ] Show the project owner when the project is remote (maybe done)
+        // - [x] Show the project owner when the project is local
+        // - [ ] Show the project owner with a lock icon when the project is local and unshared
+
+        let remote_id = self.project.read(cx).remote_id();
+        let is_local = remote_id.is_none();
+        let is_shared = self.project.read(cx).is_shared();
+        let (user_name, participant_index) = {
+            if let Some(host) = self.project.read(cx).host() {
+                debug_assert!(!is_local);
+                let (Some(host_user), Some(participant_index)) = (
+                    self.user_store.read(cx).get_cached_user(host.user_id),
+                    self.user_store
+                        .read(cx)
+                        .participant_indices()
+                        .get(&host.user_id),
+                ) else {
+                    return None;
+                };
+                (host_user.github_login.clone(), participant_index.0)
+            } else {
+                debug_assert!(is_local);
+                let name = self
+                    .user_store
+                    .read(cx)
+                    .current_user()
+                    .map(|user| user.github_login.clone())?;
+                (name, 0)
+            }
+        };
+        Some(
+            div().border().border_color(gpui::red()).child(
+                Button::new(
+                    "project_owner_trigger",
+                    format!("{user_name} ({})", !is_shared),
+                )
+                .color(Color::Player(participant_index))
+                .style(ButtonStyle::Subtle)
+                .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
+            ),
+        )
+    }
+
+    pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
+        let name = {
+            let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
+                let worktree = worktree.read(cx);
+                worktree.root_name()
+            });
+
+            names.next().unwrap_or("")
+        };
+
+        let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
+
+        div().border().border_color(gpui::red()).child(
+            Button::new("project_name_trigger", name)
+                .style(ButtonStyle::Subtle)
+                .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
+        )
+    }
+
+    pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
+        let entry = {
+            let mut names_and_branches =
+                self.project.read(cx).visible_worktrees(cx).map(|worktree| {
+                    let worktree = worktree.read(cx);
+                    worktree.root_git_entry()
+                });
+
+            names_and_branches.next().flatten()
+        };
+
+        let branch_name = entry
+            .as_ref()
+            .and_then(RepositoryEntry::branch)
+            .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
+
+        Some(
+            div().border().border_color(gpui::red()).child(
+                Button::new("project_branch_trigger", branch_name)
+                    .style(ButtonStyle::Subtle)
+                    .tooltip(move |cx| {
+                        cx.build_view(|_| {
+                            Tooltip::new("Recent Branches")
+                                .key_binding(KeyBinding::new(gpui::KeyBinding::new(
+                                    "cmd-b",
+                                    // todo!() Replace with real action.
+                                    gpui::NoAction,
+                                    None,
+                                )))
+                                .meta("Local branches only")
+                        })
+                        .into()
+                    }),
+            ),
+        )
+    }
+
     // fn collect_title_root_names(
     //     &self,
     //     theme: Arc<Theme>,

crates/collab_ui2/src/notifications/incoming_call_notification.rs πŸ”—

@@ -2,10 +2,11 @@ use crate::notification_window_options;
 use call::{ActiveCall, IncomingCall};
 use futures::StreamExt;
 use gpui::{
-    div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce,
-    StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle,
+    div, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce, Styled, ViewContext,
+    VisualContext as _, WindowHandle,
 };
 use std::sync::{Arc, Weak};
+use ui::prelude::*;
 use ui::{h_stack, v_stack, Avatar, Button, Label};
 use util::ResultExt;
 use workspace::AppState;
@@ -199,14 +200,24 @@ impl IncomingCallNotification {
 
     fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
         h_stack()
-            .child(Button::new("Accept").render(cx).bg(green()).on_click({
-                let state = self.state.clone();
-                move |_, cx| state.respond(true, cx)
-            }))
-            .child(Button::new("Decline").render(cx).bg(red()).on_click({
-                let state = self.state.clone();
-                move |_, cx| state.respond(false, cx)
-            }))
+            .child(
+                Button::new("accept", "Accept")
+                    .render(cx)
+                    // .bg(green())
+                    .on_click({
+                        let state = self.state.clone();
+                        move |_, cx| state.respond(true, cx)
+                    }),
+            )
+            .child(
+                Button::new("decline", "Decline")
+                    .render(cx)
+                    // .bg(red())
+                    .on_click({
+                        let state = self.state.clone();
+                        move |_, cx| state.respond(false, cx)
+                    }),
+            )
 
         // enum Accept {}
         // enum Decline {}

crates/command_palette2/src/command_palette.rs πŸ”—

@@ -11,7 +11,7 @@ use gpui::{
 };
 use picker::{Picker, PickerDelegate};
 
-use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
+use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem};
 use util::{
     channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
     ResultExt,

crates/diagnostics/src/diagnostics.rs πŸ”—

@@ -126,7 +126,7 @@ impl View for ProjectDiagnosticsEditor {
         json!({
             "project": json!({
                 "language_servers": project.language_server_statuses().collect::<Vec<_>>(),
-                "summary": project.diagnostic_summary(cx),
+                "summary": project.diagnostic_summary(false, cx),
             }),
             "summary": self.summary,
             "paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
@@ -195,7 +195,7 @@ impl ProjectDiagnosticsEditor {
         });
 
         let project = project_handle.read(cx);
-        let summary = project.diagnostic_summary(cx);
+        let summary = project.diagnostic_summary(false, cx);
         let mut this = Self {
             project: project_handle,
             summary,
@@ -241,7 +241,7 @@ impl ProjectDiagnosticsEditor {
         let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
             .project
             .read(cx)
-            .diagnostic_summaries(cx)
+            .diagnostic_summaries(false, cx)
             .fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
                 summaries.entry(server_id).or_default().insert(path);
                 summaries
@@ -320,7 +320,7 @@ impl ProjectDiagnosticsEditor {
                 .context("rechecking diagnostics for paths")?;
 
                 this.update(&mut cx, |this, cx| {
-                    this.summary = this.project.read(cx).diagnostic_summary(cx);
+                    this.summary = this.project.read(cx).diagnostic_summary(false, cx);
                     cx.emit(Event::TitleChanged);
                 })?;
                 anyhow::Ok(())

crates/diagnostics/src/items.rs πŸ”—

@@ -34,19 +34,19 @@ impl DiagnosticIndicator {
             }
             project::Event::DiskBasedDiagnosticsFinished { language_server_id }
             | project::Event::LanguageServerRemoved(language_server_id) => {
-                this.summary = project.read(cx).diagnostic_summary(cx);
+                this.summary = project.read(cx).diagnostic_summary(false, cx);
                 this.in_progress_checks.remove(language_server_id);
                 cx.notify();
             }
             project::Event::DiagnosticsUpdated { .. } => {
-                this.summary = project.read(cx).diagnostic_summary(cx);
+                this.summary = project.read(cx).diagnostic_summary(false, cx);
                 cx.notify();
             }
             _ => {}
         })
         .detach();
         Self {
-            summary: project.read(cx).diagnostic_summary(cx),
+            summary: project.read(cx).diagnostic_summary(false, cx),
             in_progress_checks: project
                 .read(cx)
                 .language_servers_running_disk_based_diagnostics()

crates/diagnostics2/src/diagnostics.rs πŸ”—

@@ -165,7 +165,7 @@ impl ProjectDiagnosticsEditor {
             });
 
         let project = project_handle.read(cx);
-        let summary = project.diagnostic_summary(cx);
+        let summary = project.diagnostic_summary(false, cx);
         let mut this = Self {
             project: project_handle,
             summary,
@@ -252,7 +252,7 @@ impl ProjectDiagnosticsEditor {
         let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
             .project
             .read(cx)
-            .diagnostic_summaries(cx)
+            .diagnostic_summaries(false, cx)
             .fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
                 summaries.entry(server_id).or_default().insert(path);
                 summaries
@@ -332,7 +332,7 @@ impl ProjectDiagnosticsEditor {
                 .context("rechecking diagnostics for paths")?;
 
                 this.update(&mut cx, |this, cx| {
-                    this.summary = this.project.read(cx).diagnostic_summary(cx);
+                    this.summary = this.project.read(cx).diagnostic_summary(false, cx);
                     cx.emit(ItemEvent::UpdateTab);
                     cx.emit(ItemEvent::UpdateBreadcrumbs);
                 })?;

crates/diagnostics2/src/items.rs πŸ”—

@@ -77,13 +77,13 @@ impl DiagnosticIndicator {
 
             project::Event::DiskBasedDiagnosticsFinished { language_server_id }
             | project::Event::LanguageServerRemoved(language_server_id) => {
-                this.summary = project.read(cx).diagnostic_summary(cx);
+                this.summary = project.read(cx).diagnostic_summary(false, cx);
                 this.in_progress_checks.remove(language_server_id);
                 cx.notify();
             }
 
             project::Event::DiagnosticsUpdated { .. } => {
-                this.summary = project.read(cx).diagnostic_summary(cx);
+                this.summary = project.read(cx).diagnostic_summary(false, cx);
                 cx.notify();
             }
 
@@ -92,7 +92,7 @@ impl DiagnosticIndicator {
         .detach();
 
         Self {
-            summary: project.read(cx).diagnostic_summary(cx),
+            summary: project.read(cx).diagnostic_summary(false, cx),
             in_progress_checks: project
                 .read(cx)
                 .language_servers_running_disk_based_diagnostics()

crates/diagnostics2/src/toolbar_controls.rs πŸ”—

@@ -1,5 +1,6 @@
 use crate::ProjectDiagnosticsEditor;
 use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
+use ui::prelude::*;
 use ui::{Icon, IconButton, Tooltip};
 use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 

crates/editor2/src/display_map/wrap_map.rs πŸ”—

@@ -162,7 +162,7 @@ impl WrapMap {
                 {
                     let tab_snapshot = new_snapshot.tab_snapshot.clone();
                     let range = TabPoint::zero()..tab_snapshot.max_point();
-                    let edits = new_snapshot
+                    edits = new_snapshot
                         .update(
                             tab_snapshot,
                             &[TabEdit {

crates/editor2/src/editor.rs πŸ”—

@@ -63,6 +63,7 @@ use language::{
 use lazy_static::lazy_static;
 use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
 use lsp::{DiagnosticSeverity, LanguageServerId};
+use mouse_context_menu::MouseContextMenu;
 use movement::TextLayoutDetails;
 use multi_buffer::ToOffsetUtf16;
 pub use multi_buffer::{
@@ -73,7 +74,7 @@ use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock};
 use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
 use rand::prelude::*;
-use rpc::proto::*;
+use rpc::proto::{self, *};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
@@ -99,7 +100,8 @@ use text::{OffsetUtf16, Rope};
 use theme::{
     ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
 };
-use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip};
+use ui::prelude::*;
+use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, Tooltip};
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::{
     item::{ItemEvent, ItemHandle},
@@ -152,9 +154,7 @@ pub fn render_parsed_markdown(
                 }
             }),
     );
-    let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
 
-    // todo!("add the ability to change cursor style for link ranges")
     let mut links = Vec::new();
     let mut link_ranges = Vec::new();
     for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
@@ -166,7 +166,7 @@ pub fn render_parsed_markdown(
 
     InteractiveText::new(
         element_id,
-        StyledText::new(parsed.text.clone()).with_runs(runs),
+        StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
     )
     .on_click(link_ranges, move |clicked_range_ix, cx| {
         match &links[clicked_range_ix] {
@@ -407,133 +407,17 @@ pub fn init_settings(cx: &mut AppContext) {
 
 pub fn init(cx: &mut AppContext) {
     init_settings(cx);
-    // cx.register_action_type(Editor::new_file);
-    // cx.register_action_type(Editor::new_file_in_direction);
-    // cx.register_action_type(Editor::cancel);
-    // cx.register_action_type(Editor::newline);
-    // cx.register_action_type(Editor::newline_above);
-    // cx.register_action_type(Editor::newline_below);
-    // cx.register_action_type(Editor::backspace);
-    // cx.register_action_type(Editor::delete);
-    // cx.register_action_type(Editor::tab);
-    // cx.register_action_type(Editor::tab_prev);
-    // cx.register_action_type(Editor::indent);
-    // cx.register_action_type(Editor::outdent);
-    // cx.register_action_type(Editor::delete_line);
-    // cx.register_action_type(Editor::join_lines);
-    // cx.register_action_type(Editor::sort_lines_case_sensitive);
-    // cx.register_action_type(Editor::sort_lines_case_insensitive);
-    // cx.register_action_type(Editor::reverse_lines);
-    // cx.register_action_type(Editor::shuffle_lines);
-    // cx.register_action_type(Editor::convert_to_upper_case);
-    // cx.register_action_type(Editor::convert_to_lower_case);
-    // cx.register_action_type(Editor::convert_to_title_case);
-    // cx.register_action_type(Editor::convert_to_snake_case);
-    // cx.register_action_type(Editor::convert_to_kebab_case);
-    // cx.register_action_type(Editor::convert_to_upper_camel_case);
-    // cx.register_action_type(Editor::convert_to_lower_camel_case);
-    // cx.register_action_type(Editor::delete_to_previous_word_start);
-    // cx.register_action_type(Editor::delete_to_previous_subword_start);
-    // cx.register_action_type(Editor::delete_to_next_word_end);
-    // cx.register_action_type(Editor::delete_to_next_subword_end);
-    // cx.register_action_type(Editor::delete_to_beginning_of_line);
-    // cx.register_action_type(Editor::delete_to_end_of_line);
-    // cx.register_action_type(Editor::cut_to_end_of_line);
-    // cx.register_action_type(Editor::duplicate_line);
-    // cx.register_action_type(Editor::move_line_up);
-    // cx.register_action_type(Editor::move_line_down);
-    // cx.register_action_type(Editor::transpose);
-    // cx.register_action_type(Editor::cut);
-    // cx.register_action_type(Editor::copy);
-    // cx.register_action_type(Editor::paste);
-    // cx.register_action_type(Editor::undo);
-    // cx.register_action_type(Editor::redo);
-    // cx.register_action_type(Editor::move_page_up);
-    // cx.register_action_type::<MoveDown>();
-    // cx.register_action_type(Editor::move_page_down);
-    // cx.register_action_type(Editor::next_screen);
-    // cx.register_action_type::<MoveLeft>();
-    // cx.register_action_type::<MoveRight>();
-    // cx.register_action_type(Editor::move_to_previous_word_start);
-    // cx.register_action_type(Editor::move_to_previous_subword_start);
-    // cx.register_action_type(Editor::move_to_next_word_end);
-    // cx.register_action_type(Editor::move_to_next_subword_end);
-    // cx.register_action_type(Editor::move_to_beginning_of_line);
-    // cx.register_action_type(Editor::move_to_end_of_line);
-    // cx.register_action_type(Editor::move_to_start_of_paragraph);
-    // cx.register_action_type(Editor::move_to_end_of_paragraph);
-    // cx.register_action_type(Editor::move_to_beginning);
-    // cx.register_action_type(Editor::move_to_end);
-    // cx.register_action_type(Editor::select_up);
-    // cx.register_action_type(Editor::select_down);
-    // cx.register_action_type(Editor::select_left);
-    // cx.register_action_type(Editor::select_right);
-    // cx.register_action_type(Editor::select_to_previous_word_start);
-    // cx.register_action_type(Editor::select_to_previous_subword_start);
-    // cx.register_action_type(Editor::select_to_next_word_end);
-    // cx.register_action_type(Editor::select_to_next_subword_end);
-    // cx.register_action_type(Editor::select_to_beginning_of_line);
-    // cx.register_action_type(Editor::select_to_end_of_line);
-    // cx.register_action_type(Editor::select_to_start_of_paragraph);
-    // cx.register_action_type(Editor::select_to_end_of_paragraph);
-    // cx.register_action_type(Editor::select_to_beginning);
-    // cx.register_action_type(Editor::select_to_end);
-    // cx.register_action_type(Editor::select_all);
-    // cx.register_action_type(Editor::select_all_matches);
-    // cx.register_action_type(Editor::select_line);
-    // cx.register_action_type(Editor::split_selection_into_lines);
-    // cx.register_action_type(Editor::add_selection_above);
-    // cx.register_action_type(Editor::add_selection_below);
-    // cx.register_action_type(Editor::select_next);
-    // cx.register_action_type(Editor::select_previous);
-    // cx.register_action_type(Editor::toggle_comments);
-    // cx.register_action_type(Editor::select_larger_syntax_node);
-    // cx.register_action_type(Editor::select_smaller_syntax_node);
-    // cx.register_action_type(Editor::move_to_enclosing_bracket);
-    // cx.register_action_type(Editor::undo_selection);
-    // cx.register_action_type(Editor::redo_selection);
-    // cx.register_action_type(Editor::go_to_diagnostic);
-    // cx.register_action_type(Editor::go_to_prev_diagnostic);
-    // cx.register_action_type(Editor::go_to_hunk);
-    // cx.register_action_type(Editor::go_to_prev_hunk);
-    // cx.register_action_type(Editor::go_to_definition);
-    // cx.register_action_type(Editor::go_to_definition_split);
-    // cx.register_action_type(Editor::go_to_type_definition);
-    // cx.register_action_type(Editor::go_to_type_definition_split);
-    // cx.register_action_type(Editor::fold);
-    // cx.register_action_type(Editor::fold_at);
-    // cx.register_action_type(Editor::unfold_lines);
-    // cx.register_action_type(Editor::unfold_at);
-    // cx.register_action_type(Editor::gutter_hover);
-    // cx.register_action_type(Editor::fold_selected_ranges);
-    // cx.register_action_type(Editor::show_completions);
-    // cx.register_action_type(Editor::toggle_code_actions);
-    // cx.register_action_type(Editor::open_excerpts);
-    // cx.register_action_type(Editor::toggle_soft_wrap);
-    // cx.register_action_type(Editor::toggle_inlay_hints);
-    // cx.register_action_type(Editor::reveal_in_finder);
-    // cx.register_action_type(Editor::copy_path);
-    // cx.register_action_type(Editor::copy_relative_path);
-    // cx.register_action_type(Editor::copy_highlight_json);
-    // cx.add_async_action(Editor::format);
-    // cx.register_action_type(Editor::restart_language_server);
-    // cx.register_action_type(Editor::show_character_palette);
-    // cx.add_async_action(Editor::confirm_completion);
-    // cx.add_async_action(Editor::confirm_code_action);
-    // cx.add_async_action(Editor::rename);
-    // cx.add_async_action(Editor::confirm_rename);
-    // cx.add_async_action(Editor::find_all_references);
-    // cx.register_action_type(Editor::next_copilot_suggestion);
-    // cx.register_action_type(Editor::previous_copilot_suggestion);
-    // cx.register_action_type(Editor::copilot_suggest);
-    // cx.register_action_type(Editor::context_menu_first);
-    // cx.register_action_type(Editor::context_menu_prev);
-    // cx.register_action_type(Editor::context_menu_next);
-    // cx.register_action_type(Editor::context_menu_last);
 
     workspace::register_project_item::<Editor>(cx);
     workspace::register_followable_item::<Editor>(cx);
     workspace::register_deserializable_item::<Editor>(cx);
+    cx.observe_new_views(
+        |workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
+            workspace.register_action(Editor::new_file);
+            workspace.register_action(Editor::new_file_in_direction);
+        },
+    )
+    .detach();
 }
 
 trait InvalidationRegion {
@@ -621,8 +505,6 @@ pub struct Editor {
     ime_transaction: Option<TransactionId>,
     active_diagnostics: Option<ActiveDiagnosticGroup>,
     soft_wrap_mode_override: Option<language_settings::SoftWrap>,
-    // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
-    // override_text_style: Option<Box<OverrideTextStyle>>,
     project: Option<Model<Project>>,
     collaboration_hub: Option<Box<dyn CollaborationHub>>,
     blink_manager: Model<BlinkManager>,
@@ -636,7 +518,7 @@ pub struct Editor {
     inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
     nav_history: Option<ItemNavHistory>,
     context_menu: RwLock<Option<ContextMenu>>,
-    // mouse_context_menu: View<context_menu::ContextMenu>,
+    mouse_context_menu: Option<MouseContextMenu>,
     completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
     next_completion_id: CompletionId,
     available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
@@ -971,95 +853,94 @@ impl CompletionsMenu {
 
     fn pre_resolve_completion_documentation(
         &self,
-        _editor: &Editor,
-        _cx: &mut ViewContext<Editor>,
+        editor: &Editor,
+        cx: &mut ViewContext<Editor>,
     ) -> Option<Task<()>> {
-        // todo!("implementation below ");
-        None
-    }
-    // {
-    //     let settings = EditorSettings::get_global(cx);
-    //     if !settings.show_completion_documentation {
-    //         return None;
-    //     }
+        let settings = EditorSettings::get_global(cx);
+        if !settings.show_completion_documentation {
+            return None;
+        }
 
-    //     let Some(project) = editor.project.clone() else {
-    //         return None;
-    //     };
-
-    //     let client = project.read(cx).client();
-    //     let language_registry = project.read(cx).languages().clone();
-
-    //     let is_remote = project.read(cx).is_remote();
-    //     let project_id = project.read(cx).remote_id();
-
-    //     let completions = self.completions.clone();
-    //     let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
-
-    //     Some(cx.spawn(move |this, mut cx| async move {
-    //         if is_remote {
-    //             let Some(project_id) = project_id else {
-    //                 log::error!("Remote project without remote_id");
-    //                 return;
-    //             };
-
-    //             for completion_index in completion_indices {
-    //                 let completions_guard = completions.read();
-    //                 let completion = &completions_guard[completion_index];
-    //                 if completion.documentation.is_some() {
-    //                     continue;
-    //                 }
-
-    //                 let server_id = completion.server_id;
-    //                 let completion = completion.lsp_completion.clone();
-    //                 drop(completions_guard);
-
-    //                 Self::resolve_completion_documentation_remote(
-    //                     project_id,
-    //                     server_id,
-    //                     completions.clone(),
-    //                     completion_index,
-    //                     completion,
-    //                     client.clone(),
-    //                     language_registry.clone(),
-    //                 )
-    //                 .await;
-
-    //                 _ = this.update(&mut cx, |_, cx| cx.notify());
-    //             }
-    //         } else {
-    //             for completion_index in completion_indices {
-    //                 let completions_guard = completions.read();
-    //                 let completion = &completions_guard[completion_index];
-    //                 if completion.documentation.is_some() {
-    //                     continue;
-    //                 }
-
-    //                 let server_id = completion.server_id;
-    //                 let completion = completion.lsp_completion.clone();
-    //                 drop(completions_guard);
-
-    //                 let server = project.read_with(&mut cx, |project, _| {
-    //                     project.language_server_for_id(server_id)
-    //                 });
-    //                 let Some(server) = server else {
-    //                     return;
-    //                 };
-
-    //                 Self::resolve_completion_documentation_local(
-    //                     server,
-    //                     completions.clone(),
-    //                     completion_index,
-    //                     completion,
-    //                     language_registry.clone(),
-    //                 )
-    //                 .await;
-
-    //                 _ = this.update(&mut cx, |_, cx| cx.notify());
-    //             }
-    //         }
-    //     }))
-    // }
+        let Some(project) = editor.project.clone() else {
+            return None;
+        };
+
+        let client = project.read(cx).client();
+        let language_registry = project.read(cx).languages().clone();
+
+        let is_remote = project.read(cx).is_remote();
+        let project_id = project.read(cx).remote_id();
+
+        let completions = self.completions.clone();
+        let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
+
+        Some(cx.spawn(move |this, mut cx| async move {
+            if is_remote {
+                let Some(project_id) = project_id else {
+                    log::error!("Remote project without remote_id");
+                    return;
+                };
+
+                for completion_index in completion_indices {
+                    let completions_guard = completions.read();
+                    let completion = &completions_guard[completion_index];
+                    if completion.documentation.is_some() {
+                        continue;
+                    }
+
+                    let server_id = completion.server_id;
+                    let completion = completion.lsp_completion.clone();
+                    drop(completions_guard);
+
+                    Self::resolve_completion_documentation_remote(
+                        project_id,
+                        server_id,
+                        completions.clone(),
+                        completion_index,
+                        completion,
+                        client.clone(),
+                        language_registry.clone(),
+                    )
+                    .await;
+
+                    _ = this.update(&mut cx, |_, cx| cx.notify());
+                }
+            } else {
+                for completion_index in completion_indices {
+                    let completions_guard = completions.read();
+                    let completion = &completions_guard[completion_index];
+                    if completion.documentation.is_some() {
+                        continue;
+                    }
+
+                    let server_id = completion.server_id;
+                    let completion = completion.lsp_completion.clone();
+                    drop(completions_guard);
+
+                    let server = project
+                        .read_with(&mut cx, |project, _| {
+                            project.language_server_for_id(server_id)
+                        })
+                        .ok()
+                        .flatten();
+                    let Some(server) = server else {
+                        return;
+                    };
+
+                    Self::resolve_completion_documentation_local(
+                        server,
+                        completions.clone(),
+                        completion_index,
+                        completion,
+                        language_registry.clone(),
+                    )
+                    .await;
+
+                    _ = this.update(&mut cx, |_, cx| cx.notify());
+                }
+            }
+        }))
+    }
 
     fn attempt_resolve_selected_completion_documentation(
         &mut self,
@@ -1080,10 +961,9 @@ impl CompletionsMenu {
         let completions = self.completions.clone();
         let completions_guard = completions.read();
         let completion = &completions_guard[completion_index];
-        // todo!()
-        // if completion.documentation.is_some() {
-        //     return;
-        // }
+        if completion.documentation.is_some() {
+            return;
+        }
 
         let server_id = completion.server_id;
         let completion = completion.lsp_completion.clone();
@@ -1142,41 +1022,40 @@ impl CompletionsMenu {
         client: Arc<Client>,
         language_registry: Arc<LanguageRegistry>,
     ) {
-        // todo!()
-        // let request = proto::ResolveCompletionDocumentation {
-        //     project_id,
-        //     language_server_id: server_id.0 as u64,
-        //     lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
-        // };
-
-        // let Some(response) = client
-        //     .request(request)
-        //     .await
-        //     .context("completion documentation resolve proto request")
-        //     .log_err()
-        // else {
-        //     return;
-        // };
-
-        // if response.text.is_empty() {
-        //     let mut completions = completions.write();
-        //     let completion = &mut completions[completion_index];
-        //     completion.documentation = Some(Documentation::Undocumented);
-        // }
-
-        // let documentation = if response.is_markdown {
-        //     Documentation::MultiLineMarkdown(
-        //         markdown::parse_markdown(&response.text, &language_registry, None).await,
-        //     )
-        // } else if response.text.lines().count() <= 1 {
-        //     Documentation::SingleLine(response.text)
-        // } else {
-        //     Documentation::MultiLinePlainText(response.text)
-        // };
-
-        // let mut completions = completions.write();
-        // let completion = &mut completions[completion_index];
-        // completion.documentation = Some(documentation);
+        let request = proto::ResolveCompletionDocumentation {
+            project_id,
+            language_server_id: server_id.0 as u64,
+            lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
+        };
+
+        let Some(response) = client
+            .request(request)
+            .await
+            .context("completion documentation resolve proto request")
+            .log_err()
+        else {
+            return;
+        };
+
+        if response.text.is_empty() {
+            let mut completions = completions.write();
+            let completion = &mut completions[completion_index];
+            completion.documentation = Some(Documentation::Undocumented);
+        }
+
+        let documentation = if response.is_markdown {
+            Documentation::MultiLineMarkdown(
+                markdown::parse_markdown(&response.text, &language_registry, None).await,
+            )
+        } else if response.text.lines().count() <= 1 {
+            Documentation::SingleLine(response.text)
+        } else {
+            Documentation::MultiLinePlainText(response.text)
+        };
+
+        let mut completions = completions.write();
+        let completion = &mut completions[completion_index];
+        completion.documentation = Some(documentation);
     }
 
     async fn resolve_completion_documentation_local(
@@ -1186,38 +1065,37 @@ impl CompletionsMenu {
         completion: lsp::CompletionItem,
         language_registry: Arc<LanguageRegistry>,
     ) {
-        // todo!()
-        // let can_resolve = server
-        //     .capabilities()
-        //     .completion_provider
-        //     .as_ref()
-        //     .and_then(|options| options.resolve_provider)
-        //     .unwrap_or(false);
-        // if !can_resolve {
-        //     return;
-        // }
-
-        // let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
-        // let Some(completion_item) = request.await.log_err() else {
-        //     return;
-        // };
-
-        // if let Some(lsp_documentation) = completion_item.documentation {
-        //     let documentation = language::prepare_completion_documentation(
-        //         &lsp_documentation,
-        //         &language_registry,
-        //         None, // TODO: Try to reasonably work out which language the completion is for
-        //     )
-        //     .await;
-
-        //     let mut completions = completions.write();
-        //     let completion = &mut completions[completion_index];
-        //     completion.documentation = Some(documentation);
-        // } else {
-        //     let mut completions = completions.write();
-        //     let completion = &mut completions[completion_index];
-        //     completion.documentation = Some(Documentation::Undocumented);
-        // }
+        let can_resolve = server
+            .capabilities()
+            .completion_provider
+            .as_ref()
+            .and_then(|options| options.resolve_provider)
+            .unwrap_or(false);
+        if !can_resolve {
+            return;
+        }
+
+        let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
+        let Some(completion_item) = request.await.log_err() else {
+            return;
+        };
+
+        if let Some(lsp_documentation) = completion_item.documentation {
+            let documentation = language::prepare_completion_documentation(
+                &lsp_documentation,
+                &language_registry,
+                None, // TODO: Try to reasonably work out which language the completion is for
+            )
+            .await;
+
+            let mut completions = completions.write();
+            let completion = &mut completions[completion_index];
+            completion.documentation = Some(documentation);
+        } else {
+            let mut completions = completions.write();
+            let completion = &mut completions[completion_index];
+            completion.documentation = Some(Documentation::Undocumented);
+        }
     }
 
     fn visible(&self) -> bool {
@@ -1320,11 +1198,7 @@ impl CompletionsMenu {
                             ),
                         );
                         let completion_label = StyledText::new(completion.label.text.clone())
-                            .with_runs(text_runs_for_highlights(
-                                &completion.label.text,
-                                &style.text,
-                                highlights,
-                            ));
+                            .with_highlights(&style.text, highlights);
                         let documentation_label =
                             if let Some(Documentation::SingleLine(text)) = documentation {
                                 Some(SharedString::from(text.clone()))
@@ -1738,21 +1612,11 @@ impl Editor {
     //         Self::new(EditorMode::Full, buffer, None, field_editor_style, cx)
     //     }
 
-    //     pub fn auto_height(
-    //         max_lines: usize,
-    //         field_editor_style: Option<Arc<GetFieldEditorTheme>>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Self {
-    //         let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new()));
-    //         let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
-    //         Self::new(
-    //             EditorMode::AutoHeight { max_lines },
-    //             buffer,
-    //             None,
-    //             field_editor_style,
-    //             cx,
-    //         )
-    //     }
+    pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
+        let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+        let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+        Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
+    }
 
     pub fn for_buffer(
         buffer: Model<Buffer>,
@@ -1772,14 +1636,7 @@ impl Editor {
     }
 
     pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
-        let mut clone = Self::new(
-            self.mode,
-            self.buffer.clone(),
-            self.project.clone(),
-            // todo!
-            // self.get_field_editor_theme.clone(),
-            cx,
-        );
+        let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx);
         self.display_map.update(cx, |display_map, cx| {
             let snapshot = display_map.snapshot(cx);
             clone.display_map.update(cx, |display_map, cx| {
@@ -1796,17 +1653,11 @@ impl Editor {
         mode: EditorMode,
         buffer: Model<MultiBuffer>,
         project: Option<Model<Project>>,
-        // todo!()
-        // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        // let editor_view_id = cx.view_id();
         let style = cx.text_style();
         let font_size = style.font_size.to_pixels(cx.rem_size());
         let display_map = cx.build_model(|cx| {
-            // todo!()
-            // let settings = settings::get::<ThemeSettings>(cx);
-            // let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
             DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
         });
 
@@ -1862,7 +1713,6 @@ impl Editor {
             ime_transaction: Default::default(),
             active_diagnostics: None,
             soft_wrap_mode_override,
-            // get_field_editor_theme,
             collaboration_hub: project.clone().map(|project| Box::new(project) as _),
             project,
             blink_manager: blink_manager.clone(),
@@ -1876,8 +1726,7 @@ impl Editor {
             inlay_background_highlights: Default::default(),
             nav_history: None,
             context_menu: RwLock::new(None),
-            // mouse_context_menu: cx
-            //     .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
+            mouse_context_menu: None,
             completion_tasks: Default::default(),
             next_completion_id: 0,
             next_inlay_id: 0,
@@ -1886,7 +1735,6 @@ impl Editor {
             document_highlights_task: Default::default(),
             pending_rename: Default::default(),
             searchable: true,
-            // override_text_style: None,
             cursor_shape: Default::default(),
             autoindent_mode: Some(AutoindentMode::EachLine),
             collapse_matches: false,
@@ -2004,25 +1852,25 @@ impl Editor {
         }
     }
 
-    //     pub fn new_file_in_direction(
-    //         workspace: &mut Workspace,
-    //         action: &workspace::NewFileInDirection,
-    //         cx: &mut ViewContext<Workspace>,
-    //     ) {
-    //         let project = workspace.project().clone();
-    //         if project.read(cx).is_remote() {
-    //             cx.propagate();
-    //         } else if let Some(buffer) = project
-    //             .update(cx, |project, cx| project.create_buffer("", None, cx))
-    //             .log_err()
-    //         {
-    //             workspace.split_item(
-    //                 action.0,
-    //                 Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
-    //                 cx,
-    //             );
-    //         }
-    //     }
+    pub fn new_file_in_direction(
+        workspace: &mut Workspace,
+        action: &workspace::NewFileInDirection,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let project = workspace.project().clone();
+        if project.read(cx).is_remote() {
+            cx.propagate();
+        } else if let Some(buffer) = project
+            .update(cx, |project, cx| project.create_buffer("", None, cx))
+            .log_err()
+        {
+            workspace.split_item(
+                action.0,
+                Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
+                cx,
+            );
+        }
+    }
 
     pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
         self.buffer.read(cx).replica_id()
@@ -4391,7 +4239,7 @@ impl Editor {
                                         editor.fold_at(&FoldAt { buffer_row }, cx);
                                     }
                                 }))
-                                .color(ui::Color::Muted)
+                                .icon_color(ui::Color::Muted)
                         })
                     })
                     .flatten()
@@ -8378,6 +8226,18 @@ impl Editor {
         cx.notify();
     }
 
+    pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
+        let rem_size = cx.rem_size();
+        self.display_map.update(cx, |map, cx| {
+            map.set_font(
+                style.text.font(),
+                style.text.font_size.to_pixels(rem_size),
+                cx,
+            )
+        });
+        self.style = Some(style);
+    }
+
     pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
         self.display_map
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
@@ -8800,62 +8660,56 @@ impl Editor {
     //         self.searchable
     //     }
 
-    //     fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext<Workspace>) {
-    //         let active_item = workspace.active_item(cx);
-    //         let editor_handle = if let Some(editor) = active_item
-    //             .as_ref()
-    //             .and_then(|item| item.act_as::<Self>(cx))
-    //         {
-    //             editor
-    //         } else {
-    //             cx.propagate();
-    //             return;
-    //         };
-
-    //         let editor = editor_handle.read(cx);
-    //         let buffer = editor.buffer.read(cx);
-    //         if buffer.is_singleton() {
-    //             cx.propagate();
-    //             return;
-    //         }
+    fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(cx);
+        if buffer.is_singleton() {
+            cx.propagate();
+            return;
+        }
 
-    //         let mut new_selections_by_buffer = HashMap::default();
-    //         for selection in editor.selections.all::<usize>(cx) {
-    //             for (buffer, mut range, _) in
-    //                 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
-    //             {
-    //                 if selection.reversed {
-    //                     mem::swap(&mut range.start, &mut range.end);
-    //                 }
-    //                 new_selections_by_buffer
-    //                     .entry(buffer)
-    //                     .or_insert(Vec::new())
-    //                     .push(range)
-    //             }
-    //         }
+        let Some(workspace) = self.workspace() else {
+            cx.propagate();
+            return;
+        };
 
-    //         editor_handle.update(cx, |editor, cx| {
-    //             editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx);
-    //         });
-    //         let pane = workspace.active_pane().clone();
-    //         pane.update(cx, |pane, _| pane.disable_history());
-
-    //         // We defer the pane interaction because we ourselves are a workspace item
-    //         // and activating a new item causes the pane to call a method on us reentrantly,
-    //         // which panics if we're on the stack.
-    //         cx.defer(move |workspace, cx| {
-    //             for (buffer, ranges) in new_selections_by_buffer.into_iter() {
-    //                 let editor = workspace.open_project_item::<Self>(buffer, cx);
-    //                 editor.update(cx, |editor, cx| {
-    //                     editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
-    //                         s.select_ranges(ranges);
-    //                     });
-    //                 });
-    //             }
-
-    //             pane.update(cx, |pane, _| pane.enable_history());
-    //         });
-    //     }
+        let mut new_selections_by_buffer = HashMap::default();
+        for selection in self.selections.all::<usize>(cx) {
+            for (buffer, mut range, _) in
+                buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
+            {
+                if selection.reversed {
+                    mem::swap(&mut range.start, &mut range.end);
+                }
+                new_selections_by_buffer
+                    .entry(buffer)
+                    .or_insert(Vec::new())
+                    .push(range)
+            }
+        }
+
+        self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
+
+        // We defer the pane interaction because we ourselves are a workspace item
+        // and activating a new item causes the pane to call a method on us reentrantly,
+        // which panics if we're on the stack.
+        cx.window_context().defer(move |cx| {
+            workspace.update(cx, |workspace, cx| {
+                let pane = workspace.active_pane().clone();
+                pane.update(cx, |pane, _| pane.disable_history());
+
+                for (buffer, ranges) in new_selections_by_buffer.into_iter() {
+                    let editor = workspace.open_project_item::<Self>(buffer, cx);
+                    editor.update(cx, |editor, cx| {
+                        editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
+                            s.select_ranges(ranges);
+                        });
+                    });
+                }
+
+                pane.update(cx, |pane, _| pane.enable_history());
+            })
+        });
+    }
 
     fn jump(
         &mut self,
@@ -9401,7 +9255,7 @@ impl Render for Editor {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let settings = ThemeSettings::get_global(cx);
         let text_style = match self.mode {
-            EditorMode::SingleLine => TextStyle {
+            EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
                 color: cx.theme().colors().text,
                 font_family: settings.ui_font.family.clone(),
                 font_features: settings.ui_font.features,
@@ -9414,8 +9268,6 @@ impl Render for Editor {
                 white_space: WhiteSpace::Normal,
             },
 
-            EditorMode::AutoHeight { max_lines } => todo!(),
-
             EditorMode::Full => TextStyle {
                 color: cx.theme().colors().text,
                 font_family: settings.buffer_font.family.clone(),
@@ -9450,106 +9302,6 @@ impl Render for Editor {
     }
 }
 
-// impl View for Editor {
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let style = self.style(cx);
-//         let font_changed = self.display_map.update(cx, |map, cx| {
-//             map.set_fold_ellipses_color(style.folds.ellipses.text_color);
-//             map.set_font_with_size(style.text.font_id, style.text.font_size, cx)
-//         });
-
-//         if font_changed {
-//             cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
-//                 hide_hover(editor, cx);
-//                 hide_link_definition(editor, cx);
-//             });
-//         }
-
-//         Stack::new()
-//             .with_child(EditorElement::new(style.clone()))
-//             .with_child(ChildView::new(&self.mouse_context_menu, cx))
-//             .into_any()
-//     }
-
-//     fn ui_name() -> &'static str {
-//         "Editor"
-//     }
-
-//     fn focus_in(&mut self, focused: AnyView, cx: &mut ViewContext<Self>) {
-//         if cx.is_self_focused() {
-//             let focused_event = EditorFocused(cx.handle());
-//             cx.emit(Event::Focused);
-//             cx.emit_global(focused_event);
-//         }
-//         if let Some(rename) = self.pending_rename.as_ref() {
-//             cx.focus(&rename.editor);
-//         } else if cx.is_self_focused() || !focused.is::<Editor>() {
-//             if !self.focused {
-//                 self.blink_manager.update(cx, BlinkManager::enable);
-//             }
-//             self.focused = true;
-//             self.buffer.update(cx, |buffer, cx| {
-//                 buffer.finalize_last_transaction(cx);
-//                 if self.leader_peer_id.is_none() {
-//                     buffer.set_active_selections(
-//                         &self.selections.disjoint_anchors(),
-//                         self.selections.line_mode,
-//                         self.cursor_shape,
-//                         cx,
-//                     );
-//                 }
-//             });
-//         }
-//     }
-
-//     fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-//         let blurred_event = EditorBlurred(cx.handle());
-//         cx.emit_global(blurred_event);
-//         self.focused = false;
-//         self.blink_manager.update(cx, BlinkManager::disable);
-//         self.buffer
-//             .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
-//         self.hide_context_menu(cx);
-//         hide_hover(self, cx);
-//         cx.emit(Event::Blurred);
-//         cx.notify();
-//     }
-
-//     fn modifiers_changed(
-//         &mut self,
-//         event: &gpui::platform::ModifiersChangedEvent,
-//         cx: &mut ViewContext<Self>,
-//     ) -> bool {
-//         let pending_selection = self.has_pending_selection();
-
-//         if let Some(point) = &self.link_go_to_definition_state.last_trigger_point {
-//             if event.cmd && !pending_selection {
-//                 let point = point.clone();
-//                 let snapshot = self.snapshot(cx);
-//                 let kind = point.definition_kind(event.shift);
-
-//                 show_link_definition(kind, self, point, snapshot, cx);
-//                 return false;
-//             }
-//         }
-
-//         {
-//             if self.link_go_to_definition_state.symbol_range.is_some()
-//                 || !self.link_go_to_definition_state.definitions.is_empty()
-//             {
-//                 self.link_go_to_definition_state.symbol_range.take();
-//                 self.link_go_to_definition_state.definitions.clear();
-//                 cx.notify();
-//             }
-
-//             self.link_go_to_definition_state.task = None;
-
-//             self.clear_highlights::<LinkGoToDefinitionState>(cx);
-//         }
-
-//         false
-//     }
-
 impl InputHandler for Editor {
     fn text_for_range(
         &mut self,
@@ -9796,72 +9548,6 @@ impl InputHandler for Editor {
     }
 }
 
-// fn build_style(
-//     settings: &ThemeSettings,
-//     get_field_editor_theme: Option<&GetFieldEditorTheme>,
-//     override_text_style: Option<&OverrideTextStyle>,
-//     cx: &mut AppContext,
-// ) -> EditorStyle {
-//     let font_cache = cx.font_cache();
-//     let line_height_scalar = settings.line_height();
-//     let theme_id = settings.theme.meta.id;
-//     let mut theme = settings.theme.editor.clone();
-//     let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme {
-//         let field_editor_theme = get_field_editor_theme(&settings.theme);
-//         theme.text_color = field_editor_theme.text.color;
-//         theme.selection = field_editor_theme.selection;
-//         theme.background = field_editor_theme
-//             .container
-//             .background_color
-//             .unwrap_or_default();
-//         EditorStyle {
-//             text: field_editor_theme.text,
-//             placeholder_text: field_editor_theme.placeholder_text,
-//             line_height_scalar,
-//             theme,
-//             theme_id,
-//         }
-//     } else {
-//         todo!();
-//         // let font_family_id = settings.buffer_font_family;
-//         // let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
-//         // let font_properties = Default::default();
-//         // let font_id = font_cache
-//         //     .select_font(font_family_id, &font_properties)
-//         //     .unwrap();
-//         // let font_size = settings.buffer_font_size(cx);
-//         // EditorStyle {
-//         //     text: TextStyle {
-//         //         color: settings.theme.editor.text_color,
-//         //         font_family_name,
-//         //         font_family_id,
-//         //         font_id,
-//         //         font_size,
-//         //         font_properties,
-//         //         underline: Default::default(),
-//         //         soft_wrap: false,
-//         //     },
-//         //     placeholder_text: None,
-//         //     line_height_scalar,
-//         //     theme,
-//         //     theme_id,
-//         // }
-//     };
-
-//     if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) {
-//         if let Some(highlighted) = style
-//             .text
-//             .clone()
-//             .highlight(highlight_style, font_cache)
-//             .log_err()
-//         {
-//             style.text = highlighted;
-//         }
-//     }
-
-//     style
-// }
-
 trait SelectionExt {
     fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
     fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;

crates/editor2/src/editor_tests.rs πŸ”—

@@ -5427,178 +5427,177 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
     );
 }
 
-//todo!(completion)
-// #[gpui::test]
-// async fn test_completion(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_completion(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
-//                 resolve_provider: Some(true),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+                resolve_provider: Some(true),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
 
-//     cx.set_state(indoc! {"
-//         oneˇ
-//         two
-//         three
-//     "});
-//     cx.simulate_keystroke(".");
-//     handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.|<>
-//             two
-//             three
-//         "},
-//         vec!["first_completion", "second_completion"],
-//     )
-//     .await;
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
-//     let apply_additional_edits = cx.update_editor(|editor, cx| {
-//         editor.context_menu_next(&Default::default(), cx);
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completionˇ
-//         two
-//         three
-//     "});
+    cx.set_state(indoc! {"
+        oneˇ
+        two
+        three
+    "});
+    cx.simulate_keystroke(".");
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.|<>
+            two
+            three
+        "},
+        vec!["first_completion", "second_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor.context_menu_next(&Default::default(), cx);
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"
+        one.second_completionˇ
+        two
+        three
+    "});
 
-//     handle_resolve_completion_request(
-//         &mut cx,
-//         Some(vec![
-//             (
-//                 //This overlaps with the primary completion edit which is
-//                 //misbehavior from the LSP spec, test that we filter it out
-//                 indoc! {"
-//                     one.second_Λ‡completion
-//                     two
-//                     threeˇ
-//                 "},
-//                 "overlapping additional edit",
-//             ),
-//             (
-//                 indoc! {"
-//                     one.second_completion
-//                     two
-//                     threeˇ
-//                 "},
-//                 "\nadditional edit",
-//             ),
-//         ]),
-//     )
-//     .await;
-//     apply_additional_edits.await.unwrap();
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completionˇ
-//         two
-//         three
-//         additional edit
-//     "});
+    handle_resolve_completion_request(
+        &mut cx,
+        Some(vec![
+            (
+                //This overlaps with the primary completion edit which is
+                //misbehavior from the LSP spec, test that we filter it out
+                indoc! {"
+                    one.second_Λ‡completion
+                    two
+                    threeˇ
+                "},
+                "overlapping additional edit",
+            ),
+            (
+                indoc! {"
+                    one.second_completion
+                    two
+                    threeˇ
+                "},
+                "\nadditional edit",
+            ),
+        ]),
+    )
+    .await;
+    apply_additional_edits.await.unwrap();
+    cx.assert_editor_state(indoc! {"
+        one.second_completionˇ
+        two
+        three
+        additional edit
+    "});
 
-//     cx.set_state(indoc! {"
-//         one.second_completion
-//         twoˇ
-//         threeˇ
-//         additional edit
-//     "});
-//     cx.simulate_keystroke(" ");
-//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-//     cx.simulate_keystroke("s");
-//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+    cx.set_state(indoc! {"
+        one.second_completion
+        twoˇ
+        threeˇ
+        additional edit
+    "});
+    cx.simulate_keystroke(" ");
+    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+    cx.simulate_keystroke("s");
+    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completion
-//         two sˇ
-//         three sˇ
-//         additional edit
-//     "});
-//     handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.second_completion
-//             two s
-//             three <s|>
-//             additional edit
-//         "},
-//         vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-//     )
-//     .await;
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
+    cx.assert_editor_state(indoc! {"
+        one.second_completion
+        two sˇ
+        three sˇ
+        additional edit
+    "});
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.second_completion
+            two s
+            three <s|>
+            additional edit
+        "},
+        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
 
-//     cx.simulate_keystroke("i");
+    cx.simulate_keystroke("i");
 
-//     handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.second_completion
-//             two si
-//             three <si|>
-//             additional edit
-//         "},
-//         vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-//     )
-//     .await;
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.second_completion
+            two si
+            three <si|>
+            additional edit
+        "},
+        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
 
-//     let apply_additional_edits = cx.update_editor(|editor, cx| {
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completion
-//         two sixth_completionˇ
-//         three sixth_completionˇ
-//         additional edit
-//     "});
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"
+        one.second_completion
+        two sixth_completionˇ
+        three sixth_completionˇ
+        additional edit
+    "});
 
-//     handle_resolve_completion_request(&mut cx, None).await;
-//     apply_additional_edits.await.unwrap();
+    handle_resolve_completion_request(&mut cx, None).await;
+    apply_additional_edits.await.unwrap();
 
-//     cx.update(|cx| {
-//         cx.update_global::<SettingsStore, _, _>(|settings, cx| {
-//             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-//                 settings.show_completions_on_input = Some(false);
-//             });
-//         })
-//     });
-//     cx.set_state("editorˇ");
-//     cx.simulate_keystroke(".");
-//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-//     cx.simulate_keystroke("c");
-//     cx.simulate_keystroke("l");
-//     cx.simulate_keystroke("o");
-//     cx.assert_editor_state("editor.cloˇ");
-//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-//     cx.update_editor(|editor, cx| {
-//         editor.show_completions(&ShowCompletions, cx);
-//     });
-//     handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
-//     let apply_additional_edits = cx.update_editor(|editor, cx| {
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state("editor.closeˇ");
-//     handle_resolve_completion_request(&mut cx, None).await;
-//     apply_additional_edits.await.unwrap();
-// }
+    cx.update(|cx| {
+        cx.update_global::<SettingsStore, _>(|settings, cx| {
+            settings.update_user_settings::<EditorSettings>(cx, |settings| {
+                settings.show_completions_on_input = Some(false);
+            });
+        })
+    });
+    cx.set_state("editorˇ");
+    cx.simulate_keystroke(".");
+    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+    cx.simulate_keystroke("c");
+    cx.simulate_keystroke("l");
+    cx.simulate_keystroke("o");
+    cx.assert_editor_state("editor.cloˇ");
+    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+    cx.update_editor(|editor, cx| {
+        editor.show_completions(&ShowCompletions, cx);
+    });
+    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state("editor.closeˇ");
+    handle_resolve_completion_request(&mut cx, None).await;
+    apply_additional_edits.await.unwrap();
+}
 
 #[gpui::test]
 async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
@@ -7803,197 +7802,196 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
     );
 }
 
-//todo!(completions)
-// #[gpui::test]
-// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![".".to_string()]),
-//                 resolve_provider: Some(true),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![".".to_string()]),
+                resolve_provider: Some(true),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
 
-//     cx.set_state(indoc! {"fn main() { let a = 2Λ‡; }"});
-//     cx.simulate_keystroke(".");
-//     let completion_item = lsp::CompletionItem {
-//         label: "some".into(),
-//         kind: Some(lsp::CompletionItemKind::SNIPPET),
-//         detail: Some("Wrap the expression in an `Option::Some`".to_string()),
-//         documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
-//             kind: lsp::MarkupKind::Markdown,
-//             value: "```rust\nSome(2)\n```".to_string(),
-//         })),
-//         deprecated: Some(false),
-//         sort_text: Some("fffffff2".to_string()),
-//         filter_text: Some("some".to_string()),
-//         insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-//         text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-//             range: lsp::Range {
-//                 start: lsp::Position {
-//                     line: 0,
-//                     character: 22,
-//                 },
-//                 end: lsp::Position {
-//                     line: 0,
-//                     character: 22,
-//                 },
-//             },
-//             new_text: "Some(2)".to_string(),
-//         })),
-//         additional_text_edits: Some(vec![lsp::TextEdit {
-//             range: lsp::Range {
-//                 start: lsp::Position {
-//                     line: 0,
-//                     character: 20,
-//                 },
-//                 end: lsp::Position {
-//                     line: 0,
-//                     character: 22,
-//                 },
-//             },
-//             new_text: "".to_string(),
-//         }]),
-//         ..Default::default()
-//     };
-
-//     let closure_completion_item = completion_item.clone();
-//     let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
-//         let task_completion_item = closure_completion_item.clone();
-//         async move {
-//             Ok(Some(lsp::CompletionResponse::Array(vec![
-//                 task_completion_item,
-//             ])))
-//         }
-//     });
+    cx.set_state(indoc! {"fn main() { let a = 2Λ‡; }"});
+    cx.simulate_keystroke(".");
+    let completion_item = lsp::CompletionItem {
+        label: "some".into(),
+        kind: Some(lsp::CompletionItemKind::SNIPPET),
+        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
+        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
+            kind: lsp::MarkupKind::Markdown,
+            value: "```rust\nSome(2)\n```".to_string(),
+        })),
+        deprecated: Some(false),
+        sort_text: Some("fffffff2".to_string()),
+        filter_text: Some("some".to_string()),
+        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+            range: lsp::Range {
+                start: lsp::Position {
+                    line: 0,
+                    character: 22,
+                },
+                end: lsp::Position {
+                    line: 0,
+                    character: 22,
+                },
+            },
+            new_text: "Some(2)".to_string(),
+        })),
+        additional_text_edits: Some(vec![lsp::TextEdit {
+            range: lsp::Range {
+                start: lsp::Position {
+                    line: 0,
+                    character: 20,
+                },
+                end: lsp::Position {
+                    line: 0,
+                    character: 22,
+                },
+            },
+            new_text: "".to_string(),
+        }]),
+        ..Default::default()
+    };
 
-//     request.next().await;
+    let closure_completion_item = completion_item.clone();
+    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
+        let task_completion_item = closure_completion_item.clone();
+        async move {
+            Ok(Some(lsp::CompletionResponse::Array(vec![
+                task_completion_item,
+            ])))
+        }
+    });
 
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
-//     let apply_additional_edits = cx.update_editor(|editor, cx| {
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)Λ‡; }"});
-
-//     cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
-//         let task_completion_item = completion_item.clone();
-//         async move { Ok(task_completion_item) }
-//     })
-//     .next()
-//     .await
-//     .unwrap();
-//     apply_additional_edits.await.unwrap();
-//     cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)Λ‡; }"});
-// }
+    request.next().await;
 
-// #[gpui::test]
-// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)Λ‡; }"});
+
+    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
+        let task_completion_item = completion_item.clone();
+        async move { Ok(task_completion_item) }
+    })
+    .next()
+    .await
+    .unwrap();
+    apply_additional_edits.await.unwrap();
+    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)Λ‡; }"});
+}
 
-//     let mut cx = EditorLspTestContext::new(
-//         Language::new(
-//             LanguageConfig {
-//                 path_suffixes: vec!["jsx".into()],
-//                 overrides: [(
-//                     "element".into(),
-//                     LanguageConfigOverride {
-//                         word_characters: Override::Set(['-'].into_iter().collect()),
-//                         ..Default::default()
-//                     },
-//                 )]
-//                 .into_iter()
-//                 .collect(),
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_typescript::language_tsx()),
-//         )
-//         .with_override_query("(jsx_self_closing_element) @element")
-//         .unwrap(),
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![":".to_string()]),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
+#[gpui::test]
+async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
 
-//     cx.lsp
-//         .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
-//             Ok(Some(lsp::CompletionResponse::Array(vec![
-//                 lsp::CompletionItem {
-//                     label: "bg-blue".into(),
-//                     ..Default::default()
-//                 },
-//                 lsp::CompletionItem {
-//                     label: "bg-red".into(),
-//                     ..Default::default()
-//                 },
-//                 lsp::CompletionItem {
-//                     label: "bg-yellow".into(),
-//                     ..Default::default()
-//                 },
-//             ])))
-//         });
+    let mut cx = EditorLspTestContext::new(
+        Language::new(
+            LanguageConfig {
+                path_suffixes: vec!["jsx".into()],
+                overrides: [(
+                    "element".into(),
+                    LanguageConfigOverride {
+                        word_characters: Override::Set(['-'].into_iter().collect()),
+                        ..Default::default()
+                    },
+                )]
+                .into_iter()
+                .collect(),
+                ..Default::default()
+            },
+            Some(tree_sitter_typescript::language_tsx()),
+        )
+        .with_override_query("(jsx_self_closing_element) @element")
+        .unwrap(),
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![":".to_string()]),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
 
-//     cx.set_state(r#"<p class="bgˇ" />"#);
+    cx.lsp
+        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
+            Ok(Some(lsp::CompletionResponse::Array(vec![
+                lsp::CompletionItem {
+                    label: "bg-blue".into(),
+                    ..Default::default()
+                },
+                lsp::CompletionItem {
+                    label: "bg-red".into(),
+                    ..Default::default()
+                },
+                lsp::CompletionItem {
+                    label: "bg-yellow".into(),
+                    ..Default::default()
+                },
+            ])))
+        });
 
-//     // Trigger completion when typing a dash, because the dash is an extra
-//     // word character in the 'element' scope, which contains the cursor.
-//     cx.simulate_keystroke("-");
-//     cx.executor().run_until_parked();
-//     cx.update_editor(|editor, _| {
-//         if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-//             assert_eq!(
-//                 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-//                 &["bg-red", "bg-blue", "bg-yellow"]
-//             );
-//         } else {
-//             panic!("expected completion menu to be open");
-//         }
-//     });
+    cx.set_state(r#"<p class="bgˇ" />"#);
 
-//     cx.simulate_keystroke("l");
-//     cx.executor().run_until_parked();
-//     cx.update_editor(|editor, _| {
-//         if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-//             assert_eq!(
-//                 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-//                 &["bg-blue", "bg-yellow"]
-//             );
-//         } else {
-//             panic!("expected completion menu to be open");
-//         }
-//     });
+    // Trigger completion when typing a dash, because the dash is an extra
+    // word character in the 'element' scope, which contains the cursor.
+    cx.simulate_keystroke("-");
+    cx.executor().run_until_parked();
+    cx.update_editor(|editor, _| {
+        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+            assert_eq!(
+                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+                &["bg-red", "bg-blue", "bg-yellow"]
+            );
+        } else {
+            panic!("expected completion menu to be open");
+        }
+    });
 
-//     // When filtering completions, consider the character after the '-' to
-//     // be the start of a subword.
-//     cx.set_state(r#"<p class="yelˇ" />"#);
-//     cx.simulate_keystroke("l");
-//     cx.executor().run_until_parked();
-//     cx.update_editor(|editor, _| {
-//         if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-//             assert_eq!(
-//                 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-//                 &["bg-yellow"]
-//             );
-//         } else {
-//             panic!("expected completion menu to be open");
-//         }
-//     });
-// }
+    cx.simulate_keystroke("l");
+    cx.executor().run_until_parked();
+    cx.update_editor(|editor, _| {
+        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+            assert_eq!(
+                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+                &["bg-blue", "bg-yellow"]
+            );
+        } else {
+            panic!("expected completion menu to be open");
+        }
+    });
+
+    // When filtering completions, consider the character after the '-' to
+    // be the start of a subword.
+    cx.set_state(r#"<p class="yelˇ" />"#);
+    cx.simulate_keystroke("l");
+    cx.executor().run_until_parked();
+    cx.update_editor(|editor, _| {
+        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+            assert_eq!(
+                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+                &["bg-yellow"]
+            );
+        } else {
+            panic!("expected completion menu to be open");
+        }
+    });
+}
 
 #[gpui::test]
 async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {

crates/editor2/src/element.rs πŸ”—

@@ -9,9 +9,11 @@ use crate::{
         self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
     },
     link_go_to_definition::{
-        go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
-        update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+        go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition,
+        update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+        LinkGoToDefinitionState,
     },
+    mouse_context_menu,
     scroll::scroll_amount::ScrollAmount,
     CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
     HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, OpenExcerpts, PageDown, PageUp, Point,
@@ -19,14 +21,15 @@ use crate::{
 };
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
+use git::diff::DiffHunkStatus;
 use gpui::{
-    div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
-    BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
-    ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
-    IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
-    ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
-    StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
-    ViewContext, WeakView, WindowContext, WrappedLine,
+    div, overlay, point, px, relative, size, transparent_black, Action, AnchorCorner, AnyElement,
+    AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle,
+    DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, EntityId, Hsla,
+    InteractiveBounds, InteractiveElement, IntoElement, LineLayout, ModifiersChangedEvent,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
+    ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
+    Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -48,6 +51,7 @@ use std::{
 };
 use sum_tree::Bias;
 use theme::{ActiveTheme, PlayerColor};
+use ui::prelude::*;
 use ui::{h_stack, IconButton, Tooltip};
 use util::ResultExt;
 use workspace::item::Item;
@@ -138,8 +142,6 @@ impl EditorElement {
         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);
@@ -262,7 +264,7 @@ impl EditorElement {
         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::open_excerpts);
         register_action(view, cx, Editor::toggle_soft_wrap);
         register_action(view, cx, Editor::toggle_inlay_hints);
         register_action(view, cx, hover_popover::hover);
@@ -311,7 +313,57 @@ impl EditorElement {
         register_action(view, cx, Editor::context_menu_last);
     }
 
-    fn mouse_down(
+    fn register_key_listeners(&self, cx: &mut WindowContext) {
+        cx.on_key_event({
+            let editor = self.editor.clone();
+            move |event: &ModifiersChangedEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
+                    cx.stop_propagation();
+                }
+            }
+        });
+    }
+
+    fn modifiers_changed(
+        editor: &mut Editor,
+        event: &ModifiersChangedEvent,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        let pending_selection = editor.has_pending_selection();
+
+        if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
+            if event.command && !pending_selection {
+                let point = point.clone();
+                let snapshot = editor.snapshot(cx);
+                let kind = point.definition_kind(event.shift);
+
+                show_link_definition(kind, editor, point, snapshot, cx);
+                return false;
+            }
+        }
+
+        {
+            if editor.link_go_to_definition_state.symbol_range.is_some()
+                || !editor.link_go_to_definition_state.definitions.is_empty()
+            {
+                editor.link_go_to_definition_state.symbol_range.take();
+                editor.link_go_to_definition_state.definitions.clear();
+                cx.notify();
+            }
+
+            editor.link_go_to_definition_state.task = None;
+
+            editor.clear_highlights::<LinkGoToDefinitionState>(cx);
+        }
+
+        false
+    }
+
+    fn mouse_left_down(
         editor: &mut Editor,
         event: &MouseDownEvent,
         position_map: &PositionMap,
@@ -364,25 +416,25 @@ impl EditorElement {
         true
     }
 
-    // fn mouse_right_down(
-    //     editor: &mut Editor,
-    //     position: gpui::Point<Pixels>,
-    //     position_map: &PositionMap,
-    //     text_bounds: Bounds<Pixels>,
-    //     cx: &mut EventContext<Editor>,
-    // ) -> bool {
-    //     if !text_bounds.contains_point(position) {
-    //         return false;
-    //     }
-    //     let point_for_position = position_map.point_for_position(text_bounds, position);
-    //     mouse_context_menu::deploy_context_menu(
-    //         editor,
-    //         position,
-    //         point_for_position.previous_valid,
-    //         cx,
-    //     );
-    //     true
-    // }
+    fn mouse_right_down(
+        editor: &mut Editor,
+        event: &MouseDownEvent,
+        position_map: &PositionMap,
+        text_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        if !text_bounds.contains_point(&event.position) {
+            return false;
+        }
+        let point_for_position = position_map.point_for_position(text_bounds, event.position);
+        mouse_context_menu::deploy_context_menu(
+            editor,
+            event.position,
+            point_for_position.previous_valid,
+            cx,
+        );
+        true
+    }
 
     fn mouse_up(
         editor: &mut Editor,
@@ -724,87 +776,85 @@ impl EditorElement {
     }
 
     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;
-
-        // let scroll_position = layout.position_map.snapshot.scroll_position();
-        // let scroll_top = scroll_position.y * line_height;
-
-        // for hunk in &layout.display_hunks {
-        //     let (display_row_range, status) = match hunk {
-        //         //TODO: This rendering is entirely a horrible hack
-        //         &DisplayDiffHunk::Folded { display_row: row } => {
-        //             let start_y = row as f32 * line_height - scroll_top;
-        //             let end_y = start_y + line_height;
-
-        //             let width = diff_style.removed_width_em * line_height;
-        //             let highlight_origin = bounds.origin + point(-width, start_y);
-        //             let highlight_size = point(width * 2., end_y - start_y);
-        //             let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
-        //             cx.paint_quad(Quad {
-        //                 bounds: highlight_bounds,
-        //                 background: Some(diff_style.modified),
-        //                 border: Border::new(0., Color::transparent_black()).into(),
-        //                 corner_radii: (1. * line_height).into(),
-        //             });
-
-        //             continue;
-        //         }
-
-        //         DisplayDiffHunk::Unfolded {
-        //             display_row_range,
-        //             status,
-        //         } => (display_row_range, status),
-        //     };
-
-        //     let color = match status {
-        //         DiffHunkStatus::Added => diff_style.inserted,
-        //         DiffHunkStatus::Modified => diff_style.modified,
-
-        //         //TODO: This rendering is entirely a horrible hack
-        //         DiffHunkStatus::Removed => {
-        //             let row = display_row_range.start;
-
-        //             let offset = line_height / 2.;
-        //             let start_y = row as f32 * line_height - offset - scroll_top;
-        //             let end_y = start_y + line_height;
-
-        //             let width = diff_style.removed_width_em * line_height;
-        //             let highlight_origin = bounds.origin + point(-width, start_y);
-        //             let highlight_size = point(width * 2., end_y - start_y);
-        //             let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
-        //             cx.paint_quad(Quad {
-        //                 bounds: highlight_bounds,
-        //                 background: Some(diff_style.deleted),
-        //                 border: Border::new(0., Color::transparent_black()).into(),
-        //                 corner_radii: (1. * line_height).into(),
-        //             });
-
-        //             continue;
-        //         }
-        //     };
-
-        //     let start_row = display_row_range.start;
-        //     let end_row = display_row_range.end;
-
-        //     let start_y = start_row as f32 * line_height - scroll_top;
-        //     let end_y = end_row as f32 * line_height - scroll_top;
-
-        //     let width = diff_style.width_em * line_height;
-        //     let highlight_origin = bounds.origin + point(-width, start_y);
-        //     let highlight_size = point(width * 2., end_y - start_y);
-        //     let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
-        //     cx.paint_quad(Quad {
-        //         bounds: highlight_bounds,
-        //         background: Some(color),
-        //         border: Border::new(0., Color::transparent_black()).into(),
-        //         corner_radii: (diff_style.corner_radius * line_height).into(),
-        //     });
-        // }
+        let line_height = layout.position_map.line_height;
+
+        let scroll_position = layout.position_map.snapshot.scroll_position();
+        let scroll_top = scroll_position.y * line_height;
+
+        for hunk in &layout.display_hunks {
+            let (display_row_range, status) = match hunk {
+                //TODO: This rendering is entirely a horrible hack
+                &DisplayDiffHunk::Folded { display_row: row } => {
+                    let start_y = row as f32 * line_height - scroll_top;
+                    let end_y = start_y + line_height;
+
+                    let width = 0.275 * line_height;
+                    let highlight_origin = bounds.origin + point(-width, start_y);
+                    let highlight_size = size(width * 2., end_y - start_y);
+                    let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+                    cx.paint_quad(
+                        highlight_bounds,
+                        Corners::all(1. * line_height),
+                        gpui::yellow(), // todo!("use the right color")
+                        Edges::default(),
+                        transparent_black(),
+                    );
+
+                    continue;
+                }
+
+                DisplayDiffHunk::Unfolded {
+                    display_row_range,
+                    status,
+                } => (display_row_range, status),
+            };
+
+            let color = match status {
+                DiffHunkStatus::Added => gpui::green(), // todo!("use the appropriate color")
+                DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color")
+
+                //TODO: This rendering is entirely a horrible hack
+                DiffHunkStatus::Removed => {
+                    let row = display_row_range.start;
+
+                    let offset = line_height / 2.;
+                    let start_y = row as f32 * line_height - offset - scroll_top;
+                    let end_y = start_y + line_height;
+
+                    let width = 0.275 * line_height;
+                    let highlight_origin = bounds.origin + point(-width, start_y);
+                    let highlight_size = size(width * 2., end_y - start_y);
+                    let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+                    cx.paint_quad(
+                        highlight_bounds,
+                        Corners::all(1. * line_height),
+                        gpui::red(), // todo!("use the right color")
+                        Edges::default(),
+                        transparent_black(),
+                    );
+
+                    continue;
+                }
+            };
+
+            let start_row = display_row_range.start;
+            let end_row = display_row_range.end;
+
+            let start_y = start_row as f32 * line_height - scroll_top;
+            let end_y = end_row as f32 * line_height - scroll_top;
+
+            let width = 0.275 * line_height;
+            let highlight_origin = bounds.origin + point(-width, start_y);
+            let highlight_size = size(width * 2., end_y - start_y);
+            let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+            cx.paint_quad(
+                highlight_bounds,
+                Corners::all(0.05 * line_height),
+                color, // todo!("use the right color")
+                Edges::default(),
+                transparent_black(),
+            );
+        }
     }
 
     fn paint_text(
@@ -830,15 +880,19 @@ impl EditorElement {
                 bounds: text_bounds,
             }),
             |cx| {
-                // todo!("cursor region")
-                // cx.scene().push_cursor_region(CursorRegion {
-                //     bounds,
-                //     style: if !editor.link_go_to_definition_state.definitions.is_empty {
-                //         CursorStyle::PointingHand
-                //     } else {
-                //         CursorStyle::IBeam
-                //     },
-                // });
+                if text_bounds.contains_point(&cx.mouse_position()) {
+                    if self
+                        .editor
+                        .read(cx)
+                        .link_go_to_definition_state
+                        .definitions
+                        .is_empty()
+                    {
+                        cx.set_cursor_style(CursorStyle::IBeam);
+                    } else {
+                        cx.set_cursor_style(CursorStyle::PointingHand);
+                    }
+                }
 
                 let fold_corner_radius = 0.15 * layout.position_map.line_height;
                 cx.with_element_id(Some("folds"), |cx| {
@@ -1137,6 +1191,22 @@ impl EditorElement {
                             }
                         }
                     }
+
+                    if let Some(mouse_context_menu) =
+                        self.editor.read(cx).mouse_context_menu.as_ref()
+                    {
+                        let element = overlay()
+                            .position(mouse_context_menu.position)
+                            .child(mouse_context_menu.context_menu.clone())
+                            .anchor(AnchorCorner::TopLeft)
+                            .snap_to_window();
+                        element.draw(
+                            gpui::Point::default(),
+                            size(AvailableSpace::MinContent, AvailableSpace::MinContent),
+                            cx,
+                            |_, _| {},
+                        );
+                    }
                 })
             },
         )
@@ -1661,11 +1731,6 @@ impl EditorElement {
         cx: &mut WindowContext,
     ) -> LayoutState {
         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();
 
@@ -1701,6 +1766,7 @@ impl EditorElement {
             };
 
             editor.gutter_width = gutter_width;
+
             let text_width = bounds.size.width - gutter_width;
             let overscroll = size(em_width, px(0.));
             let snapshot = {
@@ -1727,25 +1793,6 @@ impl EditorElement {
                 .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);
 
@@ -2063,7 +2110,7 @@ impl EditorElement {
                 .unwrap();
 
             LayoutState {
-                mode: editor_mode,
+                mode: snapshot.mode,
                 position_map: Arc::new(PositionMap {
                     size: bounds.size,
                     scroll_position: point(
@@ -2307,10 +2354,10 @@ impl EditorElement {
                     return;
                 }
 
-                let should_cancel = editor.update(cx, |editor, cx| {
+                let handled = editor.update(cx, |editor, cx| {
                     Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
                 });
-                if should_cancel {
+                if handled {
                     cx.stop_propagation();
                 }
             }
@@ -2326,19 +2373,25 @@ impl EditorElement {
                     return;
                 }
 
-                let should_cancel = editor.update(cx, |editor, cx| {
-                    Self::mouse_down(
-                        editor,
-                        event,
-                        &position_map,
-                        text_bounds,
-                        gutter_bounds,
-                        &stacking_order,
-                        cx,
-                    )
-                });
+                let handled = match event.button {
+                    MouseButton::Left => editor.update(cx, |editor, cx| {
+                        Self::mouse_left_down(
+                            editor,
+                            event,
+                            &position_map,
+                            text_bounds,
+                            gutter_bounds,
+                            &stacking_order,
+                            cx,
+                        )
+                    }),
+                    MouseButton::Right => editor.update(cx, |editor, cx| {
+                        Self::mouse_right_down(editor, event, &position_map, text_bounds, cx)
+                    }),
+                    _ => false,
+                };
 
-                if should_cancel {
+                if handled {
                     cx.stop_propagation()
                 }
             }
@@ -2350,7 +2403,7 @@ impl EditorElement {
             let stacking_order = cx.stacking_order().clone();
 
             move |event: &MouseUpEvent, phase, cx| {
-                let should_cancel = editor.update(cx, |editor, cx| {
+                let handled = editor.update(cx, |editor, cx| {
                     Self::mouse_up(
                         editor,
                         event,
@@ -2361,26 +2414,11 @@ impl EditorElement {
                     )
                 });
 
-                if should_cancel {
+                if handled {
                     cx.stop_propagation()
                 }
             }
         });
-        //todo!()
-        // on_down(MouseButton::Right, {
-        //     let position_map = layout.position_map.clone();
-        //     move |event, editor, cx| {
-        //         if !Self::mouse_right_down(
-        //             editor,
-        //             event.position,
-        //             position_map.as_ref(),
-        //             text_bounds,
-        //             cx,
-        //         ) {
-        //             cx.propagate_event();
-        //         }
-        //     }
-        // });
         cx.on_mouse_event({
             let position_map = layout.position_map.clone();
             let editor = self.editor.clone();
@@ -2616,19 +2654,44 @@ impl Element for EditorElement {
         cx: &mut gpui::WindowContext,
     ) -> (gpui::LayoutId, Self::State) {
         self.editor.update(cx, |editor, cx| {
-            editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
+            editor.set_style(self.style.clone(), cx);
 
-            let rem_size = cx.rem_size();
-            let mut style = Style::default();
-            style.size.width = relative(1.).into();
-            style.size.height = match editor.mode {
+            let layout_id = match editor.mode {
                 EditorMode::SingleLine => {
-                    self.style.text.line_height_in_pixels(cx.rem_size()).into()
+                    let rem_size = cx.rem_size();
+                    let mut style = Style::default();
+                    style.size.width = relative(1.).into();
+                    style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
+                    cx.request_layout(&style, None)
+                }
+                EditorMode::AutoHeight { max_lines } => {
+                    let editor_handle = cx.view().clone();
+                    let max_line_number_width =
+                        self.max_line_number_width(&editor.snapshot(cx), cx);
+                    cx.request_measured_layout(
+                        Style::default(),
+                        move |known_dimensions, available_space, cx| {
+                            editor_handle
+                                .update(cx, |editor, cx| {
+                                    compute_auto_height_layout(
+                                        editor,
+                                        max_lines,
+                                        max_line_number_width,
+                                        known_dimensions,
+                                        cx,
+                                    )
+                                })
+                                .unwrap_or_default()
+                        },
+                    )
+                }
+                EditorMode::Full => {
+                    let mut style = Style::default();
+                    style.size.width = relative(1.).into();
+                    style.size.height = relative(1.).into();
+                    cx.request_layout(&style, None)
                 }
-                EditorMode::AutoHeight { .. } => todo!(),
-                EditorMode::Full => relative(1.).into(),
             };
-            let layout_id = cx.request_layout(&style, None);
 
             (layout_id, ())
         })
@@ -2656,6 +2719,7 @@ impl Element for EditorElement {
         let dispatch_context = self.editor.read(cx).dispatch_context(cx);
         cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
             self.register_actions(cx);
+            self.register_key_listeners(cx);
 
             // We call with_z_index to establish a new stacking context.
             cx.with_z_index(0, |cx| {
@@ -2697,604 +2761,6 @@ impl IntoElement for EditorElement {
     }
 }
 
-// impl EditorElement {
-//     type LayoutState = LayoutState;
-//     type PaintState = ();
-
-//     fn layout(
-//         &mut self,
-//         constraint: SizeConstraint,
-//         editor: &mut Editor,
-//         cx: &mut ViewContext<Editor>,
-//     ) -> (gpui::Point<Pixels>, Self::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();
-
-//         let line_height = (style.text.font_size * style.line_height_scalar).round();
-
-//         let gutter_padding;
-//         let gutter_width;
-//         let gutter_margin;
-//         if snapshot.show_gutter {
-//             let em_width = style.text.em_width(cx.font_cache());
-//             gutter_padding = (em_width * style.gutter_padding_factor).round();
-//             gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
-//             gutter_margin = -style.text.descent(cx.font_cache());
-//         } else {
-//             gutter_padding = 0.0;
-//             gutter_width = 0.0;
-//             gutter_margin = 0.0;
-//         };
-
-//         let text_width = size.x - gutter_width;
-//         let em_width = style.text.em_width(cx.font_cache());
-//         let em_advance = style.text.em_advance(cx.font_cache());
-//         let overscroll = point(em_width, 0.);
-//         let snapshot = {
-//             editor.set_visible_line_count(size.y / line_height, cx);
-
-//             let editor_width = text_width - gutter_margin - overscroll.x - 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
-//             }
-//         };
-
-//         let wrap_guides = editor
-//             .wrap_guides(cx)
-//             .iter()
-//             .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
-//             .collect();
-
-//         let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
-//         if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
-//             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 = snapshot.mode {
-//             size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
-//         } else if size.y.is_infinite() {
-//             size.set_y(scroll_height);
-//         }
-//         let gutter_size = point(gutter_width, size.y);
-//         let text_size = point(text_width, size.y);
-
-//         let autoscroll_horizontally = editor.autoscroll_vertically(size.y, 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 = size.y / 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<(SelectionStyle, Vec<SelectionLayout>)> = Vec::new();
-//         let mut active_rows = BTreeMap::new();
-//         let mut fold_ranges = Vec::new();
-//         let is_singleton = editor.is_singleton(cx);
-
-//         let highlighted_rows = editor.highlighted_rows();
-//         let theme = theme::current(cx);
-//         let highlighted_ranges = editor.background_highlights_in_range(
-//             start_anchor..end_anchor,
-//             &snapshot.display_snapshot,
-//             theme.as_ref(),
-//         );
-
-//         fold_ranges.extend(
-//             snapshot
-//                 .folds_in_range(start_anchor..end_anchor)
-//                 .map(|anchor| {
-//                     let start = anchor.start.to_point(&snapshot.buffer_snapshot);
-//                     (
-//                         start.row,
-//                         start.to_display_point(&snapshot.display_snapshot)
-//                             ..anchor.end.to_display_point(&snapshot),
-//                     )
-//                 }),
-//         );
-
-//         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);
-//             }
-
-//             selections.push((style.selection, 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 =
-//                                 style.selection_style_for_room_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 {
-//                     style.selection_style_for_room_participant(participant_index.0)
-//                 } else {
-//                     style.absent_selection
-//                 };
-
-//                 // Don't re-render the leader's selections, since the local selections
-//                 // match theirs.
-//                 if Some(selection.peer_id) == editor.leader_peer_id {
-//                     continue;
-//                 }
-
-//                 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,
-//                     ));
-//             }
-
-//             selections.extend(remote_selections.into_values());
-//         }
-
-//         let scrollbar_settings = &settings::get::<EditorSettings>(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 fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)> = fold_ranges
-//             .into_iter()
-//             .map(|(id, fold)| {
-//                 let color = self
-//                     .style
-//                     .folds
-//                     .ellipses
-//                     .background
-//                     .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
-//                     .color;
-
-//                 (id, fold, color)
-//             })
-//             .collect();
-
-//         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_number_layouts, fold_statuses) = self.layout_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 = 0.0;
-//         let line_layouts =
-//             self.layout_lines(start_row..end_row, &line_number_layouts, &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 style = self.style.clone();
-//         let longest_line_width = layout_line(
-//             snapshot.longest_row(),
-//             &snapshot,
-//             &style,
-//             cx.text_layout_cache(),
-//         )
-//         .width();
-//         let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x;
-//         let em_width = style.text.em_width(cx.font_cache());
-//         let (scroll_width, blocks) = self.layout_blocks(
-//             start_row..end_row,
-//             &snapshot,
-//             size.x,
-//             scroll_width,
-//             gutter_padding,
-//             gutter_width,
-//             em_width,
-//             gutter_width + gutter_margin,
-//             line_height,
-//             &style,
-//             &line_layouts,
-//             editor,
-//             cx,
-//         );
-
-//         let scroll_max = point(
-//             ((scroll_width - text_size.x) / em_width).max(0.0),
-//             max_row as f32,
-//         );
-
-//         let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
-//         let autoscrolled = if autoscroll_horizontally {
-//             editor.autoscroll_horizontally(
-//                 start_row,
-//                 text_size.x,
-//                 scroll_width,
-//                 em_width,
-//                 &line_layouts,
-//                 cx,
-//             )
-//         } else {
-//             false
-//         };
-
-//         if clamped || autoscrolled {
-//             snapshot = editor.snapshot(cx);
-//         }
-
-//         let style = editor.style(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, style.clone(), cx);
-//                 }
-
-//                 let active = matches!(
-//                     editor.context_menu.read().as_ref(),
-//                     Some(crate::ContextMenu::CodeActions(_))
-//                 );
-
-//                 code_actions_indicator = editor
-//                     .render_code_actions_indicator(&style, active, cx)
-//                     .map(|indicator| (newest_selection_head.row(), indicator));
-//             }
-//         }
-
-//         let visible_rows = start_row..start_row + line_layouts.len() as u32;
-//         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 = editor.render_fold_indicators(
-//             fold_statuses,
-//             &style,
-//             editor.gutter_hovered,
-//             line_height,
-//             gutter_margin,
-//             cx,
-//         );
-
-//         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,
-//             );
-//         }
-
-//         if let Some((_, indicator)) = code_actions_indicator.as_mut() {
-//             indicator.layout(
-//                 SizeConstraint::strict_along(
-//                     Axis::Vertical,
-//                     line_height * style.code_actions.vertical_scale,
-//                 ),
-//                 editor,
-//                 cx,
-//             );
-//         }
-
-//         for fold_indicator in fold_indicators.iter_mut() {
-//             if let Some(indicator) = fold_indicator.as_mut() {
-//                 indicator.layout(
-//                     SizeConstraint::strict_along(
-//                         Axis::Vertical,
-//                         line_height * style.code_actions.vertical_scale,
-//                     ),
-//                     editor,
-//                     cx,
-//                 );
-//             }
-//         }
-
-//         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 = self.style.text.font_size / 2.0;
-//         let invisible_symbol_style = RunStyle {
-//             color: self.style.whitespace,
-//             font_id: self.style.text.font_id,
-//             underline: Default::default(),
-//         };
-
-//         (
-//             size,
-//             LayoutState {
-//                 mode,
-//                 position_map: Arc::new(PositionMap {
-//                     size,
-//                     scroll_max,
-//                     line_layouts,
-//                     line_height,
-//                     em_width,
-//                     em_advance,
-//                     snapshot,
-//                 }),
-//                 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,
-//                 fold_ranges,
-//                 line_number_layouts,
-//                 display_hunks,
-//                 blocks,
-//                 selections,
-//                 context_menu,
-//                 code_actions_indicator,
-//                 fold_indicators,
-//                 tab_invisible: cx.text_layout_cache().layout_str(
-//                     "β†’",
-//                     invisible_symbol_font_size,
-//                     &[("β†’".len(), invisible_symbol_style)],
-//                 ),
-//                 space_invisible: cx.text_layout_cache().layout_str(
-//                     "β€’",
-//                     invisible_symbol_font_size,
-//                     &[("β€’".len(), invisible_symbol_style)],
-//                 ),
-//                 hover_popovers: hover,
-//             },
-//         )
-//     }
-
-//     fn paint(
-//         &mut self,
-//         bounds: Bounds<Pixels>,
-//         visible_bounds: Bounds<Pixels>,
-//         layout: &mut Self::LayoutState,
-//         editor: &mut Editor,
-//         cx: &mut ViewContext<Editor>,
-//     ) -> Self::PaintState {
-//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-//         cx.scene().push_layer(Some(visible_bounds));
-
-//         let gutter_bounds = Bounds::<Pixels>::new(bounds.origin, layout.gutter_size);
-//         let text_bounds = Bounds::<Pixels>::new(
-//             bounds.origin + point(layout.gutter_size.x, 0.0),
-//             layout.text_size,
-//         );
-
-//         Self::attach_mouse_handlers(
-//             &layout.position_map,
-//             layout.hover_popovers.is_some(),
-//             visible_bounds,
-//             text_bounds,
-//             gutter_bounds,
-//             bounds,
-//             cx,
-//         );
-
-//         self.paint_background(gutter_bounds, text_bounds, layout, cx);
-//         if layout.gutter_size.x > 0. {
-//             self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
-//         }
-//         self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
-
-//         cx.scene().push_layer(Some(bounds));
-//         if !layout.blocks.is_empty {
-//             self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
-//         }
-//         self.paint_scrollbar(bounds, layout, &editor, cx);
-//         cx.scene().pop_layer();
-//         cx.scene().pop_layer();
-//     }
-
-//     fn rect_for_text_range(
-//         &self,
-//         range_utf16: Range<usize>,
-//         bounds: Bounds<Pixels>,
-//         _: Bounds<Pixels>,
-//         layout: &Self::LayoutState,
-//         _: &Self::PaintState,
-//         _: &Editor,
-//         _: &ViewContext<Editor>,
-//     ) -> Option<Bounds<Pixels>> {
-//         let text_bounds = Bounds::<Pixels>::new(
-//             bounds.origin + point(layout.gutter_size.x, 0.0),
-//             layout.text_size,
-//         );
-//         let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.);
-//         let scroll_position = layout.position_map.snapshot.scroll_position();
-//         let start_row = scroll_position.y as u32;
-//         let scroll_top = scroll_position.y * layout.position_map.line_height;
-//         let scroll_left = scroll_position.x * layout.position_map.em_width;
-
-//         let range_start = OffsetUtf16(range_utf16.start)
-//             .to_display_point(&layout.position_map.snapshot.display_snapshot);
-//         if range_start.row() < start_row {
-//             return None;
-//         }
-
-//         let line = &layout
-//             .position_map
-//             .line_layouts
-//             .get((range_start.row() - start_row) as usize)?
-//             .line;
-//         let range_start_x = line.x_for_index(range_start.column() as usize);
-//         let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
-//         Some(Bounds::<Pixels>::new(
-//             content_origin
-//                 + point(
-//                     range_start_x,
-//                     range_start_y + layout.position_map.line_height,
-//                 )
-//                 - point(scroll_left, scroll_top),
-//             point(
-//                 layout.position_map.em_width,
-//                 layout.position_map.line_height,
-//             ),
-//         ))
-//     }
-
-//     fn debug(
-//         &self,
-//         bounds: Bounds<Pixels>,
-//         _: &Self::LayoutState,
-//         _: &Self::PaintState,
-//         _: &Editor,
-//         _: &ViewContext<Editor>,
-//     ) -> json::Value {
-//         json!({
-//             "type": "BufferElement",
-//             "bounds": bounds.to_json()
-//         })
-//     }
-// }
-
 type BufferRow = u32;
 
 pub struct LayoutState {

crates/editor2/src/hover_popover.rs πŸ”—

@@ -15,7 +15,7 @@ use lsp::DiagnosticSeverity;
 use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
 use settings::Settings;
 use std::{ops::Range, sync::Arc, time::Duration};
-use ui::Tooltip;
+use ui::{StyledExt, Tooltip};
 use util::TryFutureExt;
 use workspace::Workspace;
 
@@ -476,8 +476,10 @@ impl InfoPopover {
     ) -> AnyElement {
         div()
             .id("info_popover")
+            .elevation_2(cx)
+            .text_ui()
+            .p_2()
             .overflow_y_scroll()
-            .bg(gpui::red())
             .max_w(max_size.width)
             .max_h(max_size.height)
             // Prevent a mouse move on the popover from being propagated to the editor,

crates/editor2/src/items.rs πŸ”—

@@ -4,13 +4,14 @@ use crate::{
     EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
     NavigationData, ToPoint as _,
 };
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use collections::HashSet;
 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, WindowContext,
+    div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, Div, Entity,
+    EntityId, EventEmitter, FocusHandle, IntoElement, Model, ParentElement, Pixels, Render,
+    SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+    WindowContext,
 };
 use language::{
     proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -20,6 +21,7 @@ use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPat
 use rpc::proto::{self, update_view, PeerId};
 use settings::Settings;
 use smallvec::SmallVec;
+use std::fmt::Write;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -31,8 +33,11 @@ use std::{
 use text::Selection;
 use theme::{ActiveTheme, Theme};
 use ui::{Color, Label};
-use util::{paths::PathExt, ResultExt, TryFutureExt};
-use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
+use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use workspace::{
+    item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
+    StatusItemView,
+};
 use workspace::{
     item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
     searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -71,110 +76,108 @@ impl FollowableItem for Editor {
         workspace: View<Workspace>,
         remote_id: ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<Task<Result<View<Self>>>> {
-        todo!()
+        let project = workspace.read(cx).project().to_owned();
+        let Some(proto::view::Variant::Editor(_)) = state else {
+            return None;
+        };
+        let Some(proto::view::Variant::Editor(state)) = state.take() else {
+            unreachable!()
+        };
+
+        let client = project.read(cx).client();
+        let replica_id = project.read(cx).replica_id();
+        let buffer_ids = state
+            .excerpts
+            .iter()
+            .map(|excerpt| excerpt.buffer_id)
+            .collect::<HashSet<_>>();
+        let buffers = project.update(cx, |project, cx| {
+            buffer_ids
+                .iter()
+                .map(|id| project.open_buffer_by_id(*id, cx))
+                .collect::<Vec<_>>()
+        });
+
+        let pane = pane.downgrade();
+        Some(cx.spawn(|mut cx| async move {
+            let mut buffers = futures::future::try_join_all(buffers).await?;
+            let editor = pane.update(&mut cx, |pane, cx| {
+                let mut editors = pane.items_of_type::<Self>();
+                editors.find(|editor| {
+                    let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
+                    let singleton_buffer_matches = state.singleton
+                        && buffers.first()
+                            == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
+                    ids_match || singleton_buffer_matches
+                })
+            })?;
+
+            let editor = if let Some(editor) = editor {
+                editor
+            } else {
+                pane.update(&mut cx, |_, cx| {
+                    let multibuffer = cx.build_model(|cx| {
+                        let mut multibuffer;
+                        if state.singleton && buffers.len() == 1 {
+                            multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
+                        } else {
+                            multibuffer = MultiBuffer::new(replica_id);
+                            let mut excerpts = state.excerpts.into_iter().peekable();
+                            while let Some(excerpt) = excerpts.peek() {
+                                let buffer_id = excerpt.buffer_id;
+                                let buffer_excerpts = iter::from_fn(|| {
+                                    let excerpt = excerpts.peek()?;
+                                    (excerpt.buffer_id == buffer_id)
+                                        .then(|| excerpts.next().unwrap())
+                                });
+                                let buffer =
+                                    buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
+                                if let Some(buffer) = buffer {
+                                    multibuffer.push_excerpts(
+                                        buffer.clone(),
+                                        buffer_excerpts.filter_map(deserialize_excerpt_range),
+                                        cx,
+                                    );
+                                }
+                            }
+                        };
+
+                        if let Some(title) = &state.title {
+                            multibuffer = multibuffer.with_title(title.clone())
+                        }
+
+                        multibuffer
+                    });
+
+                    cx.build_view(|cx| {
+                        let mut editor =
+                            Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
+                        editor.remote_id = Some(remote_id);
+                        editor
+                    })
+                })?
+            };
+
+            update_editor_from_message(
+                editor.downgrade(),
+                project,
+                proto::update_view::Editor {
+                    selections: state.selections,
+                    pending_selection: state.pending_selection,
+                    scroll_top_anchor: state.scroll_top_anchor,
+                    scroll_x: state.scroll_x,
+                    scroll_y: state.scroll_y,
+                    ..Default::default()
+                },
+                &mut cx,
+            )
+            .await?;
+
+            Ok(editor)
+        }))
     }
-    //     let project = workspace.read(cx).project().to_owned();
-    //     let Some(proto::view::Variant::Editor(_)) = state else {
-    //         return None;
-    //     };
-    //     let Some(proto::view::Variant::Editor(state)) = state.take() else {
-    //         unreachable!()
-    //     };
-
-    //     let client = project.read(cx).client();
-    //     let replica_id = project.read(cx).replica_id();
-    //     let buffer_ids = state
-    //         .excerpts
-    //         .iter()
-    //         .map(|excerpt| excerpt.buffer_id)
-    //         .collect::<HashSet<_>>();
-    //     let buffers = project.update(cx, |project, cx| {
-    //         buffer_ids
-    //             .iter()
-    //             .map(|id| project.open_buffer_by_id(*id, cx))
-    //             .collect::<Vec<_>>()
-    //     });
-
-    //     let pane = pane.downgrade();
-    //     Some(cx.spawn(|mut cx| async move {
-    //         let mut buffers = futures::future::try_join_all(buffers).await?;
-    //         let editor = pane.read_with(&cx, |pane, cx| {
-    //             let mut editors = pane.items_of_type::<Self>();
-    //             editors.find(|editor| {
-    //                 let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
-    //                 let singleton_buffer_matches = state.singleton
-    //                     && buffers.first()
-    //                         == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
-    //                 ids_match || singleton_buffer_matches
-    //             })
-    //         })?;
-
-    //         let editor = if let Some(editor) = editor {
-    //             editor
-    //         } else {
-    //             pane.update(&mut cx, |_, cx| {
-    //                 let multibuffer = cx.add_model(|cx| {
-    //                     let mut multibuffer;
-    //                     if state.singleton && buffers.len() == 1 {
-    //                         multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
-    //                     } else {
-    //                         multibuffer = MultiBuffer::new(replica_id);
-    //                         let mut excerpts = state.excerpts.into_iter().peekable();
-    //                         while let Some(excerpt) = excerpts.peek() {
-    //                             let buffer_id = excerpt.buffer_id;
-    //                             let buffer_excerpts = iter::from_fn(|| {
-    //                                 let excerpt = excerpts.peek()?;
-    //                                 (excerpt.buffer_id == buffer_id)
-    //                                     .then(|| excerpts.next().unwrap())
-    //                             });
-    //                             let buffer =
-    //                                 buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
-    //                             if let Some(buffer) = buffer {
-    //                                 multibuffer.push_excerpts(
-    //                                     buffer.clone(),
-    //                                     buffer_excerpts.filter_map(deserialize_excerpt_range),
-    //                                     cx,
-    //                                 );
-    //                             }
-    //                         }
-    //                     };
-
-    //                     if let Some(title) = &state.title {
-    //                         multibuffer = multibuffer.with_title(title.clone())
-    //                     }
-
-    //                     multibuffer
-    //                 });
-
-    //                 cx.add_view(|cx| {
-    //                     let mut editor =
-    //                         Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
-    //                     editor.remote_id = Some(remote_id);
-    //                     editor
-    //                 })
-    //             })?
-    //         };
-
-    //         update_editor_from_message(
-    //             editor.downgrade(),
-    //             project,
-    //             proto::update_view::Editor {
-    //                 selections: state.selections,
-    //                 pending_selection: state.pending_selection,
-    //                 scroll_top_anchor: state.scroll_top_anchor,
-    //                 scroll_x: state.scroll_x,
-    //                 scroll_y: state.scroll_y,
-    //                 ..Default::default()
-    //             },
-    //             &mut cx,
-    //         )
-    //         .await?;
-
-    //         Ok(editor)
-    //     }))
-    // }
 
     fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
         self.leader_peer_id = leader_peer_id;
@@ -195,7 +198,7 @@ impl FollowableItem for Editor {
         cx.notify();
     }
 
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
         let buffer = self.buffer.read(cx);
         let scroll_anchor = self.scroll_manager.anchor();
         let excerpts = buffer
@@ -242,7 +245,7 @@ impl FollowableItem for Editor {
         &self,
         event: &Self::FollowableEvent,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool {
         let update =
             update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
@@ -315,7 +318,7 @@ impl FollowableItem for Editor {
         })
     }
 
-    fn is_project_item(&self, _cx: &AppContext) -> bool {
+    fn is_project_item(&self, _cx: &WindowContext) -> bool {
         true
     }
 }
@@ -324,132 +327,129 @@ async fn update_editor_from_message(
     this: WeakView<Editor>,
     project: Model<Project>,
     message: proto::update_view::Editor,
-    cx: &mut AsyncAppContext,
+    cx: &mut AsyncWindowContext,
 ) -> Result<()> {
-    todo!()
+    // Open all of the buffers of which excerpts were added to the editor.
+    let inserted_excerpt_buffer_ids = message
+        .inserted_excerpts
+        .iter()
+        .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
+        .collect::<HashSet<_>>();
+    let inserted_excerpt_buffers = project.update(cx, |project, cx| {
+        inserted_excerpt_buffer_ids
+            .into_iter()
+            .map(|id| project.open_buffer_by_id(id, cx))
+            .collect::<Vec<_>>()
+    })?;
+    let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
+
+    // Update the editor's excerpts.
+    this.update(cx, |editor, cx| {
+        editor.buffer.update(cx, |multibuffer, cx| {
+            let mut removed_excerpt_ids = message
+                .deleted_excerpts
+                .into_iter()
+                .map(ExcerptId::from_proto)
+                .collect::<Vec<_>>();
+            removed_excerpt_ids.sort_by({
+                let multibuffer = multibuffer.read(cx);
+                move |a, b| a.cmp(&b, &multibuffer)
+            });
+
+            let mut insertions = message.inserted_excerpts.into_iter().peekable();
+            while let Some(insertion) = insertions.next() {
+                let Some(excerpt) = insertion.excerpt else {
+                    continue;
+                };
+                let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
+                    continue;
+                };
+                let buffer_id = excerpt.buffer_id;
+                let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
+                    continue;
+                };
+
+                let adjacent_excerpts = iter::from_fn(|| {
+                    let insertion = insertions.peek()?;
+                    if insertion.previous_excerpt_id.is_none()
+                        && insertion.excerpt.as_ref()?.buffer_id == buffer_id
+                    {
+                        insertions.next()?.excerpt
+                    } else {
+                        None
+                    }
+                });
+
+                multibuffer.insert_excerpts_with_ids_after(
+                    ExcerptId::from_proto(previous_excerpt_id),
+                    buffer,
+                    [excerpt]
+                        .into_iter()
+                        .chain(adjacent_excerpts)
+                        .filter_map(|excerpt| {
+                            Some((
+                                ExcerptId::from_proto(excerpt.id),
+                                deserialize_excerpt_range(excerpt)?,
+                            ))
+                        }),
+                    cx,
+                );
+            }
+
+            multibuffer.remove_excerpts(removed_excerpt_ids, cx);
+        });
+    })?;
+
+    // Deserialize the editor state.
+    let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
+        let buffer = editor.buffer.read(cx).read(cx);
+        let selections = message
+            .selections
+            .into_iter()
+            .filter_map(|selection| deserialize_selection(&buffer, selection))
+            .collect::<Vec<_>>();
+        let pending_selection = message
+            .pending_selection
+            .and_then(|selection| deserialize_selection(&buffer, selection));
+        let scroll_top_anchor = message
+            .scroll_top_anchor
+            .and_then(|anchor| deserialize_anchor(&buffer, anchor));
+        anyhow::Ok((selections, pending_selection, scroll_top_anchor))
+    })??;
+
+    // Wait until the buffer has received all of the operations referenced by
+    // the editor's new state.
+    this.update(cx, |editor, cx| {
+        editor.buffer.update(cx, |buffer, cx| {
+            buffer.wait_for_anchors(
+                selections
+                    .iter()
+                    .chain(pending_selection.as_ref())
+                    .flat_map(|selection| [selection.start, selection.end])
+                    .chain(scroll_top_anchor),
+                cx,
+            )
+        })
+    })?
+    .await?;
+
+    // Update the editor's state.
+    this.update(cx, |editor, cx| {
+        if !selections.is_empty() || pending_selection.is_some() {
+            editor.set_selections_from_remote(selections, pending_selection, cx);
+            editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
+        } else if let Some(scroll_top_anchor) = scroll_top_anchor {
+            editor.set_scroll_anchor_remote(
+                ScrollAnchor {
+                    anchor: scroll_top_anchor,
+                    offset: point(message.scroll_x, message.scroll_y),
+                },
+                cx,
+            );
+        }
+    })?;
+    Ok(())
 }
-// Previous implementation of the above
-//     // Open all of the buffers of which excerpts were added to the editor.
-//     let inserted_excerpt_buffer_ids = message
-//         .inserted_excerpts
-//         .iter()
-//         .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
-//         .collect::<HashSet<_>>();
-//     let inserted_excerpt_buffers = project.update(cx, |project, cx| {
-//         inserted_excerpt_buffer_ids
-//             .into_iter()
-//             .map(|id| project.open_buffer_by_id(id, cx))
-//             .collect::<Vec<_>>()
-//     })?;
-//     let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
-
-//     // Update the editor's excerpts.
-//     this.update(cx, |editor, cx| {
-//         editor.buffer.update(cx, |multibuffer, cx| {
-//             let mut removed_excerpt_ids = message
-//                 .deleted_excerpts
-//                 .into_iter()
-//                 .map(ExcerptId::from_proto)
-//                 .collect::<Vec<_>>();
-//             removed_excerpt_ids.sort_by({
-//                 let multibuffer = multibuffer.read(cx);
-//                 move |a, b| a.cmp(&b, &multibuffer)
-//             });
-
-//             let mut insertions = message.inserted_excerpts.into_iter().peekable();
-//             while let Some(insertion) = insertions.next() {
-//                 let Some(excerpt) = insertion.excerpt else {
-//                     continue;
-//                 };
-//                 let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
-//                     continue;
-//                 };
-//                 let buffer_id = excerpt.buffer_id;
-//                 let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
-//                     continue;
-//                 };
-
-//                 let adjacent_excerpts = iter::from_fn(|| {
-//                     let insertion = insertions.peek()?;
-//                     if insertion.previous_excerpt_id.is_none()
-//                         && insertion.excerpt.as_ref()?.buffer_id == buffer_id
-//                     {
-//                         insertions.next()?.excerpt
-//                     } else {
-//                         None
-//                     }
-//                 });
-
-//                 multibuffer.insert_excerpts_with_ids_after(
-//                     ExcerptId::from_proto(previous_excerpt_id),
-//                     buffer,
-//                     [excerpt]
-//                         .into_iter()
-//                         .chain(adjacent_excerpts)
-//                         .filter_map(|excerpt| {
-//                             Some((
-//                                 ExcerptId::from_proto(excerpt.id),
-//                                 deserialize_excerpt_range(excerpt)?,
-//                             ))
-//                         }),
-//                     cx,
-//                 );
-//             }
-
-//             multibuffer.remove_excerpts(removed_excerpt_ids, cx);
-//         });
-//     })?;
-
-//     // Deserialize the editor state.
-//     let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
-//         let buffer = editor.buffer.read(cx).read(cx);
-//         let selections = message
-//             .selections
-//             .into_iter()
-//             .filter_map(|selection| deserialize_selection(&buffer, selection))
-//             .collect::<Vec<_>>();
-//         let pending_selection = message
-//             .pending_selection
-//             .and_then(|selection| deserialize_selection(&buffer, selection));
-//         let scroll_top_anchor = message
-//             .scroll_top_anchor
-//             .and_then(|anchor| deserialize_anchor(&buffer, anchor));
-//         anyhow::Ok((selections, pending_selection, scroll_top_anchor))
-//     })??;
-
-//     // Wait until the buffer has received all of the operations referenced by
-//     // the editor's new state.
-//     this.update(cx, |editor, cx| {
-//         editor.buffer.update(cx, |buffer, cx| {
-//             buffer.wait_for_anchors(
-//                 selections
-//                     .iter()
-//                     .chain(pending_selection.as_ref())
-//                     .flat_map(|selection| [selection.start, selection.end])
-//                     .chain(scroll_top_anchor),
-//                 cx,
-//             )
-//         })
-//     })?
-//     .await?;
-
-//     // Update the editor's state.
-//     this.update(cx, |editor, cx| {
-//         if !selections.is_empty() || pending_selection.is_some() {
-//             editor.set_selections_from_remote(selections, pending_selection, cx);
-//             editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
-//         } else if let Some(scroll_top_anchor) = scroll_top_anchor {
-//             editor.set_scroll_anchor_remote(
-//                 ScrollAnchor {
-//                     anchor: scroll_top_anchor,
-//                     offset: point(message.scroll_x, message.scroll_y),
-//                 },
-//                 cx,
-//             );
-//         }
-//     })?;
-//     Ok(())
-// }
 
 fn serialize_excerpt(
     buffer_id: u64,
@@ -529,39 +529,38 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
 
 impl Item for Editor {
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
-        todo!();
-        // if let Ok(data) = data.downcast::<NavigationData>() {
-        //     let newest_selection = self.selections.newest::<Point>(cx);
-        //     let buffer = self.buffer.read(cx).read(cx);
-        //     let offset = if buffer.can_resolve(&data.cursor_anchor) {
-        //         data.cursor_anchor.to_point(&buffer)
-        //     } else {
-        //         buffer.clip_point(data.cursor_position, Bias::Left)
-        //     };
-
-        //     let mut scroll_anchor = data.scroll_anchor;
-        //     if !buffer.can_resolve(&scroll_anchor.anchor) {
-        //         scroll_anchor.anchor = buffer.anchor_before(
-        //             buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
-        //         );
-        //     }
-
-        //     drop(buffer);
-
-        //     if newest_selection.head() == offset {
-        //         false
-        //     } else {
-        //         let nav_history = self.nav_history.take();
-        //         self.set_scroll_anchor(scroll_anchor, cx);
-        //         self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-        //             s.select_ranges([offset..offset])
-        //         });
-        //         self.nav_history = nav_history;
-        //         true
-        //     }
-        // } else {
-        //     false
-        // }
+        if let Ok(data) = data.downcast::<NavigationData>() {
+            let newest_selection = self.selections.newest::<Point>(cx);
+            let buffer = self.buffer.read(cx).read(cx);
+            let offset = if buffer.can_resolve(&data.cursor_anchor) {
+                data.cursor_anchor.to_point(&buffer)
+            } else {
+                buffer.clip_point(data.cursor_position, Bias::Left)
+            };
+
+            let mut scroll_anchor = data.scroll_anchor;
+            if !buffer.can_resolve(&scroll_anchor.anchor) {
+                scroll_anchor.anchor = buffer.anchor_before(
+                    buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
+                );
+            }
+
+            drop(buffer);
+
+            if newest_selection.head() == offset {
+                false
+            } else {
+                let nav_history = self.nav_history.take();
+                self.set_scroll_anchor(scroll_anchor, cx);
+                self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.select_ranges([offset..offset])
+                });
+                self.nav_history = nav_history;
+                true
+            }
+        } else {
+            false
+        }
     }
 
     fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
@@ -765,35 +764,34 @@ impl Item for Editor {
     }
 
     fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        todo!();
-        // let cursor = self.selections.newest_anchor().head();
-        // let multibuffer = &self.buffer().read(cx);
-        // let (buffer_id, symbols) =
-        //     multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
-        // let buffer = multibuffer.buffer(buffer_id)?;
-
-        // let buffer = buffer.read(cx);
-        // let filename = buffer
-        //     .snapshot()
-        //     .resolve_file_path(
-        //         cx,
-        //         self.project
-        //             .as_ref()
-        //             .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
-        //             .unwrap_or_default(),
-        //     )
-        //     .map(|path| path.to_string_lossy().to_string())
-        //     .unwrap_or_else(|| "untitled".to_string());
-
-        // let mut breadcrumbs = vec![BreadcrumbText {
-        //     text: filename,
-        //     highlights: None,
-        // }];
-        // breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
-        //     text: symbol.text,
-        //     highlights: Some(symbol.highlight_ranges),
-        // }));
-        // Some(breadcrumbs)
+        let cursor = self.selections.newest_anchor().head();
+        let multibuffer = &self.buffer().read(cx);
+        let (buffer_id, symbols) =
+            multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?;
+        let buffer = multibuffer.buffer(buffer_id)?;
+
+        let buffer = buffer.read(cx);
+        let filename = buffer
+            .snapshot()
+            .resolve_file_path(
+                cx,
+                self.project
+                    .as_ref()
+                    .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
+                    .unwrap_or_default(),
+            )
+            .map(|path| path.to_string_lossy().to_string())
+            .unwrap_or_else(|| "untitled".to_string());
+
+        let mut breadcrumbs = vec![BreadcrumbText {
+            text: filename,
+            highlights: None,
+        }];
+        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
+            text: symbol.text,
+            highlights: Some(symbol.highlight_ranges),
+        }));
+        Some(breadcrumbs)
     }
 
     fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
@@ -1120,86 +1118,78 @@ pub struct CursorPosition {
     _observe_active_editor: Option<Subscription>,
 }
 
-// impl Default for CursorPosition {
-//     fn default() -> Self {
-//         Self::new()
-//     }
-// }
-
-// impl CursorPosition {
-//     pub fn new() -> Self {
-//         Self {
-//             position: None,
-//             selected_count: 0,
-//             _observe_active_editor: None,
-//         }
-//     }
-
-//     fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-//         let editor = editor.read(cx);
-//         let buffer = editor.buffer().read(cx).snapshot(cx);
-
-//         self.selected_count = 0;
-//         let mut last_selection: Option<Selection<usize>> = None;
-//         for selection in editor.selections.all::<usize>(cx) {
-//             self.selected_count += selection.end - selection.start;
-//             if last_selection
-//                 .as_ref()
-//                 .map_or(true, |last_selection| selection.id > last_selection.id)
-//             {
-//                 last_selection = Some(selection);
-//             }
-//         }
-//         self.position = last_selection.map(|s| s.head().to_point(&buffer));
-
-//         cx.notify();
-//     }
-// }
-
-// impl Entity for CursorPosition {
-//     type Event = ();
-// }
-
-// impl View for CursorPosition {
-//     fn ui_name() -> &'static str {
-//         "CursorPosition"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         if let Some(position) = self.position {
-//             let theme = &theme::current(cx).workspace.status_bar;
-//             let mut text = format!(
-//                 "{}{FILE_ROW_COLUMN_DELIMITER}{}",
-//                 position.row + 1,
-//                 position.column + 1
-//             );
-//             if self.selected_count > 0 {
-//                 write!(text, " ({} selected)", self.selected_count).unwrap();
-//             }
-//             Label::new(text, theme.cursor_position.clone()).into_any()
-//         } else {
-//             Empty::new().into_any()
-//         }
-//     }
-// }
-
-// impl StatusItemView for CursorPosition {
-//     fn set_active_pane_item(
-//         &mut self,
-//         active_pane_item: Option<&dyn ItemHandle>,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
-//             self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
-//             self.update_position(editor, cx);
-//         } else {
-//             self.position = None;
-//             self._observe_active_editor = None;
-//         }
-
-//         cx.notify();
-//     }
-// }
+impl Default for CursorPosition {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl CursorPosition {
+    pub fn new() -> Self {
+        Self {
+            position: None,
+            selected_count: 0,
+            _observe_active_editor: None,
+        }
+    }
+
+    fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
+        let editor = editor.read(cx);
+        let buffer = editor.buffer().read(cx).snapshot(cx);
+
+        self.selected_count = 0;
+        let mut last_selection: Option<Selection<usize>> = None;
+        for selection in editor.selections.all::<usize>(cx) {
+            self.selected_count += selection.end - selection.start;
+            if last_selection
+                .as_ref()
+                .map_or(true, |last_selection| selection.id > last_selection.id)
+            {
+                last_selection = Some(selection);
+            }
+        }
+        self.position = last_selection.map(|s| s.head().to_point(&buffer));
+
+        cx.notify();
+    }
+}
+
+impl Render for CursorPosition {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        div().when_some(self.position, |el, position| {
+            let mut text = format!(
+                "{}{FILE_ROW_COLUMN_DELIMITER}{}",
+                position.row + 1,
+                position.column + 1
+            );
+            if self.selected_count > 0 {
+                write!(text, " ({} selected)", self.selected_count).unwrap();
+            }
+
+            el.child(Label::new(text))
+        })
+    }
+}
+
+impl StatusItemView for CursorPosition {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
+            self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
+            self.update_position(editor, cx);
+        } else {
+            self.position = None;
+            self._observe_active_editor = None;
+        }
+
+        cx.notify();
+    }
+}
 
 fn path_for_buffer<'a>(
     buffer: &Model<MultiBuffer>,
@@ -5,7 +5,7 @@ use crate::{
     Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId,
     SelectPhase,
 };
-use gpui::{Task, ViewContext};
+use gpui::{px, Task, ViewContext};
 use language::{Bias, ToOffset};
 use lsp::LanguageServerId;
 use project::{
@@ -13,6 +13,7 @@ use project::{
     ResolveState,
 };
 use std::ops::Range;
+use theme::ActiveTheme as _;
 use util::TryFutureExt;
 
 #[derive(Debug, Default)]
@@ -485,40 +486,45 @@ pub fn show_link_definition(
                         });
 
                     if any_definition_does_not_contain_current_location {
-                        // todo!()
-                        // // Highlight symbol using theme link definition highlight style
-                        // let style = theme::current(cx).editor.link_definition;
-                        // let highlight_range =
-                        //     symbol_range.unwrap_or_else(|| match &trigger_point {
-                        //         TriggerPoint::Text(trigger_anchor) => {
-                        //             let snapshot = &snapshot.buffer_snapshot;
-                        //             // If no symbol range returned from language server, use the surrounding word.
-                        //             let (offset_range, _) =
-                        //                 snapshot.surrounding_word(*trigger_anchor);
-                        //             RangeInEditor::Text(
-                        //                 snapshot.anchor_before(offset_range.start)
-                        //                     ..snapshot.anchor_after(offset_range.end),
-                        //             )
-                        //         }
-                        //         TriggerPoint::InlayHint(highlight, _, _) => {
-                        //             RangeInEditor::Inlay(highlight.clone())
-                        //         }
-                        //     });
-
-                        // match highlight_range {
-                        //     RangeInEditor::Text(text_range) => this
-                        //         .highlight_text::<LinkGoToDefinitionState>(
-                        //             vec![text_range],
-                        //             style,
-                        //             cx,
-                        //         ),
-                        //     RangeInEditor::Inlay(highlight) => this
-                        //         .highlight_inlays::<LinkGoToDefinitionState>(
-                        //             vec![highlight],
-                        //             style,
-                        //             cx,
-                        //         ),
-                        // }
+                        let style = gpui::HighlightStyle {
+                            underline: Some(gpui::UnderlineStyle {
+                                thickness: px(1.),
+                                ..Default::default()
+                            }),
+                            color: Some(gpui::red()),
+                            ..Default::default()
+                        };
+                        let highlight_range =
+                            symbol_range.unwrap_or_else(|| match &trigger_point {
+                                TriggerPoint::Text(trigger_anchor) => {
+                                    let snapshot = &snapshot.buffer_snapshot;
+                                    // If no symbol range returned from language server, use the surrounding word.
+                                    let (offset_range, _) =
+                                        snapshot.surrounding_word(*trigger_anchor);
+                                    RangeInEditor::Text(
+                                        snapshot.anchor_before(offset_range.start)
+                                            ..snapshot.anchor_after(offset_range.end),
+                                    )
+                                }
+                                TriggerPoint::InlayHint(highlight, _, _) => {
+                                    RangeInEditor::Inlay(highlight.clone())
+                                }
+                            });
+
+                        match highlight_range {
+                            RangeInEditor::Text(text_range) => this
+                                .highlight_text::<LinkGoToDefinitionState>(
+                                    vec![text_range],
+                                    style,
+                                    cx,
+                                ),
+                            RangeInEditor::Inlay(highlight) => this
+                                .highlight_inlays::<LinkGoToDefinitionState>(
+                                    vec![highlight],
+                                    style,
+                                    cx,
+                                ),
+                        }
                     } else {
                         hide_link_definition(this, cx);
                     }

crates/editor2/src/mouse_context_menu.rs πŸ”—

@@ -1,5 +1,14 @@
-use crate::{DisplayPoint, Editor, EditorMode, SelectMode};
-use gpui::{Pixels, Point, ViewContext};
+use crate::{
+    DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
+    Rename, RevealInFinder, SelectMode, ToggleCodeActions,
+};
+use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
+
+pub struct MouseContextMenu {
+    pub(crate) position: Point<Pixels>,
+    pub(crate) context_menu: View<ui::ContextMenu>,
+    _subscription: Subscription,
+}
 
 pub fn deploy_context_menu(
     editor: &mut Editor,
@@ -7,50 +16,57 @@ pub fn deploy_context_menu(
     point: DisplayPoint,
     cx: &mut ViewContext<Editor>,
 ) {
-    todo!();
+    if !editor.is_focused(cx) {
+        editor.focus(cx);
+    }
+
+    // Don't show context menu for inline editors
+    if editor.mode() != EditorMode::Full {
+        return;
+    }
 
-    // if !editor.focused {
-    //     cx.focus_self();
-    // }
+    // Don't show the context menu if there isn't a project associated with this editor
+    if editor.project.is_none() {
+        return;
+    }
 
-    // // Don't show context menu for inline editors
-    // if editor.mode() != EditorMode::Full {
-    //     return;
-    // }
+    // Move the cursor to the clicked location so that dispatched actions make sense
+    editor.change_selections(None, cx, |s| {
+        s.clear_disjoint();
+        s.set_pending_display_range(point..point, SelectMode::Character);
+    });
 
-    // // Don't show the context menu if there isn't a project associated with this editor
-    // if editor.project.is_none() {
-    //     return;
-    // }
+    let context_menu = ui::ContextMenu::build(cx, |menu, cx| {
+        menu.action("Rename Symbol", Box::new(Rename), cx)
+            .action("Go to Definition", Box::new(GoToDefinition), cx)
+            .action("Go to Type Definition", Box::new(GoToTypeDefinition), cx)
+            .action("Find All References", Box::new(FindAllReferences), cx)
+            .action(
+                "Code Actions",
+                Box::new(ToggleCodeActions {
+                    deployed_from_indicator: false,
+                }),
+                cx,
+            )
+            .separator()
+            .action("Reveal in Finder", Box::new(RevealInFinder), cx)
+    });
+    let context_menu_focus = context_menu.focus_handle(cx);
+    cx.focus(&context_menu_focus);
 
-    // // Move the cursor to the clicked location so that dispatched actions make sense
-    // editor.change_selections(None, cx, |s| {
-    //     s.clear_disjoint();
-    //     s.set_pending_display_range(point..point, SelectMode::Character);
-    // });
+    let _subscription = cx.subscribe(&context_menu, move |this, _, event: &DismissEvent, cx| {
+        this.mouse_context_menu.take();
+        if context_menu_focus.contains_focused(cx) {
+            this.focus(cx);
+        }
+    });
 
-    // editor.mouse_context_menu.update(cx, |menu, cx| {
-    //     menu.show(
-    //         position,
-    //         AnchorCorner::TopLeft,
-    //         vec![
-    //             ContextMenuItem::action("Rename Symbol", Rename),
-    //             ContextMenuItem::action("Go to Definition", GoToDefinition),
-    //             ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition),
-    //             ContextMenuItem::action("Find All References", FindAllReferences),
-    //             ContextMenuItem::action(
-    //                 "Code Actions",
-    //                 ToggleCodeActions {
-    //                     deployed_from_indicator: false,
-    //                 },
-    //             ),
-    //             ContextMenuItem::Separator,
-    //             ContextMenuItem::action("Reveal in Finder", RevealInFinder),
-    //         ],
-    //         cx,
-    //     );
-    // });
-    // cx.notify();
+    editor.mouse_context_menu = Some(MouseContextMenu {
+        position,
+        context_menu,
+        _subscription,
+    });
+    cx.notify();
 }
 
 // #[cfg(test)]

crates/editor2/src/selections_collection.rs πŸ”—

@@ -592,31 +592,32 @@ impl<'a> MutableSelectionsCollection<'a> {
         self.select(selections)
     }
 
-    pub fn select_anchor_ranges<I: IntoIterator<Item = Range<Anchor>>>(&mut self, ranges: I) {
-        todo!()
-        // let buffer = self.buffer.read(self.cx).snapshot(self.cx);
-        // let selections = ranges
-        //     .into_iter()
-        //     .map(|range| {
-        //         let mut start = range.start;
-        //         let mut end = range.end;
-        //         let reversed = if start.cmp(&end, &buffer).is_gt() {
-        //             mem::swap(&mut start, &mut end);
-        //             true
-        //         } else {
-        //             false
-        //         };
-        //         Selection {
-        //             id: post_inc(&mut self.collection.next_selection_id),
-        //             start,
-        //             end,
-        //             reversed,
-        //             goal: SelectionGoal::None,
-        //         }
-        //     })
-        //     .collect::<Vec<_>>();
-
-        // self.select_anchors(selections)
+    pub fn select_anchor_ranges<I>(&mut self, ranges: I)
+    where
+        I: IntoIterator<Item = Range<Anchor>>,
+    {
+        let buffer = self.buffer.read(self.cx).snapshot(self.cx);
+        let selections = ranges
+            .into_iter()
+            .map(|range| {
+                let mut start = range.start;
+                let mut end = range.end;
+                let reversed = if start.cmp(&end, &buffer).is_gt() {
+                    mem::swap(&mut start, &mut end);
+                    true
+                } else {
+                    false
+                };
+                Selection {
+                    id: post_inc(&mut self.collection.next_selection_id),
+                    start,
+                    end,
+                    reversed,
+                    goal: SelectionGoal::None,
+                }
+            })
+            .collect::<Vec<_>>();
+        self.select_anchors(selections)
     }
 
     pub fn new_selection_id(&mut self) -> usize {

crates/editor2/src/test.rs πŸ”—

@@ -27,7 +27,7 @@ pub fn marked_display_snapshot(
     let (unmarked_text, markers) = marked_text_offsets(text);
 
     let font = cx.text_style().font();
-    let font_size: Pixels = 14.into();
+    let font_size: Pixels = 14usize.into();
 
     let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
     let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));

crates/file_finder/src/file_finder.rs πŸ”—

@@ -518,6 +518,7 @@ impl PickerDelegate for FileFinderDelegate {
     }
 
     fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
+        let raw_query = raw_query.trim();
         if raw_query.is_empty() {
             let project = self.project.read(cx);
             self.latest_search_id = post_inc(&mut self.search_count);
@@ -539,7 +540,6 @@ impl PickerDelegate for FileFinderDelegate {
             cx.notify();
             Task::ready(())
         } else {
-            let raw_query = &raw_query;
             let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
                 Ok::<_, std::convert::Infallible>(FileSearchQuery {
                     raw_query: raw_query.to_owned(),
@@ -735,6 +735,7 @@ mod tests {
         cx.dispatch_action(window.into(), Toggle);
 
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
         finder
             .update(cx, |finder, cx| {
                 finder.delegate_mut().update_matches("bna".to_string(), cx)
@@ -743,7 +744,6 @@ mod tests {
         finder.read_with(cx, |finder, _| {
             assert_eq!(finder.delegate().matches.len(), 2);
         });
-
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
         cx.dispatch_action(window.into(), SelectNext);
         cx.dispatch_action(window.into(), Confirm);
@@ -762,6 +762,49 @@ mod tests {
                 "bandana"
             );
         });
+
+        for bandana_query in [
+            "bandana",
+            " bandana",
+            "bandana ",
+            " bandana ",
+            " ndan ",
+            " band ",
+        ] {
+            finder
+                .update(cx, |finder, cx| {
+                    finder
+                        .delegate_mut()
+                        .update_matches(bandana_query.to_string(), cx)
+                })
+                .await;
+            finder.read_with(cx, |finder, _| {
+                assert_eq!(
+                    finder.delegate().matches.len(),
+                    1,
+                    "Wrong number of matches for bandana query '{bandana_query}'"
+                );
+            });
+            let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
+            cx.dispatch_action(window.into(), SelectNext);
+            cx.dispatch_action(window.into(), Confirm);
+            active_pane
+                .condition(cx, |pane, _| pane.active_item().is_some())
+                .await;
+            cx.read(|cx| {
+                let active_item = active_pane.read(cx).active_item().unwrap();
+                assert_eq!(
+                    active_item
+                        .as_any()
+                        .downcast_ref::<Editor>()
+                        .unwrap()
+                        .read(cx)
+                        .title(cx),
+                    "bandana",
+                    "Wrong match for bandana query '{bandana_query}'"
+                );
+            });
+        }
     }
 
     #[gpui::test]

crates/file_finder2/src/file_finder.rs πŸ”—

@@ -15,7 +15,7 @@ use std::{
     },
 };
 use text::Point;
-use ui::{v_stack, HighlightedLabel, ListItem};
+use ui::{prelude::*, v_stack, HighlightedLabel, ListItem};
 use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
 use workspace::Workspace;
 
@@ -552,6 +552,7 @@ impl PickerDelegate for FileFinderDelegate {
         raw_query: String,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> Task<()> {
+        let raw_query = raw_query.trim();
         if raw_query.is_empty() {
             let project = self.project.read(cx);
             self.latest_search_id = post_inc(&mut self.search_count);
@@ -573,7 +574,6 @@ impl PickerDelegate for FileFinderDelegate {
             cx.notify();
             Task::ready(())
         } else {
-            let raw_query = &raw_query;
             let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
                 Ok::<_, std::convert::Infallible>(FileSearchQuery {
                     raw_query: raw_query.to_owned(),
@@ -766,18 +766,49 @@ mod tests {
         let (picker, workspace, cx) = build_find_picker(project, cx);
 
         cx.simulate_input("bna");
-
         picker.update(cx, |picker, _| {
             assert_eq!(picker.delegate.matches.len(), 2);
         });
-
         cx.dispatch_action(SelectNext);
         cx.dispatch_action(Confirm);
-
         cx.read(|cx| {
             let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
             assert_eq!(active_editor.read(cx).title(cx), "bandana");
         });
+
+        for bandana_query in [
+            "bandana",
+            " bandana",
+            "bandana ",
+            " bandana ",
+            " ndan ",
+            " band ",
+        ] {
+            picker
+                .update(cx, |picker, cx| {
+                    picker
+                        .delegate
+                        .update_matches(bandana_query.to_string(), cx)
+                })
+                .await;
+            picker.update(cx, |picker, _| {
+                assert_eq!(
+                    picker.delegate.matches.len(),
+                    1,
+                    "Wrong number of matches for bandana query '{bandana_query}'"
+                );
+            });
+            cx.dispatch_action(SelectNext);
+            cx.dispatch_action(Confirm);
+            cx.read(|cx| {
+                let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+                assert_eq!(
+                    active_editor.read(cx).title(cx),
+                    "bandana",
+                    "Wrong match for bandana query '{bandana_query}'"
+                );
+            });
+        }
     }
 
     #[gpui::test]
@@ -1225,7 +1256,7 @@ mod tests {
         //
         // TODO: without closing, the opened items do not propagate their history changes for some reason
         // it does work in real app though, only tests do not propagate.
-        workspace.update(cx, |_, cx| dbg!(cx.focused()));
+        workspace.update(cx, |_, cx| cx.focused());
 
         let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
         assert!(

crates/gpui2/build.rs πŸ”—

@@ -65,6 +65,8 @@ fn generate_shader_bindings() -> PathBuf {
         "MonochromeSprite".into(),
         "PolychromeSprite".into(),
         "PathSprite".into(),
+        "SurfaceInputIndex".into(),
+        "SurfaceBounds".into(),
     ]);
     config.no_includes = true;
     config.enumeration.prefix_with_name = true;

crates/gpui2/src/app.rs πŸ”—

@@ -520,6 +520,10 @@ impl AppContext {
         self.platform.should_auto_hide_scrollbars()
     }
 
+    pub fn restart(&self) {
+        self.platform.restart()
+    }
+
     pub(crate) fn push_effect(&mut self, effect: Effect) {
         match &effect {
             Effect::Notify { emitter } => {

crates/gpui2/src/app/test_context.rs πŸ”—

@@ -2,8 +2,8 @@ use crate::{
     div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
     BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
     KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
-    TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
-    WindowOptions,
+    TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
@@ -502,6 +502,19 @@ impl<'a> VisualTestContext<'a> {
         self.cx.dispatch_action(self.window, action)
     }
 
+    pub fn window_title(&mut self) -> Option<String> {
+        self.cx
+            .update_window(self.window, |_, cx| {
+                cx.window
+                    .platform_window
+                    .as_test()
+                    .unwrap()
+                    .window_title
+                    .clone()
+            })
+            .unwrap()
+    }
+
     pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
         self.cx.simulate_keystrokes(self.window, keystrokes)
     }
@@ -509,6 +522,39 @@ impl<'a> VisualTestContext<'a> {
     pub fn simulate_input(&mut self, input: &str) {
         self.cx.simulate_input(self.window, input)
     }
+
+    pub fn simulate_activation(&mut self) {
+        self.simulate_window_events(&mut |handlers| {
+            handlers
+                .active_status_change
+                .iter_mut()
+                .for_each(|f| f(true));
+        })
+    }
+
+    pub fn simulate_deactivation(&mut self) {
+        self.simulate_window_events(&mut |handlers| {
+            handlers
+                .active_status_change
+                .iter_mut()
+                .for_each(|f| f(false));
+        })
+    }
+
+    fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
+        let handlers = self
+            .cx
+            .update_window(self.window, |_, cx| {
+                cx.window
+                    .platform_window
+                    .as_test()
+                    .unwrap()
+                    .handlers
+                    .clone()
+            })
+            .unwrap();
+        f(&mut *handlers.lock());
+    }
 }
 
 impl<'a> Context for VisualTestContext<'a> {

crates/gpui2/src/elements/div.rs πŸ”—

@@ -12,6 +12,7 @@ use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
     cell::RefCell,
+    cmp::Ordering,
     fmt::Debug,
     mem,
     rc::Rc,
@@ -357,6 +358,11 @@ pub trait StatefulInteractiveElement: InteractiveElement {
         self
     }
 
+    fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
+        self.interactivity().scroll_handle = Some(scroll_handle.clone());
+        self
+    }
+
     fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
@@ -626,6 +632,26 @@ impl Element for Div {
         let mut child_max = Point::default();
         let content_size = if element_state.child_layout_ids.is_empty() {
             bounds.size
+        } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() {
+            let mut state = scroll_handle.0.borrow_mut();
+            state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len());
+            state.bounds = bounds;
+            let requested = state.requested_scroll_top.take();
+
+            for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() {
+                let child_bounds = cx.layout_bounds(*child_layout_id);
+                child_min = child_min.min(&child_bounds.origin);
+                child_max = child_max.max(&child_bounds.lower_right());
+                state.child_bounds.push(child_bounds);
+
+                if let Some(requested) = requested.as_ref() {
+                    if requested.0 == ix {
+                        *state.offset.borrow_mut() =
+                            bounds.origin - (child_bounds.origin - point(px(0.), requested.1));
+                    }
+                }
+            }
+            (child_max - child_min).into()
         } else {
             for child_layout_id in &element_state.child_layout_ids {
                 let child_bounds = cx.layout_bounds(*child_layout_id);
@@ -696,6 +722,7 @@ pub struct Interactivity {
     pub key_context: KeyContext,
     pub focusable: bool,
     pub tracked_focus_handle: Option<FocusHandle>,
+    pub scroll_handle: Option<ScrollHandle>,
     pub focus_listeners: FocusListeners,
     pub group: Option<SharedString>,
     pub base_style: StyleRefinement,
@@ -754,6 +781,10 @@ impl Interactivity {
             });
         }
 
+        if let Some(scroll_handle) = self.scroll_handle.as_ref() {
+            element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
+        }
+
         let style = self.compute_style(None, &mut element_state, cx);
         let layout_id = f(style, cx);
         (layout_id, element_state)
@@ -824,7 +855,6 @@ impl Interactivity {
             .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
 
         if let Some(group_bounds) = hover_group_bounds {
-            // todo!() needs cx.was_top_layer
             let hovered = group_bounds.contains_point(&cx.mouse_position());
             cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                 if phase == DispatchPhase::Capture {
@@ -836,13 +866,13 @@ impl Interactivity {
         }
 
         if self.hover_style.is_some()
-            || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
+            || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
         {
-            let interactive_bounds = interactive_bounds.clone();
-            let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx);
+            let bounds = bounds.intersect(&cx.content_mask().bounds);
+            let hovered = bounds.contains_point(&cx.mouse_position());
             cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
                 if phase == DispatchPhase::Capture {
-                    if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
+                    if bounds.contains_point(&event.position) != hovered {
                         cx.notify();
                     }
                 }
@@ -1143,7 +1173,9 @@ impl Interactivity {
             let mouse_position = cx.mouse_position();
             if let Some(group_hover) = self.group_hover_style.as_ref() {
                 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
-                    if group_bounds.contains_point(&mouse_position) {
+                    if group_bounds.contains_point(&mouse_position)
+                        && cx.was_top_layer(&mouse_position, cx.stacking_order())
+                    {
                         style.refine(&group_hover.style);
                     }
                 }
@@ -1162,7 +1194,6 @@ impl Interactivity {
                 for (state_type, group_drag_style) in &self.group_drag_over_styles {
                     if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
                         if *state_type == drag.view.entity_type()
-                        // todo!() needs to handle cx.content_mask() and cx.is_top()
                             && group_bounds.contains_point(&mouse_position)
                         {
                             style.refine(&group_drag_style.style);
@@ -1175,7 +1206,6 @@ impl Interactivity {
                         && bounds
                             .intersect(&cx.content_mask().bounds)
                             .contains_point(&mouse_position)
-                        && cx.was_top_layer(&mouse_position, cx.stacking_order())
                     {
                         style.refine(drag_over_style);
                     }
@@ -1207,6 +1237,7 @@ impl Default for Interactivity {
             key_context: KeyContext::default(),
             focusable: false,
             tracked_focus_handle: None,
+            scroll_handle: None,
             focus_listeners: SmallVec::default(),
             // scroll_offset: Point::default(),
             group: None,
@@ -1430,3 +1461,83 @@ where
         self.element.children_mut()
     }
 }
+
+#[derive(Default)]
+struct ScrollHandleState {
+    // not great to have the nested rc's...
+    offset: Rc<RefCell<Point<Pixels>>>,
+    bounds: Bounds<Pixels>,
+    child_bounds: Vec<Bounds<Pixels>>,
+    requested_scroll_top: Option<(usize, Pixels)>,
+}
+
+#[derive(Clone)]
+pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
+
+impl ScrollHandle {
+    pub fn new() -> Self {
+        Self(Rc::default())
+    }
+
+    pub fn offset(&self) -> Point<Pixels> {
+        self.0.borrow().offset.borrow().clone()
+    }
+
+    pub fn top_item(&self) -> usize {
+        let state = self.0.borrow();
+        let top = state.bounds.top() - state.offset.borrow().y;
+
+        match state.child_bounds.binary_search_by(|bounds| {
+            if top < bounds.top() {
+                Ordering::Greater
+            } else if top > bounds.bottom() {
+                Ordering::Less
+            } else {
+                Ordering::Equal
+            }
+        }) {
+            Ok(ix) => ix,
+            Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
+        }
+    }
+
+    pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
+        self.0.borrow().child_bounds.get(ix).cloned()
+    }
+
+    /// scroll_to_item scrolls the minimal amount to ensure that the item is
+    /// fully visible
+    pub fn scroll_to_item(&self, ix: usize) {
+        let state = self.0.borrow();
+
+        let Some(bounds) = state.child_bounds.get(ix) else {
+            return;
+        };
+
+        let scroll_offset = state.offset.borrow().y;
+
+        if bounds.top() + scroll_offset < state.bounds.top() {
+            state.offset.borrow_mut().y = state.bounds.top() - bounds.top();
+        } else if bounds.bottom() + scroll_offset > state.bounds.bottom() {
+            state.offset.borrow_mut().y = state.bounds.bottom() - bounds.bottom();
+        }
+    }
+
+    pub fn logical_scroll_top(&self) -> (usize, Pixels) {
+        let ix = self.top_item();
+        let state = self.0.borrow();
+
+        if let Some(child_bounds) = state.child_bounds.get(ix) {
+            (
+                ix,
+                child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
+            )
+        } else {
+            (ix, px(0.))
+        }
+    }
+
+    pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
+        self.0.borrow_mut().requested_scroll_top = Some((ix, px));
+    }
+}

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

@@ -1,10 +1,12 @@
 use std::sync::Arc;
 
 use crate::{
-    Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
-    IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
+    point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
+    InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
+    StyleRefinement, Styled, WindowContext,
 };
 use futures::FutureExt;
+use media::core_video::CVImageBuffer;
 use util::ResultExt;
 
 #[derive(Clone, Debug)]
@@ -12,6 +14,7 @@ pub enum ImageSource {
     /// Image content will be loaded from provided URI at render time.
     Uri(SharedString),
     Data(Arc<ImageData>),
+    Surface(CVImageBuffer),
 }
 
 impl From<SharedString> for ImageSource {
@@ -20,40 +23,45 @@ impl From<SharedString> for ImageSource {
     }
 }
 
+impl From<&'static str> for ImageSource {
+    fn from(uri: &'static str) -> Self {
+        Self::Uri(uri.into())
+    }
+}
+
+impl From<String> for ImageSource {
+    fn from(uri: String) -> Self {
+        Self::Uri(uri.into())
+    }
+}
+
 impl From<Arc<ImageData>> for ImageSource {
     fn from(value: Arc<ImageData>) -> Self {
         Self::Data(value)
     }
 }
 
+impl From<CVImageBuffer> for ImageSource {
+    fn from(value: CVImageBuffer) -> Self {
+        Self::Surface(value)
+    }
+}
+
 pub struct Img {
     interactivity: Interactivity,
-    source: Option<ImageSource>,
+    source: ImageSource,
     grayscale: bool,
 }
 
-pub fn img() -> Img {
+pub fn img(source: impl Into<ImageSource>) -> Img {
     Img {
         interactivity: Interactivity::default(),
-        source: None,
+        source: source.into(),
         grayscale: false,
     }
 }
 
 impl Img {
-    pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
-        self.source = Some(ImageSource::from(uri.into()));
-        self
-    }
-    pub fn data(mut self, data: Arc<ImageData>) -> Self {
-        self.source = Some(ImageSource::from(data));
-        self
-    }
-
-    pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
-        self.source = Some(source.into());
-        self
-    }
     pub fn grayscale(mut self, grayscale: bool) -> Self {
         self.grayscale = grayscale;
         self
@@ -68,9 +76,8 @@ impl Element for Img {
         element_state: Option<Self::State>,
         cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
-        self.interactivity.layout(element_state, cx, |style, cx| {
-            cx.request_layout(&style, None)
-        })
+        self.interactivity
+            .layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
     }
 
     fn paint(
@@ -85,10 +92,9 @@ impl Element for Img {
             element_state,
             cx,
             |style, _scroll_offset, cx| {
-                let corner_radii = style.corner_radii;
-
-                if let Some(source) = self.source {
-                    let image = match source {
+                let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
+                cx.with_z_index(1, |cx| {
+                    match self.source {
                         ImageSource::Uri(uri) => {
                             let image_future = cx.image_cache.get(uri.clone());
                             if let Some(data) = image_future
@@ -96,7 +102,9 @@ impl Element for Img {
                                 .now_or_never()
                                 .and_then(|result| result.ok())
                             {
-                                data
+                                let new_bounds = preserve_aspect_ratio(bounds, data.size());
+                                cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
+                                    .log_err();
                             } else {
                                 cx.spawn(|mut cx| async move {
                                     if image_future.await.ok().is_some() {
@@ -104,17 +112,23 @@ impl Element for Img {
                                     }
                                 })
                                 .detach();
-                                return;
                             }
                         }
-                        ImageSource::Data(image) => image,
+
+                        ImageSource::Data(data) => {
+                            let new_bounds = preserve_aspect_ratio(bounds, data.size());
+                            cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
+                                .log_err();
+                        }
+
+                        ImageSource::Surface(surface) => {
+                            let size = size(surface.width().into(), surface.height().into());
+                            let new_bounds = preserve_aspect_ratio(bounds, size);
+                            // TODO: Add support for corner_radii and grayscale.
+                            cx.paint_surface(new_bounds, surface);
+                        }
                     };
-                    let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
-                    cx.with_z_index(1, |cx| {
-                        cx.paint_image(bounds, corner_radii, image, self.grayscale)
-                            .log_err()
-                    });
-                }
+                });
             },
         )
     }
@@ -143,3 +157,29 @@ impl InteractiveElement for Img {
         &mut self.interactivity
     }
 }
+
+fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
+    let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
+    let image_ratio = image_size.width / image_size.height;
+    let bounds_ratio = bounds.size.width / bounds.size.height;
+
+    let new_size = if bounds_ratio > image_ratio {
+        size(
+            image_size.width * (bounds.size.height / image_size.height),
+            bounds.size.height,
+        )
+    } else {
+        size(
+            bounds.size.width,
+            image_size.height * (bounds.size.width / image_size.width),
+        )
+    };
+
+    Bounds {
+        origin: point(
+            bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
+            bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
+        ),
+        size: new_size,
+    }
+}

crates/gpui2/src/elements/text.rs πŸ”—

@@ -1,6 +1,7 @@
 use crate::{
-    Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
-    Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
+    Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
+    MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
+    WhiteSpace, WindowContext, WrappedLine,
 };
 use anyhow::anyhow;
 use parking_lot::{Mutex, MutexGuard};
@@ -87,7 +88,28 @@ impl StyledText {
         }
     }
 
-    pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
+    pub fn with_highlights(
+        mut self,
+        default_style: &TextStyle,
+        highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
+    ) -> Self {
+        let mut runs = Vec::new();
+        let mut ix = 0;
+        for (range, highlight) in highlights {
+            if ix < range.start {
+                runs.push(default_style.clone().to_run(range.start - ix));
+            }
+            runs.push(
+                default_style
+                    .clone()
+                    .highlight(highlight)
+                    .to_run(range.len()),
+            );
+            ix = range.end;
+        }
+        if ix < self.text.len() {
+            runs.push(default_style.to_run(self.text.len() - ix));
+        }
         self.runs = Some(runs);
         self
     }
@@ -144,7 +166,6 @@ impl TextState {
         runs: Option<Vec<TextRun>>,
         cx: &mut WindowContext,
     ) -> LayoutId {
-        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
@@ -152,18 +173,16 @@ impl TextState {
             .to_pixels(font_size.into(), cx.rem_size());
         let text = SharedString::from(text);
 
-        let rem_size = cx.rem_size();
-
         let runs = if let Some(runs) = runs {
             runs
         } else {
             vec![text_style.to_run(text.len())]
         };
 
-        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
+        let layout_id = cx.request_measured_layout(Default::default(), {
             let element_state = self.clone();
 
-            move |known_dimensions, available_space| {
+            move |known_dimensions, available_space, cx| {
                 let wrap_width = if text_style.white_space == WhiteSpace::Normal {
                     known_dimensions.width.or(match available_space.width {
                         crate::AvailableSpace::Definite(x) => Some(x),
@@ -181,7 +200,8 @@ impl TextState {
                     }
                 }
 
-                let Some(lines) = text_system
+                let Some(lines) = cx
+                    .text_system()
                     .shape_text(
                         &text, font_size, &runs, wrap_width, // Wrap if we know the width.
                     )
@@ -265,7 +285,9 @@ impl TextState {
 pub struct InteractiveText {
     element_id: ElementId,
     text: StyledText,
-    click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+    click_listener:
+        Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+    clickable_ranges: Vec<Range<usize>>,
 }
 
 struct InteractiveTextClickEvent {
@@ -284,6 +306,7 @@ impl InteractiveText {
             element_id: id.into(),
             text,
             click_listener: None,
+            clickable_ranges: Vec::new(),
         }
     }
 
@@ -292,7 +315,7 @@ impl InteractiveText {
         ranges: Vec<Range<usize>>,
         listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
     ) -> Self {
-        self.click_listener = Some(Box::new(move |event, cx| {
+        self.click_listener = Some(Box::new(move |ranges, event, cx| {
             for (range_ix, range) in ranges.iter().enumerate() {
                 if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
                 {
@@ -300,6 +323,7 @@ impl InteractiveText {
                 }
             }
         }));
+        self.clickable_ranges = ranges;
         self
     }
 }
@@ -334,6 +358,19 @@ impl Element for InteractiveText {
 
     fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
         if let Some(click_listener) = self.click_listener {
+            if let Some(ix) = state
+                .text_state
+                .index_for_position(bounds, cx.mouse_position())
+            {
+                if self
+                    .clickable_ranges
+                    .iter()
+                    .any(|range| range.contains(&ix))
+                {
+                    cx.set_cursor_style(crate::CursorStyle::PointingHand)
+                }
+            }
+
             let text_state = state.text_state.clone();
             let mouse_down = state.mouse_down_index.clone();
             if let Some(mouse_down_index) = mouse_down.get() {
@@ -343,6 +380,7 @@ impl Element for InteractiveText {
                             text_state.index_for_position(bounds, event.position)
                         {
                             click_listener(
+                                &self.clickable_ranges,
                                 InteractiveTextClickEvent {
                                     mouse_down_index,
                                     mouse_up_index,

crates/gpui2/src/elements/uniform_list.rs πŸ”—

@@ -109,7 +109,6 @@ impl Element for UniformList {
         cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         let max_items = self.item_count;
-        let rem_size = cx.rem_size();
         let item_size = state
             .as_ref()
             .map(|s| s.item_size)
@@ -120,9 +119,7 @@ impl Element for UniformList {
                 .layout(state.map(|s| s.interactive), cx, |style, cx| {
                     cx.request_measured_layout(
                         style,
-                        rem_size,
-                        move |known_dimensions: Size<Option<Pixels>>,
-                              available_space: Size<AvailableSpace>| {
+                        move |known_dimensions, available_space, _cx| {
                             let desired_height = item_size.height * max_items;
                             let width =
                                 known_dimensions

crates/gpui2/src/geometry.rs πŸ”—

@@ -655,6 +655,20 @@ pub struct Corners<T: Clone + Default + Debug> {
     pub bottom_left: T,
 }
 
+impl<T> Corners<T>
+where
+    T: Clone + Default + Debug,
+{
+    pub fn all(value: T) -> Self {
+        Self {
+            top_left: value.clone(),
+            top_right: value.clone(),
+            bottom_right: value.clone(),
+            bottom_left: value,
+        }
+    }
+}
+
 impl Corners<AbsoluteLength> {
     pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
         let max = size.width.max(size.height) / 2.;
@@ -740,7 +754,7 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
     Deserialize,
 )]
 #[repr(transparent)]
-pub struct Pixels(pub(crate) f32);
+pub struct Pixels(pub f32);
 
 impl std::ops::Div for Pixels {
     type Output = f32;
@@ -905,6 +919,12 @@ impl From<Pixels> for usize {
     }
 }
 
+impl From<usize> for Pixels {
+    fn from(pixels: usize) -> Self {
+        Pixels(pixels as f32)
+    }
+}
+
 #[derive(
     Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
 )]
@@ -959,6 +979,18 @@ impl From<u64> for DevicePixels {
     }
 }
 
+impl From<DevicePixels> for usize {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as usize
+    }
+}
+
+impl From<usize> for DevicePixels {
+    fn from(device_pixels: usize) -> Self {
+        DevicePixels(device_pixels as i32)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
 #[repr(transparent)]
 pub struct ScaledPixels(pub(crate) f32);
@@ -1034,7 +1066,7 @@ impl sqlez::bindable::Bind for GlobalPixels {
 }
 
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
-pub struct Rems(f32);
+pub struct Rems(pub f32);
 
 impl Mul<Pixels> for Rems {
     type Output = Pixels;

crates/gpui2/src/platform.rs πŸ”—

@@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow {
     fn draw(&self, scene: Scene);
 
     fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+    #[cfg(any(test, feature = "test-support"))]
+    fn as_test(&self) -> Option<&TestWindow> {
+        None
+    }
 }
 
 pub trait PlatformDispatcher: Send + Sync {

crates/gpui2/src/platform/mac/metal_renderer.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{
     point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
     Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
-    Quad, ScaledPixels, Scene, Shadow, Size, Underline,
+    Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
 };
 use cocoa::{
     base::{NO, YES},
@@ -9,6 +9,9 @@ use cocoa::{
     quartzcore::AutoresizingMask,
 };
 use collections::HashMap;
+use core_foundation::base::TCFType;
+use foreign_types::ForeignType;
+use media::core_video::CVMetalTextureCache;
 use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
 use smallvec::SmallVec;
@@ -27,9 +30,11 @@ pub(crate) struct MetalRenderer {
     underlines_pipeline_state: metal::RenderPipelineState,
     monochrome_sprites_pipeline_state: metal::RenderPipelineState,
     polychrome_sprites_pipeline_state: metal::RenderPipelineState,
+    surfaces_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     instances: metal::Buffer,
     sprite_atlas: Arc<MetalAtlas>,
+    core_video_texture_cache: CVMetalTextureCache,
 }
 
 impl MetalRenderer {
@@ -143,6 +148,14 @@ impl MetalRenderer {
             "polychrome_sprite_fragment",
             MTLPixelFormat::BGRA8Unorm,
         );
+        let surfaces_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "surfaces",
+            "surface_vertex",
+            "surface_fragment",
+            MTLPixelFormat::BGRA8Unorm,
+        );
 
         let command_queue = device.new_command_queue();
         let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
@@ -157,9 +170,11 @@ impl MetalRenderer {
             underlines_pipeline_state,
             monochrome_sprites_pipeline_state,
             polychrome_sprites_pipeline_state,
+            surfaces_pipeline_state,
             unit_vertices,
             instances,
             sprite_atlas,
+            core_video_texture_cache: CVMetalTextureCache::new(device.as_ptr()).unwrap(),
         }
     }
 
@@ -268,6 +283,14 @@ impl MetalRenderer {
                         command_encoder,
                     );
                 }
+                PrimitiveBatch::Surfaces(surfaces) => {
+                    self.draw_surfaces(
+                        surfaces,
+                        &mut instance_offset,
+                        viewport_size,
+                        command_encoder,
+                    );
+                }
             }
         }
 
@@ -793,6 +816,102 @@ impl MetalRenderer {
         );
         *offset = next_offset;
     }
+
+    fn draw_surfaces(
+        &mut self,
+        surfaces: &[Surface],
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            SurfaceInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_bytes(
+            SurfaceInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
+        );
+
+        for surface in surfaces {
+            let texture_size = size(
+                DevicePixels::from(surface.image_buffer.width() as i32),
+                DevicePixels::from(surface.image_buffer.height() as i32),
+            );
+
+            assert_eq!(
+                surface.image_buffer.pixel_format_type(),
+                media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
+            );
+
+            let y_texture = self
+                .core_video_texture_cache
+                .create_texture_from_image(
+                    surface.image_buffer.as_concrete_TypeRef(),
+                    ptr::null(),
+                    MTLPixelFormat::R8Unorm,
+                    surface.image_buffer.plane_width(0),
+                    surface.image_buffer.plane_height(0),
+                    0,
+                )
+                .unwrap();
+            let cb_cr_texture = self
+                .core_video_texture_cache
+                .create_texture_from_image(
+                    surface.image_buffer.as_concrete_TypeRef(),
+                    ptr::null(),
+                    MTLPixelFormat::RG8Unorm,
+                    surface.image_buffer.plane_width(1),
+                    surface.image_buffer.plane_height(1),
+                    1,
+                )
+                .unwrap();
+
+            align_offset(offset);
+            let next_offset = *offset + mem::size_of::<Surface>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            command_encoder.set_vertex_buffer(
+                SurfaceInputIndex::Surfaces as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+            command_encoder.set_vertex_bytes(
+                SurfaceInputIndex::TextureSize as u64,
+                mem::size_of_val(&texture_size) as u64,
+                &texture_size as *const Size<DevicePixels> as *const _,
+            );
+            command_encoder.set_fragment_texture(
+                SurfaceInputIndex::YTexture as u64,
+                Some(y_texture.as_texture_ref()),
+            );
+            command_encoder.set_fragment_texture(
+                SurfaceInputIndex::CbCrTexture as u64,
+                Some(cb_cr_texture.as_texture_ref()),
+            );
+
+            unsafe {
+                let buffer_contents =
+                    (self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds;
+                ptr::write(
+                    buffer_contents,
+                    SurfaceBounds {
+                        bounds: surface.bounds,
+                        content_mask: surface.content_mask.clone(),
+                    },
+                );
+            }
+
+            command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
+            *offset = next_offset;
+        }
+    }
 }
 
 fn build_pipeline_state(
@@ -898,6 +1017,16 @@ enum SpriteInputIndex {
     AtlasTexture = 4,
 }
 
+#[repr(C)]
+enum SurfaceInputIndex {
+    Vertices = 0,
+    Surfaces = 1,
+    ViewportSize = 2,
+    TextureSize = 3,
+    YTexture = 4,
+    CbCrTexture = 5,
+}
+
 #[repr(C)]
 enum PathRasterizationInputIndex {
     Vertices = 0,
@@ -911,3 +1040,10 @@ pub struct PathSprite {
     pub color: Hsla,
     pub tile: AtlasTile,
 }
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct SurfaceBounds {
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+}

crates/gpui2/src/platform/mac/shaders.metal πŸ”—

@@ -469,6 +469,58 @@ fragment float4 path_sprite_fragment(
   return color;
 }
 
+struct SurfaceVertexOutput {
+  float4 position [[position]];
+  float2 texture_position;
+  float clip_distance [[clip_distance]][4];
+};
+
+struct SurfaceFragmentInput {
+  float4 position [[position]];
+  float2 texture_position;
+};
+
+vertex SurfaceVertexOutput surface_vertex(
+    uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
+    constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
+    constant Size_DevicePixels *viewport_size
+    [[buffer(SurfaceInputIndex_ViewportSize)]],
+    constant Size_DevicePixels *texture_size
+    [[buffer(SurfaceInputIndex_TextureSize)]]) {
+  float2 unit_vertex = unit_vertices[unit_vertex_id];
+  SurfaceBounds surface = surfaces[surface_id];
+  float4 device_position =
+      to_device_position(unit_vertex, surface.bounds, viewport_size);
+  float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
+                                                 surface.content_mask.bounds);
+  // We are going to copy the whole texture, so the texture position corresponds
+  // to the current vertex of the unit triangle.
+  float2 texture_position = unit_vertex;
+  return SurfaceVertexOutput{
+      device_position,
+      texture_position,
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
+}
+
+fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
+                                 texture2d<float> y_texture
+                                 [[texture(SurfaceInputIndex_YTexture)]],
+                                 texture2d<float> cb_cr_texture
+                                 [[texture(SurfaceInputIndex_CbCrTexture)]]) {
+  constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
+  const float4x4 ycbcrToRGBTransform =
+      float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
+               float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
+               float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
+               float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
+  float4 ycbcr = float4(
+      y_texture.sample(texture_sampler, input.texture_position).r,
+      cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
+
+  return ycbcrToRGBTransform * ycbcr;
+}
+
 float4 hsla_to_rgba(Hsla hsla) {
   float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
   float s = hsla.s;

crates/gpui2/src/platform/test/platform.rs πŸ”—

@@ -189,13 +189,9 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {}
 
-    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {}
 
     fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
 

crates/gpui2/src/platform/test/window.rs πŸ”—

@@ -11,19 +11,20 @@ use std::{
 };
 
 #[derive(Default)]
-struct Handlers {
-    active_status_change: Vec<Box<dyn FnMut(bool)>>,
-    input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
-    moved: Vec<Box<dyn FnMut()>>,
-    resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
+pub(crate) struct TestWindowHandlers {
+    pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
+    pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
+    pub(crate) moved: Vec<Box<dyn FnMut()>>,
+    pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
 }
 
 pub struct TestWindow {
     bounds: WindowBounds,
     current_scene: Mutex<Option<Scene>>,
     display: Rc<dyn PlatformDisplay>,
+    pub(crate) window_title: Option<String>,
     pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
-    handlers: Mutex<Handlers>,
+    pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
     platform: Weak<TestPlatform>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
 }
@@ -42,6 +43,7 @@ impl TestWindow {
             input_handler: None,
             sprite_atlas: Arc::new(TestAtlas::new()),
             handlers: Default::default(),
+            window_title: Default::default(),
         }
     }
 }
@@ -100,8 +102,8 @@ impl PlatformWindow for TestWindow {
         todo!()
     }
 
-    fn set_title(&mut self, _title: &str) {
-        todo!()
+    fn set_title(&mut self, title: &str) {
+        self.window_title = Some(title.to_owned());
     }
 
     fn set_edited(&mut self, _edited: bool) {
@@ -167,6 +169,10 @@ impl PlatformWindow for TestWindow {
     fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
         self.sprite_atlas.clone()
     }
+
+    fn as_test(&self) -> Option<&TestWindow> {
+        Some(self)
+    }
 }
 
 pub struct TestAtlasState {

crates/gpui2/src/scene.rs πŸ”—

@@ -25,6 +25,7 @@ pub(crate) struct SceneBuilder {
     underlines: Vec<Underline>,
     monochrome_sprites: Vec<MonochromeSprite>,
     polychrome_sprites: Vec<PolychromeSprite>,
+    surfaces: Vec<Surface>,
 }
 
 impl Default for SceneBuilder {
@@ -38,6 +39,7 @@ impl Default for SceneBuilder {
             underlines: Vec::new(),
             monochrome_sprites: Vec::new(),
             polychrome_sprites: Vec::new(),
+            surfaces: Vec::new(),
         }
     }
 }
@@ -120,6 +122,7 @@ impl SceneBuilder {
                 (PrimitiveKind::PolychromeSprite, ix) => {
                     self.polychrome_sprites[ix].order = draw_order as DrawOrder
                 }
+                (PrimitiveKind::Surface, ix) => self.surfaces[ix].order = draw_order as DrawOrder,
             }
         }
 
@@ -129,6 +132,7 @@ impl SceneBuilder {
         self.underlines.sort_unstable();
         self.monochrome_sprites.sort_unstable();
         self.polychrome_sprites.sort_unstable();
+        self.surfaces.sort_unstable();
 
         Scene {
             shadows: mem::take(&mut self.shadows),
@@ -137,6 +141,7 @@ impl SceneBuilder {
             underlines: mem::take(&mut self.underlines),
             monochrome_sprites: mem::take(&mut self.monochrome_sprites),
             polychrome_sprites: mem::take(&mut self.polychrome_sprites),
+            surfaces: mem::take(&mut self.surfaces),
         }
     }
 
@@ -185,6 +190,10 @@ impl SceneBuilder {
                 sprite.order = layer_id;
                 self.polychrome_sprites.push(sprite);
             }
+            Primitive::Surface(mut surface) => {
+                surface.order = layer_id;
+                self.surfaces.push(surface);
+            }
         }
     }
 }
@@ -196,6 +205,7 @@ pub(crate) struct Scene {
     pub underlines: Vec<Underline>,
     pub monochrome_sprites: Vec<MonochromeSprite>,
     pub polychrome_sprites: Vec<PolychromeSprite>,
+    pub surfaces: Vec<Surface>,
 }
 
 impl Scene {
@@ -224,6 +234,9 @@ impl Scene {
             polychrome_sprites: &self.polychrome_sprites,
             polychrome_sprites_start: 0,
             polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
+            surfaces: &self.surfaces,
+            surfaces_start: 0,
+            surfaces_iter: self.surfaces.iter().peekable(),
         }
     }
 }
@@ -247,6 +260,9 @@ struct BatchIterator<'a> {
     polychrome_sprites: &'a [PolychromeSprite],
     polychrome_sprites_start: usize,
     polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
+    surfaces: &'a [Surface],
+    surfaces_start: usize,
+    surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
 }
 
 impl<'a> Iterator for BatchIterator<'a> {
@@ -272,6 +288,10 @@ impl<'a> Iterator for BatchIterator<'a> {
                 self.polychrome_sprites_iter.peek().map(|s| s.order),
                 PrimitiveKind::PolychromeSprite,
             ),
+            (
+                self.surfaces_iter.peek().map(|s| s.order),
+                PrimitiveKind::Surface,
+            ),
         ];
         orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
 
@@ -378,6 +398,21 @@ impl<'a> Iterator for BatchIterator<'a> {
                     sprites: &self.polychrome_sprites[sprites_start..sprites_end],
                 })
             }
+            PrimitiveKind::Surface => {
+                let surfaces_start = self.surfaces_start;
+                let mut surfaces_end = surfaces_start;
+                while self
+                    .surfaces_iter
+                    .next_if(|surface| surface.order <= max_order)
+                    .is_some()
+                {
+                    surfaces_end += 1;
+                }
+                self.surfaces_start = surfaces_end;
+                Some(PrimitiveBatch::Surfaces(
+                    &self.surfaces[surfaces_start..surfaces_end],
+                ))
+            }
         }
     }
 }
@@ -391,6 +426,7 @@ pub enum PrimitiveKind {
     Underline,
     MonochromeSprite,
     PolychromeSprite,
+    Surface,
 }
 
 pub enum Primitive {
@@ -400,6 +436,7 @@ pub enum Primitive {
     Underline(Underline),
     MonochromeSprite(MonochromeSprite),
     PolychromeSprite(PolychromeSprite),
+    Surface(Surface),
 }
 
 impl Primitive {
@@ -411,6 +448,7 @@ impl Primitive {
             Primitive::Underline(underline) => &underline.bounds,
             Primitive::MonochromeSprite(sprite) => &sprite.bounds,
             Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+            Primitive::Surface(surface) => &surface.bounds,
         }
     }
 
@@ -422,6 +460,7 @@ impl Primitive {
             Primitive::Underline(underline) => &underline.content_mask,
             Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
             Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+            Primitive::Surface(surface) => &surface.content_mask,
         }
     }
 }
@@ -440,6 +479,7 @@ pub(crate) enum PrimitiveBatch<'a> {
         texture_id: AtlasTextureId,
         sprites: &'a [PolychromeSprite],
     },
+    Surfaces(&'a [Surface]),
 }
 
 #[derive(Default, Debug, Clone, Eq, PartialEq)]
@@ -593,6 +633,32 @@ impl From<PolychromeSprite> for Primitive {
     }
 }
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Surface {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub image_buffer: media::core_video::CVImageBuffer,
+}
+
+impl Ord for Surface {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
+    }
+}
+
+impl PartialOrd for Surface {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<Surface> for Primitive {
+    fn from(surface: Surface) -> Self {
+        Primitive::Surface(surface)
+    }
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub(crate) struct PathId(pub(crate) usize);
 

crates/gpui2/src/taffy.rs πŸ”—

@@ -1,4 +1,7 @@
-use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style};
+use crate::{
+    AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style,
+    WindowContext,
+};
 use collections::{HashMap, HashSet};
 use smallvec::SmallVec;
 use std::fmt::Debug;
@@ -9,13 +12,21 @@ use taffy::{
     Taffy,
 };
 
-type Measureable = dyn Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync;
-
 pub struct TaffyLayoutEngine {
-    taffy: Taffy<Box<Measureable>>,
+    taffy: Taffy,
     children_to_parents: HashMap<LayoutId, LayoutId>,
     absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
     computed_layouts: HashSet<LayoutId>,
+    nodes_to_measure: HashMap<
+        LayoutId,
+        Box<
+            dyn FnMut(
+                Size<Option<Pixels>>,
+                Size<AvailableSpace>,
+                &mut WindowContext,
+            ) -> Size<Pixels>,
+        >,
+    >,
 }
 
 static EXPECT_MESSAGE: &'static str =
@@ -28,6 +39,7 @@ impl TaffyLayoutEngine {
             children_to_parents: HashMap::default(),
             absolute_layout_bounds: HashMap::default(),
             computed_layouts: HashSet::default(),
+            nodes_to_measure: HashMap::default(),
         }
     }
 
@@ -36,6 +48,7 @@ impl TaffyLayoutEngine {
         self.children_to_parents.clear();
         self.absolute_layout_bounds.clear();
         self.computed_layouts.clear();
+        self.nodes_to_measure.clear();
     }
 
     pub fn request_layout(
@@ -65,18 +78,18 @@ impl TaffyLayoutEngine {
         &mut self,
         style: Style,
         rem_size: Pixels,
-        measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
-            + Send
-            + Sync
+        measure: impl FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
             + 'static,
     ) -> LayoutId {
         let style = style.to_taffy(rem_size);
 
-        let measurable = Box::new(measure);
-        self.taffy
-            .new_leaf_with_context(style, measurable)
+        let layout_id = self
+            .taffy
+            .new_leaf_with_context(style, ())
             .expect(EXPECT_MESSAGE)
-            .into()
+            .into();
+        self.nodes_to_measure.insert(layout_id, Box::new(measure));
+        layout_id
     }
 
     // Used to understand performance
@@ -126,7 +139,12 @@ impl TaffyLayoutEngine {
         Ok(edges)
     }
 
-    pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) {
+    pub fn compute_layout(
+        &mut self,
+        id: LayoutId,
+        available_space: Size<AvailableSpace>,
+        cx: &mut WindowContext,
+    ) {
         // Leaving this here until we have a better instrumentation approach.
         // println!("Laying out {} children", self.count_all_children(id)?);
         // println!("Max layout depth: {}", self.max_depth(0, id)?);
@@ -159,8 +177,8 @@ impl TaffyLayoutEngine {
             .compute_layout_with_measure(
                 id.into(),
                 available_space.into(),
-                |known_dimensions, available_space, _node_id, context| {
-                    let Some(measure) = context else {
+                |known_dimensions, available_space, node_id, _context| {
+                    let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else {
                         return taffy::geometry::Size::default();
                     };
 
@@ -169,10 +187,11 @@ impl TaffyLayoutEngine {
                         height: known_dimensions.height.map(Pixels),
                     };
 
-                    measure(known_dimensions, available_space.into()).into()
+                    measure(known_dimensions, available_space.into(), cx).into()
                 },
             )
             .expect(EXPECT_MESSAGE);
+
         // println!("compute_layout took {:?}", started_at.elapsed());
     }
 

crates/gpui2/src/view.rs πŸ”—

@@ -209,9 +209,7 @@ impl AnyView {
     ) {
         cx.with_absolute_element_offset(origin, |cx| {
             let (layout_id, rendered_element) = (self.layout)(self, cx);
-            cx.window
-                .layout_engine
-                .compute_layout(layout_id, available_space);
+            cx.compute_layout(layout_id, available_space);
             (self.paint)(self, rendered_element, cx);
         })
     }
@@ -240,6 +238,10 @@ impl Element for AnyView {
     }
 
     fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        debug_assert!(
+            state.is_some(),
+            "state is None. Did you include an AnyView twice in the tree?"
+        );
         (self.paint)(&self, state.take().unwrap(), cx)
     }
 }

crates/gpui2/src/window.rs πŸ”—

@@ -8,8 +8,8 @@ use crate::{
     MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
     PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
     RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
-    VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
+    UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Context as _, Result};
 use collections::HashMap;
@@ -18,6 +18,7 @@ use futures::{
     channel::{mpsc, oneshot},
     StreamExt,
 };
+use media::core_video::CVImageBuffer;
 use parking_lot::RwLock;
 use slotmap::SlotMap;
 use smallvec::SmallVec;
@@ -208,7 +209,7 @@ pub struct Window {
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
     viewport_size: Size<Pixels>,
-    pub(crate) layout_engine: TaffyLayoutEngine,
+    layout_engine: Option<TaffyLayoutEngine>,
     pub(crate) root_view: Option<AnyView>,
     pub(crate) element_id_stack: GlobalElementId,
     pub(crate) previous_frame: Frame,
@@ -326,7 +327,7 @@ impl Window {
             sprite_atlas,
             rem_size: px(16.),
             viewport_size: content_size,
-            layout_engine: TaffyLayoutEngine::new(),
+            layout_engine: Some(TaffyLayoutEngine::new()),
             root_view: None,
             element_id_stack: GlobalElementId::default(),
             previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
@@ -605,9 +606,11 @@ impl<'a> WindowContext<'a> {
         self.app.layout_id_buffer.extend(children.into_iter());
         let rem_size = self.rem_size();
 
-        self.window
-            .layout_engine
-            .request_layout(style, rem_size, &self.app.layout_id_buffer)
+        self.window.layout_engine.as_mut().unwrap().request_layout(
+            style,
+            rem_size,
+            &self.app.layout_id_buffer,
+        )
     }
 
     /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
@@ -617,22 +620,25 @@ impl<'a> WindowContext<'a> {
     /// The given closure is invoked at layout time with the known dimensions and available space and
     /// returns a `Size`.
     pub fn request_measured_layout<
-        F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
+        F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+            + 'static,
     >(
         &mut self,
         style: Style,
-        rem_size: Pixels,
         measure: F,
     ) -> LayoutId {
+        let rem_size = self.rem_size();
         self.window
             .layout_engine
+            .as_mut()
+            .unwrap()
             .request_measured_layout(style, rem_size, measure)
     }
 
     pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
-        self.window
-            .layout_engine
-            .compute_layout(layout_id, available_space)
+        let mut layout_engine = self.window.layout_engine.take().unwrap();
+        layout_engine.compute_layout(layout_id, available_space, self);
+        self.window.layout_engine = Some(layout_engine);
     }
 
     /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
@@ -642,6 +648,8 @@ impl<'a> WindowContext<'a> {
         let mut bounds = self
             .window
             .layout_engine
+            .as_mut()
+            .unwrap()
             .layout_bounds(layout_id)
             .map(Into::into);
         bounds.origin += self.element_offset();
@@ -677,6 +685,10 @@ impl<'a> WindowContext<'a> {
         self.window.platform_window.zoom();
     }
 
+    pub fn set_window_title(&mut self, title: &str) {
+        self.window.platform_window.set_title(title);
+    }
+
     pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
         self.platform
             .displays()
@@ -1116,6 +1128,23 @@ impl<'a> WindowContext<'a> {
         Ok(())
     }
 
+    /// Paint a surface into the scene for the current frame at the current z-index.
+    pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
+        let scale_factor = self.scale_factor();
+        let bounds = bounds.scale(scale_factor);
+        let content_mask = self.content_mask().scale(scale_factor);
+        let window = &mut *self.window;
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
+            Surface {
+                order: 0,
+                bounds,
+                content_mask,
+                image_buffer,
+            },
+        );
+    }
+
     /// Draw pixels to the display for this window based on the contents of its scene.
     pub(crate) fn draw(&mut self) {
         let root_view = self.window.root_view.take().unwrap();
@@ -1171,7 +1200,7 @@ impl<'a> WindowContext<'a> {
         self.text_system().start_frame();
 
         let window = &mut *self.window;
-        window.layout_engine.clear();
+        window.layout_engine.as_mut().unwrap().clear();
 
         mem::swap(&mut window.previous_frame, &mut window.current_frame);
         let frame = &mut window.current_frame;
@@ -2582,7 +2611,7 @@ impl<V: 'static + Render> WindowHandle<V> {
         cx.read_window(self, |root_view, _cx| root_view.clone())
     }
 
-    pub fn is_active(&self, cx: &WindowContext) -> Option<bool> {
+    pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
         cx.windows
             .get(self.id)
             .and_then(|window| window.as_ref().map(|window| window.active))

crates/language/src/language.rs πŸ”—

@@ -197,8 +197,12 @@ impl CachedLspAdapter {
         self.adapter.code_action_kinds()
     }
 
-    pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
-        self.adapter.workspace_configuration(cx)
+    pub fn workspace_configuration(
+        &self,
+        workspace_root: &Path,
+        cx: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
+        self.adapter.workspace_configuration(workspace_root, cx)
     }
 
     pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
@@ -312,7 +316,7 @@ pub trait LspAdapter: 'static + Send + Sync {
         None
     }
 
-    fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
         futures::future::ready(serde_json::json!({})).boxed()
     }
 

crates/language2/src/language2.rs πŸ”—

@@ -200,8 +200,12 @@ impl CachedLspAdapter {
         self.adapter.code_action_kinds()
     }
 
-    pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
-        self.adapter.workspace_configuration(cx)
+    pub fn workspace_configuration(
+        &self,
+        workspace_root: &Path,
+        cx: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
+        self.adapter.workspace_configuration(workspace_root, cx)
     }
 
     pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
@@ -315,7 +319,7 @@ pub trait LspAdapter: 'static + Send + Sync {
         None
     }
 
-    fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
         futures::future::ready(serde_json::json!({})).boxed()
     }
 

crates/lsp/src/lsp.rs πŸ”—

@@ -429,8 +429,8 @@ impl LanguageServer {
         let root_uri = Url::from_file_path(&self.root_path).unwrap();
         #[allow(deprecated)]
         let params = InitializeParams {
-            process_id: Default::default(),
-            root_path: Default::default(),
+            process_id: None,
+            root_path: None,
             root_uri: Some(root_uri.clone()),
             initialization_options: options,
             capabilities: ClientCapabilities {
@@ -451,12 +451,15 @@ impl LanguageServer {
                     inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
                         refresh_support: Some(true),
                     }),
+                    diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
+                        refresh_support: None,
+                    }),
                     ..Default::default()
                 }),
                 text_document: Some(TextDocumentClientCapabilities {
                     definition: Some(GotoCapability {
                         link_support: Some(true),
-                        ..Default::default()
+                        dynamic_registration: None,
                     }),
                     code_action: Some(CodeActionClientCapabilities {
                         code_action_literal_support: Some(CodeActionLiteralSupport {
@@ -501,7 +504,7 @@ impl LanguageServer {
                     }),
                     hover: Some(HoverClientCapabilities {
                         content_format: Some(vec![MarkupKind::Markdown]),
-                        ..Default::default()
+                        dynamic_registration: None,
                     }),
                     inlay_hint: Some(InlayHintClientCapabilities {
                         resolve_support: Some(InlayHintResolveClientCapabilities {
@@ -515,6 +518,20 @@ impl LanguageServer {
                         }),
                         dynamic_registration: Some(false),
                     }),
+                    publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
+                        related_information: Some(true),
+                        ..Default::default()
+                    }),
+                    formatting: Some(DynamicRegistrationClientCapabilities {
+                        dynamic_registration: None,
+                    }),
+                    on_type_formatting: Some(DynamicRegistrationClientCapabilities {
+                        dynamic_registration: None,
+                    }),
+                    diagnostic: Some(DiagnosticClientCapabilities {
+                        related_document_support: Some(true),
+                        dynamic_registration: None,
+                    }),
                     ..Default::default()
                 }),
                 experimental: Some(json!({
@@ -524,15 +541,15 @@ impl LanguageServer {
                     work_done_progress: Some(true),
                     ..Default::default()
                 }),
-                ..Default::default()
+                general: None,
             },
-            trace: Default::default(),
+            trace: None,
             workspace_folders: Some(vec![WorkspaceFolder {
                 uri: root_uri,
                 name: Default::default(),
             }]),
-            client_info: Default::default(),
-            locale: Default::default(),
+            client_info: None,
+            locale: None,
         };
 
         let response = self.request::<request::Initialize>(params).await?;

crates/lsp2/src/lsp2.rs πŸ”—

@@ -434,8 +434,8 @@ impl LanguageServer {
         let root_uri = Url::from_file_path(&self.root_path).unwrap();
         #[allow(deprecated)]
         let params = InitializeParams {
-            process_id: Default::default(),
-            root_path: Default::default(),
+            process_id: None,
+            root_path: None,
             root_uri: Some(root_uri.clone()),
             initialization_options: options,
             capabilities: ClientCapabilities {
@@ -456,12 +456,15 @@ impl LanguageServer {
                     inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
                         refresh_support: Some(true),
                     }),
+                    diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
+                        refresh_support: None,
+                    }),
                     ..Default::default()
                 }),
                 text_document: Some(TextDocumentClientCapabilities {
                     definition: Some(GotoCapability {
                         link_support: Some(true),
-                        ..Default::default()
+                        dynamic_registration: None,
                     }),
                     code_action: Some(CodeActionClientCapabilities {
                         code_action_literal_support: Some(CodeActionLiteralSupport {
@@ -503,7 +506,7 @@ impl LanguageServer {
                     }),
                     hover: Some(HoverClientCapabilities {
                         content_format: Some(vec![MarkupKind::Markdown]),
-                        ..Default::default()
+                        dynamic_registration: None,
                     }),
                     inlay_hint: Some(InlayHintClientCapabilities {
                         resolve_support: Some(InlayHintResolveClientCapabilities {
@@ -517,6 +520,20 @@ impl LanguageServer {
                         }),
                         dynamic_registration: Some(false),
                     }),
+                    publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
+                        related_information: Some(true),
+                        ..Default::default()
+                    }),
+                    formatting: Some(DynamicRegistrationClientCapabilities {
+                        dynamic_registration: None,
+                    }),
+                    on_type_formatting: Some(DynamicRegistrationClientCapabilities {
+                        dynamic_registration: None,
+                    }),
+                    diagnostic: Some(DiagnosticClientCapabilities {
+                        related_document_support: Some(true),
+                        dynamic_registration: None,
+                    }),
                     ..Default::default()
                 }),
                 experimental: Some(json!({
@@ -526,15 +543,15 @@ impl LanguageServer {
                     work_done_progress: Some(true),
                     ..Default::default()
                 }),
-                ..Default::default()
+                general: None,
             },
-            trace: Default::default(),
+            trace: None,
             workspace_folders: Some(vec![WorkspaceFolder {
                 uri: root_uri,
                 name: Default::default(),
             }]),
-            client_info: Default::default(),
-            locale: Default::default(),
+            client_info: None,
+            locale: None,
         };
 
         let response = self.request::<request::Initialize>(params).await?;

crates/project/src/project.rs πŸ”—

@@ -2641,8 +2641,9 @@ impl Project {
                 });
 
                 for (adapter, server) in servers {
-                    let workspace_config =
-                        cx.update(|cx| adapter.workspace_configuration(cx)).await;
+                    let workspace_config = cx
+                        .update(|cx| adapter.workspace_configuration(server.root_path(), cx))
+                        .await;
                     server
                         .notify::<lsp::notification::DidChangeConfiguration>(
                             lsp::DidChangeConfigurationParams {
@@ -2753,7 +2754,7 @@ impl Project {
             stderr_capture.clone(),
             language.clone(),
             adapter.clone(),
-            worktree_path,
+            Arc::clone(&worktree_path),
             ProjectLspAdapterDelegate::new(self, cx),
             cx,
         ) {
@@ -2776,6 +2777,7 @@ impl Project {
             cx.spawn_weak(|this, mut cx| async move {
                 let result = Self::setup_and_insert_language_server(
                     this,
+                    &worktree_path,
                     override_options,
                     pending_server,
                     adapter.clone(),
@@ -2891,6 +2893,7 @@ impl Project {
 
     async fn setup_and_insert_language_server(
         this: WeakModelHandle<Self>,
+        worktree_path: &Path,
         override_initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         adapter: Arc<CachedLspAdapter>,
@@ -2903,6 +2906,7 @@ impl Project {
             this,
             override_initialization_options,
             pending_server,
+            worktree_path,
             adapter.clone(),
             server_id,
             cx,
@@ -2932,11 +2936,14 @@ impl Project {
         this: WeakModelHandle<Self>,
         override_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
+        worktree_path: &Path,
         adapter: Arc<CachedLspAdapter>,
         server_id: LanguageServerId,
         cx: &mut AsyncAppContext,
     ) -> Result<Arc<LanguageServer>> {
-        let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx)).await;
+        let workspace_config = cx
+            .update(|cx| adapter.workspace_configuration(worktree_path, cx))
+            .await;
         let language_server = pending_server.task.await?;
 
         language_server
@@ -2964,11 +2971,14 @@ impl Project {
         language_server
             .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
                 let adapter = adapter.clone();
+                let worktree_path = worktree_path.to_path_buf();
                 move |params, mut cx| {
                     let adapter = adapter.clone();
+                    let worktree_path = worktree_path.clone();
                     async move {
-                        let workspace_config =
-                            cx.update(|cx| adapter.workspace_configuration(cx)).await;
+                        let workspace_config = cx
+                            .update(|cx| adapter.workspace_configuration(&worktree_path, cx))
+                            .await;
                         Ok(params
                             .items
                             .into_iter()
@@ -6523,9 +6533,15 @@ impl Project {
             })
     }
 
-    pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
+    pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
         let mut summary = DiagnosticSummary::default();
-        for (_, _, path_summary) in self.diagnostic_summaries(cx) {
+        for (_, _, path_summary) in
+            self.diagnostic_summaries(include_ignored, cx)
+                .filter(|(path, _, _)| {
+                    let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+                    include_ignored || worktree == Some(false)
+                })
+        {
             summary.error_count += path_summary.error_count;
             summary.warning_count += path_summary.warning_count;
         }
@@ -6534,6 +6550,7 @@ impl Project {
 
     pub fn diagnostic_summaries<'a>(
         &'a self,
+        include_ignored: bool,
         cx: &'a AppContext,
     ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
         self.visible_worktrees(cx).flat_map(move |worktree| {
@@ -6544,6 +6561,10 @@ impl Project {
                 .map(move |(path, server_id, summary)| {
                     (ProjectPath { worktree_id, path }, server_id, summary)
                 })
+                .filter(move |(path, _, _)| {
+                    let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+                    include_ignored || worktree == Some(false)
+                })
         })
     }
 

crates/project/src/project_tests.rs πŸ”—

@@ -806,7 +806,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
+async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let fs = FakeFs::new(cx.background());
@@ -814,7 +814,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
         "/root",
         json!({
             "dir": {
+                ".git": {
+                    "HEAD": "ref: refs/heads/main",
+                },
+                ".gitignore": "b.rs",
                 "a.rs": "let a = 1;",
+                "b.rs": "let b = 2;",
             },
             "other.rs": "let b = c;"
         }),
@@ -822,6 +827,13 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     .await;
 
     let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
+    let (worktree, _) = project
+        .update(cx, |project, cx| {
+            project.find_or_create_local_worktree("/root/dir", true, cx)
+        })
+        .await
+        .unwrap();
+    let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
 
     let (worktree, _) = project
         .update(cx, |project, cx| {
@@ -829,12 +841,30 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+    let other_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
 
+    let server_id = LanguageServerId(0);
     project.update(cx, |project, cx| {
         project
             .update_diagnostics(
-                LanguageServerId(0),
+                server_id,
+                lsp::PublishDiagnosticsParams {
+                    uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
+                    version: None,
+                    diagnostics: vec![lsp::Diagnostic {
+                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
+                        severity: Some(lsp::DiagnosticSeverity::ERROR),
+                        message: "unused variable 'b'".to_string(),
+                        ..Default::default()
+                    }],
+                },
+                &[],
+                cx,
+            )
+            .unwrap();
+        project
+            .update_diagnostics(
+                server_id,
                 lsp::PublishDiagnosticsParams {
                     uri: Url::from_file_path("/root/other.rs").unwrap(),
                     version: None,
@@ -851,11 +881,34 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             .unwrap();
     });
 
-    let buffer = project
-        .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+    let main_ignored_buffer = project
+        .update(cx, |project, cx| {
+            project.open_buffer((main_worktree_id, "b.rs"), cx)
+        })
         .await
         .unwrap();
-    buffer.read_with(cx, |buffer, _| {
+    main_ignored_buffer.read_with(cx, |buffer, _| {
+        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
+        assert_eq!(
+            chunks
+                .iter()
+                .map(|(s, d)| (s.as_str(), *d))
+                .collect::<Vec<_>>(),
+            &[
+                ("let ", None),
+                ("b", Some(DiagnosticSeverity::ERROR)),
+                (" = 2;", None),
+            ],
+            "Gigitnored buffers should still get in-buffer diagnostics",
+        );
+    });
+    let other_buffer = project
+        .update(cx, |project, cx| {
+            project.open_buffer((other_worktree_id, ""), cx)
+        })
+        .await
+        .unwrap();
+    other_buffer.read_with(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -866,13 +919,29 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
                 ("let b = ", None),
                 ("c", Some(DiagnosticSeverity::ERROR)),
                 (";", None),
-            ]
+            ],
+            "Buffers from hidden projects should still get in-buffer diagnostics"
         );
     });
 
     project.read_with(cx, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).next(), None);
-        assert_eq!(project.diagnostic_summary(cx).error_count, 0);
+        assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
+        assert_eq!(
+            project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
+            vec![(
+                ProjectPath {
+                    worktree_id: main_worktree_id,
+                    path: Arc::from(Path::new("b.rs")),
+                },
+                server_id,
+                DiagnosticSummary {
+                    error_count: 1,
+                    warning_count: 0,
+                }
+            )]
+        );
+        assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
+        assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
     });
 }
 
@@ -1145,7 +1214,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
     project.read_with(cx, |project, cx| {
         assert_eq!(
-            project.diagnostic_summary(cx),
+            project.diagnostic_summary(false, cx),
             DiagnosticSummary {
                 error_count: 1,
                 warning_count: 0,
@@ -1171,7 +1240,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
     project.read_with(cx, |project, cx| {
         assert_eq!(
-            project.diagnostic_summary(cx),
+            project.diagnostic_summary(false, cx),
             DiagnosticSummary {
                 error_count: 0,
                 warning_count: 0,
@@ -1763,7 +1832,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
             .unwrap();
 
         assert_eq!(
-            project.diagnostic_summary(cx),
+            project.diagnostic_summary(false, cx),
             DiagnosticSummary {
                 error_count: 2,
                 warning_count: 0,

crates/project2/src/lsp_command.rs πŸ”—

@@ -717,8 +717,9 @@ async fn location_links_from_lsp(
             })?
             .await?;
 
-        buffer.update(&mut cx, |origin_buffer, cx| {
+        cx.update(|cx| {
             let origin_location = origin_range.map(|origin_range| {
+                let origin_buffer = buffer.read(cx);
                 let origin_start =
                     origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
                 let origin_end =

crates/project2/src/project2.rs πŸ”—

@@ -2677,8 +2677,9 @@ impl Project {
                 })?;
 
                 for (adapter, server) in servers {
-                    let workspace_config =
-                        cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+                    let workspace_config = cx
+                        .update(|cx| adapter.workspace_configuration(server.root_path(), cx))?
+                        .await;
                     server
                         .notify::<lsp::notification::DidChangeConfiguration>(
                             lsp::DidChangeConfigurationParams {
@@ -2790,7 +2791,7 @@ impl Project {
             stderr_capture.clone(),
             language.clone(),
             adapter.clone(),
-            worktree_path,
+            Arc::clone(&worktree_path),
             ProjectLspAdapterDelegate::new(self, cx),
             cx,
         ) {
@@ -2822,6 +2823,7 @@ impl Project {
             cx.spawn(move |this, mut cx| async move {
                 let result = Self::setup_and_insert_language_server(
                     this.clone(),
+                    &worktree_path,
                     initialization_options,
                     pending_server,
                     adapter.clone(),
@@ -2942,6 +2944,7 @@ impl Project {
 
     async fn setup_and_insert_language_server(
         this: WeakModel<Self>,
+        worktree_path: &Path,
         initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         adapter: Arc<CachedLspAdapter>,
@@ -2954,6 +2957,7 @@ impl Project {
             this.clone(),
             initialization_options,
             pending_server,
+            worktree_path,
             adapter.clone(),
             server_id,
             cx,
@@ -2983,11 +2987,14 @@ impl Project {
         this: WeakModel<Self>,
         initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
+        worktree_path: &Path,
         adapter: Arc<CachedLspAdapter>,
         server_id: LanguageServerId,
         cx: &mut AsyncAppContext,
     ) -> Result<Arc<LanguageServer>> {
-        let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+        let workspace_config = cx
+            .update(|cx| adapter.workspace_configuration(worktree_path, cx))?
+            .await;
         let language_server = pending_server.task.await?;
 
         language_server
@@ -3016,11 +3023,14 @@ impl Project {
         language_server
             .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
                 let adapter = adapter.clone();
+                let worktree_path = worktree_path.to_path_buf();
                 move |params, cx| {
                     let adapter = adapter.clone();
+                    let worktree_path = worktree_path.clone();
                     async move {
-                        let workspace_config =
-                            cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+                        let workspace_config = cx
+                            .update(|cx| adapter.workspace_configuration(&worktree_path, cx))?
+                            .await;
                         Ok(params
                             .items
                             .into_iter()
@@ -6596,9 +6606,15 @@ impl Project {
             })
     }
 
-    pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
+    pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
         let mut summary = DiagnosticSummary::default();
-        for (_, _, path_summary) in self.diagnostic_summaries(cx) {
+        for (_, _, path_summary) in
+            self.diagnostic_summaries(include_ignored, cx)
+                .filter(|(path, _, _)| {
+                    let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+                    include_ignored || worktree == Some(false)
+                })
+        {
             summary.error_count += path_summary.error_count;
             summary.warning_count += path_summary.warning_count;
         }
@@ -6607,17 +6623,23 @@ impl Project {
 
     pub fn diagnostic_summaries<'a>(
         &'a self,
+        include_ignored: bool,
         cx: &'a AppContext,
     ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
-        self.visible_worktrees(cx).flat_map(move |worktree| {
-            let worktree = worktree.read(cx);
-            let worktree_id = worktree.id();
-            worktree
-                .diagnostic_summaries()
-                .map(move |(path, server_id, summary)| {
-                    (ProjectPath { worktree_id, path }, server_id, summary)
-                })
-        })
+        self.visible_worktrees(cx)
+            .flat_map(move |worktree| {
+                let worktree = worktree.read(cx);
+                let worktree_id = worktree.id();
+                worktree
+                    .diagnostic_summaries()
+                    .map(move |(path, server_id, summary)| {
+                        (ProjectPath { worktree_id, path }, server_id, summary)
+                    })
+            })
+            .filter(move |(path, _, _)| {
+                let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+                include_ignored || worktree == Some(false)
+            })
     }
 
     pub fn disk_based_diagnostics_started(

crates/project2/src/project_tests.rs πŸ”—

@@ -823,7 +823,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
+async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let fs = FakeFs::new(cx.executor());
@@ -831,7 +831,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
         "/root",
         json!({
             "dir": {
+                ".git": {
+                    "HEAD": "ref: refs/heads/main",
+                },
+                ".gitignore": "b.rs",
                 "a.rs": "let a = 1;",
+                "b.rs": "let b = 2;",
             },
             "other.rs": "let b = c;"
         }),
@@ -839,6 +844,13 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     .await;
 
     let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
+    let (worktree, _) = project
+        .update(cx, |project, cx| {
+            project.find_or_create_local_worktree("/root/dir", true, cx)
+        })
+        .await
+        .unwrap();
+    let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
 
     let (worktree, _) = project
         .update(cx, |project, cx| {
@@ -846,12 +858,30 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    let worktree_id = worktree.update(cx, |tree, _| tree.id());
+    let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
 
+    let server_id = LanguageServerId(0);
     project.update(cx, |project, cx| {
         project
             .update_diagnostics(
-                LanguageServerId(0),
+                server_id,
+                lsp::PublishDiagnosticsParams {
+                    uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
+                    version: None,
+                    diagnostics: vec![lsp::Diagnostic {
+                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
+                        severity: Some(lsp::DiagnosticSeverity::ERROR),
+                        message: "unused variable 'b'".to_string(),
+                        ..Default::default()
+                    }],
+                },
+                &[],
+                cx,
+            )
+            .unwrap();
+        project
+            .update_diagnostics(
+                server_id,
                 lsp::PublishDiagnosticsParams {
                     uri: Url::from_file_path("/root/other.rs").unwrap(),
                     version: None,
@@ -868,11 +898,34 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             .unwrap();
     });
 
-    let buffer = project
-        .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+    let main_ignored_buffer = project
+        .update(cx, |project, cx| {
+            project.open_buffer((main_worktree_id, "b.rs"), cx)
+        })
         .await
         .unwrap();
-    buffer.update(cx, |buffer, _| {
+    main_ignored_buffer.update(cx, |buffer, _| {
+        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
+        assert_eq!(
+            chunks
+                .iter()
+                .map(|(s, d)| (s.as_str(), *d))
+                .collect::<Vec<_>>(),
+            &[
+                ("let ", None),
+                ("b", Some(DiagnosticSeverity::ERROR)),
+                (" = 2;", None),
+            ],
+            "Gigitnored buffers should still get in-buffer diagnostics",
+        );
+    });
+    let other_buffer = project
+        .update(cx, |project, cx| {
+            project.open_buffer((other_worktree_id, ""), cx)
+        })
+        .await
+        .unwrap();
+    other_buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -883,13 +936,29 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
                 ("let b = ", None),
                 ("c", Some(DiagnosticSeverity::ERROR)),
                 (";", None),
-            ]
+            ],
+            "Buffers from hidden projects should still get in-buffer diagnostics"
         );
     });
 
     project.update(cx, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(cx).next(), None);
-        assert_eq!(project.diagnostic_summary(cx).error_count, 0);
+        assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
+        assert_eq!(
+            project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
+            vec![(
+                ProjectPath {
+                    worktree_id: main_worktree_id,
+                    path: Arc::from(Path::new("b.rs")),
+                },
+                server_id,
+                DiagnosticSummary {
+                    error_count: 1,
+                    warning_count: 0,
+                }
+            )]
+        );
+        assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
+        assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
     });
 }
 
@@ -1162,7 +1231,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
     project.update(cx, |project, cx| {
         assert_eq!(
-            project.diagnostic_summary(cx),
+            project.diagnostic_summary(false, cx),
             DiagnosticSummary {
                 error_count: 1,
                 warning_count: 0,
@@ -1188,7 +1257,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
     project.update(cx, |project, cx| {
         assert_eq!(
-            project.diagnostic_summary(cx),
+            project.diagnostic_summary(false, cx),
             DiagnosticSummary {
                 error_count: 0,
                 warning_count: 0,
@@ -1777,7 +1846,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
             .unwrap();
 
         assert_eq!(
-            project.diagnostic_summary(cx),
+            project.diagnostic_summary(false, cx),
             DiagnosticSummary {
                 error_count: 2,
                 warning_count: 0,

crates/project_panel/src/project_panel.rs πŸ”—

@@ -1627,9 +1627,21 @@ impl View for ProjectPanel {
         }
     }
 
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+    fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
         Self::reset_to_default_keymap_context(keymap);
         keymap.add_identifier("menu");
+
+        if let Some(window) = cx.active_window() {
+            window.read_with(cx, |cx| {
+                let identifier = if self.filename_editor.is_focused(cx) {
+                    "editing"
+                } else {
+                    "not_editing"
+                };
+
+                keymap.add_identifier(identifier);
+            });
+        }
     }
 
     fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {

crates/project_panel2/src/project_panel.rs πŸ”—

@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
 use gpui::{
     actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
     ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
-    InteractiveElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
-    PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
-    ViewContext, VisualContext as _, WeakView, WindowContext,
+    InteractiveElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels,
+    Point, PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle,
+    View, ViewContext, VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
@@ -29,8 +29,7 @@ use std::{
     path::Path,
     sync::Arc,
 };
-use theme::ActiveTheme as _;
-use ui::{v_stack, ContextMenu, IconElement, Label, ListItem};
+use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
 use unicase::UniCase;
 use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
@@ -1421,6 +1420,22 @@ impl ProjectPanel {
         //     );
         // })
     }
+
+    fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
+        let mut dispatch_context = KeyContext::default();
+        dispatch_context.add("ProjectPanel");
+        dispatch_context.add("menu");
+
+        let identifier = if self.filename_editor.focus_handle(cx).is_focused(cx) {
+            "editing"
+        } else {
+            "not_editing"
+        };
+
+        dispatch_context.add(identifier);
+
+        dispatch_context
+    }
 }
 
 impl Render for ProjectPanel {
@@ -1434,7 +1449,7 @@ impl Render for ProjectPanel {
                 .id("project-panel")
                 .size_full()
                 .relative()
-                .key_context("ProjectPanel")
+                .key_context(self.dispatch_context(cx))
                 .on_action(cx.listener(Self::select_next))
                 .on_action(cx.listener(Self::select_prev))
                 .on_action(cx.listener(Self::expand_selected_entry))
@@ -1480,7 +1495,7 @@ impl Render for ProjectPanel {
                 .children(self.context_menu.as_ref().map(|(menu, position, _)| {
                     overlay()
                         .position(*position)
-                        .anchor(gpui::AnchorCorner::BottomLeft)
+                        .anchor(gpui::AnchorCorner::TopLeft)
                         .child(menu.clone())
                 }))
         } else {
@@ -2845,7 +2860,7 @@ mod tests {
                 let worktree = worktree.read(cx);
                 if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
                     let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
-                    panel.selection = Some(Selection {
+                    panel.selection = Some(crate::Selection {
                         worktree_id: worktree.id(),
                         entry_id,
                     });

crates/rpc2/src/rpc.rs πŸ”—

@@ -9,4 +9,4 @@ pub use notification::*;
 pub use peer::*;
 mod macros;
 
-pub const PROTOCOL_VERSION: u32 = 64;
+pub const PROTOCOL_VERSION: u32 = 66;

crates/search2/src/buffer_search.rs πŸ”—

@@ -18,7 +18,7 @@ use project::search::SearchQuery;
 use serde::Deserialize;
 use std::{any::Any, sync::Arc};
 
-use ui::{h_stack, ButtonGroup, Icon, IconButton, IconElement};
+use ui::{h_stack, Icon, IconButton, IconElement};
 use util::ResultExt;
 use workspace::{
     item::ItemHandle,
@@ -214,10 +214,11 @@ impl Render for BufferSearchBar {
             .child(
                 h_stack()
                     .flex_none()
-                    .child(ButtonGroup::new(vec![
-                        search_button_for_mode(SearchMode::Text),
-                        search_button_for_mode(SearchMode::Regex),
-                    ]))
+                    .child(
+                        h_stack()
+                            .child(search_button_for_mode(SearchMode::Text))
+                            .child(search_button_for_mode(SearchMode::Regex)),
+                    )
                     .when(supported_options.replacement, |this| {
                         this.child(super::toggle_replace_button(self.replace_enabled))
                     }),
@@ -586,8 +587,7 @@ impl BufferSearchBar {
 
         // let style = theme.search.action_button.clone();
 
-        IconButton::new(0, ui::Icon::SelectAll)
-            .on_click(|_, cx| cx.dispatch_action(Box::new(SelectAllMatches)))
+        IconButton::new(0, ui::Icon::SelectAll).action(Box::new(SelectAllMatches))
     }
 
     pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {

crates/search2/src/search.rs πŸ”—

@@ -3,7 +3,8 @@ pub use buffer_search::BufferSearchBar;
 use gpui::{actions, Action, AppContext, IntoElement};
 pub use mode::SearchMode;
 use project::search::SearchQuery;
-use ui::ButtonVariant;
+use ui::prelude::*;
+use ui::{ButtonStyle, Icon, IconButton};
 //pub use project_search::{ProjectSearchBar, ProjectSearchView};
 // use theme::components::{
 //     action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
@@ -83,35 +84,35 @@ impl SearchOptions {
     }
 
     pub fn as_button(&self, active: bool) -> impl IntoElement {
-        ui::IconButton::new(0, self.icon())
+        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))
+            .style(ButtonStyle::Subtle)
+            .when(active, |button| button.style(ButtonStyle::Filled))
     }
 }
 
 fn toggle_replace_button(active: bool) -> impl IntoElement {
     // todo: add toggle_replace button
-    ui::IconButton::new(0, ui::Icon::Replace)
+    IconButton::new(0, Icon::Replace)
         .on_click(|_, cx| {
             cx.dispatch_action(Box::new(ToggleReplace));
             cx.notify();
         })
-        .variant(ui::ButtonVariant::Ghost)
-        .when(active, |button| button.variant(ButtonVariant::Filled))
+        .style(ButtonStyle::Subtle)
+        .when(active, |button| button.style(ButtonStyle::Filled))
 }
 
 fn render_replace_button(
     action: impl Action + 'static + Send + Sync,
-    icon: ui::Icon,
+    icon: Icon,
 ) -> impl IntoElement {
     // todo: add tooltip
-    ui::IconButton::new(0, icon).on_click(move |_, cx| {
+    IconButton::new(0, icon).on_click(move |_, cx| {
         cx.dispatch_action(action.boxed_clone());
     })
 }

crates/search2/src/search_bar.rs πŸ”—

@@ -1,5 +1,6 @@
 use gpui::{ClickEvent, IntoElement, WindowContext};
-use ui::{Button, ButtonVariant, IconButton};
+use ui::prelude::*;
+use ui::{Button, IconButton};
 
 use crate::mode::SearchMode;
 
@@ -23,13 +24,7 @@ pub(crate) fn render_search_mode_button(
     is_active: bool,
     on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
 ) -> Button {
-    let button_variant = if is_active {
-        ButtonVariant::Filled
-    } else {
-        ButtonVariant::Ghost
-    };
-
-    Button::new(mode.label())
+    Button::new(mode.label(), mode.label())
+        .selected(is_active)
         .on_click(on_click)
-        .variant(button_variant)
 }

crates/storybook2/src/stories.rs πŸ”—

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

crates/storybook2/src/stories/auto_height_editor.rs πŸ”—

@@ -0,0 +1,34 @@
+use editor::Editor;
+use gpui::{
+    div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext,
+    WindowContext,
+};
+
+pub struct AutoHeightEditorStory {
+    editor: View<Editor>,
+}
+
+impl AutoHeightEditorStory {
+    pub fn new(cx: &mut WindowContext) -> View<Self> {
+        cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
+        cx.build_view(|cx| Self {
+            editor: cx.build_view(|cx| {
+                let mut editor = Editor::auto_height(3, cx);
+                editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+                editor
+            }),
+        })
+    }
+}
+
+impl Render for AutoHeightEditorStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        div()
+            .size_full()
+            .bg(white())
+            .text_sm()
+            .child(div().w_32().bg(gpui::black()).child(self.editor.clone()))
+    }
+}

crates/storybook2/src/stories/text.rs πŸ”—

@@ -1,6 +1,6 @@
 use gpui::{
-    blue, div, green, red, white, Div, InteractiveText, ParentElement, Render, Styled, StyledText,
-    TextRun, View, VisualContext, WindowContext,
+    blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render,
+    Styled, StyledText, View, VisualContext, WindowContext,
 };
 use ui::v_stack;
 
@@ -59,13 +59,11 @@ impl Render for TextStory {
             ))).child(
                 InteractiveText::new(
                     "interactive",
-                    StyledText::new("Hello world, how is it going?").with_runs(vec![
-                        cx.text_style().to_run(6),
-                        TextRun {
+                    StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
+                        (6..11, HighlightStyle {
                             background_color: Some(green()),
-                            ..cx.text_style().to_run(5)
-                        },
-                        cx.text_style().to_run(18),
+                            ..Default::default()
+                        }),
                     ]),
                 )
                 .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {

crates/storybook2/src/story_selector.rs πŸ”—

@@ -12,16 +12,19 @@ use ui::prelude::*;
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 pub enum ComponentStory {
+    AutoHeightEditor,
     Avatar,
     Button,
     Checkbox,
     ContextMenu,
+    Disclosure,
     Focus,
     Icon,
     IconButton,
     Keybinding,
     Label,
     List,
+    ListHeader,
     ListItem,
     Scroll,
     Text,
@@ -32,16 +35,19 @@ pub enum ComponentStory {
 impl ComponentStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
+            Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
             Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(),
             Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
             Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
             Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
+            Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(),
             Self::Focus => FocusStory::view(cx).into(),
             Self::Icon => cx.build_view(|_| ui::IconStory).into(),
             Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
             Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
             Self::Label => cx.build_view(|_| ui::LabelStory).into(),
             Self::List => cx.build_view(|_| ui::ListStory).into(),
+            Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
             Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
             Self::Scroll => ScrollStory::view(cx).into(),
             Self::Text => TextStory::view(cx).into(),

crates/theme2/src/styles/stories/players.rs πŸ”—

@@ -55,9 +55,8 @@ impl Render for PlayerStory {
                             .border_2()
                             .border_color(player.cursor)
                             .child(
-                                img()
+                                img("https://avatars.githubusercontent.com/u/1714999?v=4")
                                     .rounded_full()
-                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
                                     .size_6()
                                     .bg(gpui::red()),
                             )
@@ -67,51 +66,62 @@ impl Render for PlayerStory {
                 .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)
+                            .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()
+                                        img("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                            .rounded_full()
+                                            .size(px(24.))
+                                            .bg(gpui::red()),
+                                    ),
+                            )
+                            .child(
+                                div()
+                                    .relative()
+                                    .neg_mx_1()
                                     .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.))
+                                    .z_index(2)
+                                    .border_2()
+                                    .border_color(player.background)
+                                    .size(px(28.))
+                                    .child(
+                                        img("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                            .rounded_full()
+                                            .size(px(24.))
+                                            .bg(gpui::red()),
+                                    ),
+                            )
                             .child(
-                            img()
-                                .rounded_full()
-                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                .size(px(24.))
-                                .bg(gpui::red()),
-                        ),
-                    )
+                                div()
+                                    .relative()
+                                    .neg_mx_1()
+                                    .rounded_full()
+                                    .z_index(1)
+                                    .border_2()
+                                    .border_color(player.background)
+                                    .size(px(28.))
+                                    .child(
+                                        img("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                            .rounded_full()
+                                            .size(px(24.))
+                                            .bg(gpui::red()),
+                                    ),
+                            )
                     }),
                 ))
                 .child(Story::label("Player Selections"))

crates/theme_selector2/src/theme_selector.rs πŸ”—

@@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
-    SharedString, View, ViewContext, VisualContext, WeakView,
+    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View,
+    ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use settings::{update_settings_file, SettingsStore};
 use std::sync::Arc;
-use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
-use ui::ListItem;
+use theme::{Theme, ThemeRegistry, ThemeSettings};
+use ui::{prelude::*, ListItem};
 use util::ResultExt;
 use workspace::{ui::HighlightedLabel, Workspace};
 

crates/ui2/src/components.rs πŸ”—

@@ -1,19 +1,17 @@
 mod avatar;
 mod button;
-mod button2;
 mod checkbox;
 mod context_menu;
 mod disclosure;
 mod divider;
 mod icon;
-mod icon_button;
 mod keybinding;
 mod label;
 mod list;
 mod popover;
-mod slot;
+mod popover_menu;
+mod right_click_menu;
 mod stack;
-mod toggle;
 mod tooltip;
 
 #[cfg(feature = "stories")]
@@ -21,20 +19,18 @@ mod stories;
 
 pub use avatar::*;
 pub use button::*;
-pub use button2::*;
 pub use checkbox::*;
 pub use context_menu::*;
 pub use disclosure::*;
 pub use divider::*;
 pub use icon::*;
-pub use icon_button::*;
 pub use keybinding::*;
 pub use label::*;
 pub use list::*;
 pub use popover::*;
-pub use slot::*;
+pub use popover_menu::*;
+pub use right_click_menu::*;
 pub use stack::*;
-pub use toggle::*;
 pub use tooltip::*;
 
 #[cfg(feature = "stories")]

crates/ui2/src/components/avatar.rs πŸ”—

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use crate::prelude::*;
-use gpui::{img, ImageData, ImageSource, Img, IntoElement};
+use gpui::{img, rems, Div, ImageData, ImageSource, IntoElement, Styled};
 
 #[derive(Debug, Default, PartialEq, Clone)]
 pub enum Shape {
@@ -13,14 +13,15 @@ pub enum Shape {
 #[derive(IntoElement)]
 pub struct Avatar {
     src: ImageSource,
+    is_available: Option<bool>,
     shape: Shape,
 }
 
 impl RenderOnce for Avatar {
-    type Rendered = Img;
+    type Rendered = Div;
 
-    fn render(self, _: &mut WindowContext) -> Self::Rendered {
-        let mut img = img();
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        let mut img = img(self.src);
 
         if self.shape == Shape::Circle {
             img = img.rounded_full();
@@ -28,10 +29,28 @@ impl RenderOnce for Avatar {
             img = img.rounded_md();
         }
 
-        img.source(self.src.clone())
-            .size_4()
-            // todo!(Pull the avatar fallback background from the theme.)
-            .bg(gpui::red())
+        let size = rems(1.0);
+
+        div()
+            .size(size)
+            .child(
+                img.size(size)
+                    // todo!(Pull the avatar fallback background from the theme.)
+                    .bg(gpui::red()),
+            )
+            .children(self.is_available.map(|is_free| {
+                // HACK: non-integer sizes result in oval indicators.
+                let indicator_size = (size.0 * cx.rem_size() * 0.4).round();
+
+                div()
+                    .absolute()
+                    .z_index(1)
+                    .bg(if is_free { gpui::green() } else { gpui::red() })
+                    .size(indicator_size)
+                    .rounded(indicator_size)
+                    .bottom_0()
+                    .right_0()
+            }))
     }
 }
 
@@ -40,12 +59,14 @@ impl Avatar {
         Self {
             src: src.into().into(),
             shape: Shape::Circle,
+            is_available: None,
         }
     }
     pub fn data(src: Arc<ImageData>) -> Self {
         Self {
             src: src.into(),
             shape: Shape::Circle,
+            is_available: None,
         }
     }
 
@@ -53,10 +74,15 @@ impl Avatar {
         Self {
             src,
             shape: Shape::Circle,
+            is_available: None,
         }
     }
     pub fn shape(mut self, shape: Shape) -> Self {
         self.shape = shape;
         self
     }
+    pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
+        self.is_available = is_available.into();
+        self
+    }
 }

crates/ui2/src/components/button.rs πŸ”—

@@ -1,228 +1,8 @@
-use gpui::{
-    ClickEvent, DefiniteLength, Div, Hsla, IntoElement, StatefulInteractiveElement, WindowContext,
-};
-use std::rc::Rc;
-
-use crate::prelude::*;
-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 {
-    Button(Button),
-    IconButton(IconButton),
-}
-
-impl From<Button> for ButtonOrIconButton {
-    fn from(value: Button) -> Self {
-        Self::Button(value)
-    }
-}
-
-impl From<IconButton> for ButtonOrIconButton {
-    fn from(value: IconButton) -> Self {
-        Self::IconButton(value)
-    }
-}
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum IconPosition {
-    #[default]
-    Left,
-    Right,
-}
-
-#[derive(Default, Copy, Clone, PartialEq)]
-pub enum ButtonVariant {
-    #[default]
-    Ghost,
-    Filled,
-}
-
-impl ButtonVariant {
-    pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla {
-        match self {
-            ButtonVariant::Ghost => cx.theme().colors().ghost_element_background,
-            ButtonVariant::Filled => cx.theme().colors().element_background,
-        }
-    }
-
-    pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla {
-        match self {
-            ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover,
-            ButtonVariant::Filled => cx.theme().colors().element_hover,
-        }
-    }
-
-    pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla {
-        match self {
-            ButtonVariant::Ghost => cx.theme().colors().ghost_element_active,
-            ButtonVariant::Filled => cx.theme().colors().element_active,
-        }
-    }
-}
-
-#[derive(IntoElement)]
-pub struct Button {
-    disabled: bool,
-    click_handler: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
-    icon: Option<Icon>,
-    icon_position: Option<IconPosition>,
-    label: SharedString,
-    variant: ButtonVariant,
-    width: Option<DefiniteLength>,
-    color: Option<Color>,
-}
-
-impl RenderOnce for Button {
-    type Rendered = gpui::Stateful<Div>;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let (icon_color, label_color) = match (self.disabled, self.color) {
-            (true, _) => (Color::Disabled, Color::Disabled),
-            (_, None) => (Color::Default, Color::Default),
-            (_, Some(color)) => (Color::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.click_handler.clone() {
-            button = button.on_click(move |event, cx| {
-                click_handler(event, cx);
-            });
-        }
-
-        button
-    }
-}
-
-impl Button {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            disabled: false,
-            click_handler: None,
-            icon: None,
-            icon_position: None,
-            label: label.into(),
-            variant: Default::default(),
-            width: Default::default(),
-            color: None,
-        }
-    }
-
-    pub fn ghost(label: impl Into<SharedString>) -> Self {
-        Self::new(label).variant(ButtonVariant::Ghost)
-    }
-
-    pub fn variant(mut self, variant: ButtonVariant) -> Self {
-        self.variant = variant;
-        self
-    }
-
-    pub fn icon(mut self, icon: Icon) -> Self {
-        self.icon = Some(icon);
-        self
-    }
-
-    pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
-        if self.icon.is_none() {
-            panic!("An icon must be present if an icon_position is provided.");
-        }
-        self.icon_position = Some(icon_position);
-        self
-    }
-
-    pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
-        self.width = width;
-        self
-    }
-
-    pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
-        self.click_handler = Some(Rc::new(handler));
-        self
-    }
-
-    pub fn disabled(mut self, disabled: bool) -> Self {
-        self.disabled = disabled;
-        self
-    }
-
-    pub fn color(mut self, color: Option<Color>) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn label_color(&self, color: Option<Color>) -> Color {
-        if self.disabled {
-            Color::Disabled
-        } else if let Some(color) = color {
-            color
-        } else {
-            Default::default()
-        }
-    }
-
-    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: Color) -> Option<IconElement> {
-        self.icon.map(|i| IconElement::new(i).color(icon_color))
-    }
-}
-
-#[derive(IntoElement)]
-pub struct ButtonGroup {
-    buttons: Vec<Button>,
-}
-
-impl RenderOnce for ButtonGroup {
-    type Rendered = Div;
-
-    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(cx));
-        }
-
-        group
-    }
-}
-
-impl ButtonGroup {
-    pub fn new(buttons: Vec<Button>) -> Self {
-        Self { buttons }
-    }
-}
+mod button;
+pub(self) mod button_icon;
+mod button_like;
+mod icon_button;
+
+pub use button::*;
+pub use button_like::*;
+pub use icon_button::*;

crates/ui2/src/components/button/button.rs πŸ”—

@@ -0,0 +1,147 @@
+use gpui::AnyView;
+
+use crate::prelude::*;
+use crate::{
+    ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
+};
+
+use super::button_icon::ButtonIcon;
+
+#[derive(IntoElement)]
+pub struct Button {
+    base: ButtonLike,
+    label: SharedString,
+    label_color: Option<Color>,
+    selected_label: Option<SharedString>,
+    icon: Option<Icon>,
+    icon_size: Option<IconSize>,
+    icon_color: Option<Color>,
+    selected_icon: Option<Icon>,
+}
+
+impl Button {
+    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
+        Self {
+            base: ButtonLike::new(id),
+            label: label.into(),
+            label_color: None,
+            selected_label: None,
+            icon: None,
+            icon_size: None,
+            icon_color: None,
+            selected_icon: None,
+        }
+    }
+
+    pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
+        self.label_color = label_color.into();
+        self
+    }
+
+    pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
+        self.selected_label = label.into().map(Into::into);
+        self
+    }
+
+    pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+        self.icon = icon.into();
+        self
+    }
+
+    pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
+        self.icon_size = icon_size.into();
+        self
+    }
+
+    pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
+        self.icon_color = icon_color.into();
+        self
+    }
+
+    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+        self.selected_icon = icon.into();
+        self
+    }
+}
+
+impl Selectable for Button {
+    fn selected(mut self, selected: bool) -> Self {
+        self.base = self.base.selected(selected);
+        self
+    }
+}
+
+impl Disableable for Button {
+    fn disabled(mut self, disabled: bool) -> Self {
+        self.base = self.base.disabled(disabled);
+        self
+    }
+}
+
+impl Clickable for Button {
+    fn on_click(
+        mut self,
+        handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
+    ) -> Self {
+        self.base = self.base.on_click(handler);
+        self
+    }
+}
+
+impl ButtonCommon for Button {
+    fn id(&self) -> &ElementId {
+        self.base.id()
+    }
+
+    fn style(mut self, style: ButtonStyle) -> Self {
+        self.base = self.base.style(style);
+        self
+    }
+
+    fn size(mut self, size: ButtonSize) -> Self {
+        self.base = self.base.size(size);
+        self
+    }
+
+    fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+        self.base = self.base.tooltip(tooltip);
+        self
+    }
+}
+
+impl RenderOnce for Button {
+    type Rendered = ButtonLike;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        let is_disabled = self.base.disabled;
+        let is_selected = self.base.selected;
+
+        let label = self
+            .selected_label
+            .filter(|_| is_selected)
+            .unwrap_or(self.label);
+
+        let label_color = if is_disabled {
+            Color::Disabled
+        } else if is_selected {
+            Color::Selected
+        } else {
+            self.label_color.unwrap_or_default()
+        };
+
+        self.base
+            .children(self.icon.map(|icon| {
+                ButtonIcon::new(icon)
+                    .disabled(is_disabled)
+                    .selected(is_selected)
+                    .selected_icon(self.selected_icon)
+                    .size(self.icon_size)
+                    .color(self.icon_color)
+            }))
+            .child(
+                Label::new(label)
+                    .color(label_color)
+                    .line_height_style(LineHeightStyle::UILabel),
+            )
+    }
+}

crates/ui2/src/components/button/button_icon.rs πŸ”—

@@ -0,0 +1,84 @@
+use crate::{prelude::*, Icon, IconElement, IconSize};
+
+/// An icon that appears within a button.
+///
+/// Can be used as either an icon alongside a label, like in [`Button`](crate::Button),
+/// or as a standalone icon, like in [`IconButton`](crate::IconButton).
+#[derive(IntoElement)]
+pub(super) struct ButtonIcon {
+    icon: Icon,
+    size: IconSize,
+    color: Color,
+    disabled: bool,
+    selected: bool,
+    selected_icon: Option<Icon>,
+}
+
+impl ButtonIcon {
+    pub fn new(icon: Icon) -> Self {
+        Self {
+            icon,
+            size: IconSize::default(),
+            color: Color::default(),
+            disabled: false,
+            selected: false,
+            selected_icon: None,
+        }
+    }
+
+    pub fn size(mut self, size: impl Into<Option<IconSize>>) -> Self {
+        if let Some(size) = size.into() {
+            self.size = size;
+        }
+
+        self
+    }
+
+    pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
+        if let Some(color) = color.into() {
+            self.color = color;
+        }
+
+        self
+    }
+
+    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+        self.selected_icon = icon.into();
+        self
+    }
+}
+
+impl Disableable for ButtonIcon {
+    fn disabled(mut self, disabled: bool) -> Self {
+        self.disabled = disabled;
+        self
+    }
+}
+
+impl Selectable for ButtonIcon {
+    fn selected(mut self, selected: bool) -> Self {
+        self.selected = selected;
+        self
+    }
+}
+
+impl RenderOnce for ButtonIcon {
+    type Rendered = IconElement;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        let icon = self
+            .selected_icon
+            .filter(|_| self.selected)
+            .unwrap_or(self.icon);
+
+        let icon_color = if self.disabled {
+            Color::Disabled
+        } else if self.selected {
+            Color::Selected
+        } else {
+            self.color
+        };
+
+        IconElement::new(icon).size(self.size).color(icon_color)
+    }
+}

crates/ui2/src/components/button/button_like.rs πŸ”—

@@ -0,0 +1,350 @@
+use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
+use smallvec::SmallVec;
+
+use crate::h_stack;
+use crate::prelude::*;
+
+pub trait ButtonCommon: Clickable + Disableable {
+    /// A unique element ID to identify the button.
+    fn id(&self) -> &ElementId;
+
+    /// The visual style of the button.
+    ///
+    /// Mosty commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`]
+    /// for an emphasized button.
+    fn style(self, style: ButtonStyle) -> Self;
+
+    /// The size of the button.
+    ///
+    /// Most buttons will use the default size.
+    ///
+    /// [`ButtonSize`] can also be used to help build non-button elements
+    /// that are consistently sized with buttons.
+    fn size(self, size: ButtonSize) -> Self;
+
+    /// The tooltip that shows when a user hovers over the button.
+    ///
+    /// Nearly all interactable elements should have a tooltip. Some example
+    /// exceptions might a scroll bar, or a slider.
+    fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
+pub enum ButtonStyle {
+    /// A filled button with a solid background color. Provides emphasis versus
+    /// the more common subtle button.
+    Filled,
+
+    /// 🚧 Under construction 🚧
+    ///
+    /// Used to emphasize a button in some way, like a selected state, or a semantic
+    /// coloring like an error or success button.
+    Tinted,
+
+    /// The default button style, used for most buttons. Has a transparent background,
+    /// but has a background color to indicate states like hover and active.
+    #[default]
+    Subtle,
+
+    /// Used for buttons that only change forground color on hover and active states.
+    ///
+    /// TODO: Better docs for this.
+    Transparent,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct ButtonLikeStyles {
+    pub background: Hsla,
+    #[allow(unused)]
+    pub border_color: Hsla,
+    #[allow(unused)]
+    pub label_color: Hsla,
+    #[allow(unused)]
+    pub icon_color: Hsla,
+}
+
+impl ButtonStyle {
+    pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+        match self {
+            ButtonStyle::Filled => ButtonLikeStyles {
+                background: cx.theme().colors().element_background,
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Tinted => ButtonLikeStyles {
+                background: gpui::red(),
+                border_color: gpui::red(),
+                label_color: gpui::red(),
+                icon_color: gpui::red(),
+            },
+            ButtonStyle::Subtle => ButtonLikeStyles {
+                background: cx.theme().colors().ghost_element_background,
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+        }
+    }
+
+    pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+        match self {
+            ButtonStyle::Filled => ButtonLikeStyles {
+                background: cx.theme().colors().element_hover,
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Tinted => ButtonLikeStyles {
+                background: gpui::red(),
+                border_color: gpui::red(),
+                label_color: gpui::red(),
+                icon_color: gpui::red(),
+            },
+            ButtonStyle::Subtle => ButtonLikeStyles {
+                background: cx.theme().colors().ghost_element_hover,
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
+                // TODO: These are not great
+                label_color: Color::Muted.color(cx),
+                // TODO: These are not great
+                icon_color: Color::Muted.color(cx),
+            },
+        }
+    }
+
+    pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+        match self {
+            ButtonStyle::Filled => ButtonLikeStyles {
+                background: cx.theme().colors().element_active,
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Tinted => ButtonLikeStyles {
+                background: gpui::red(),
+                border_color: gpui::red(),
+                label_color: gpui::red(),
+                icon_color: gpui::red(),
+            },
+            ButtonStyle::Subtle => ButtonLikeStyles {
+                background: cx.theme().colors().ghost_element_active,
+                border_color: transparent_black(),
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
+                // TODO: These are not great
+                label_color: Color::Muted.color(cx),
+                // TODO: These are not great
+                icon_color: Color::Muted.color(cx),
+            },
+        }
+    }
+
+    #[allow(unused)]
+    pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+        match self {
+            ButtonStyle::Filled => ButtonLikeStyles {
+                background: cx.theme().colors().element_background,
+                border_color: cx.theme().colors().border_focused,
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Tinted => ButtonLikeStyles {
+                background: gpui::red(),
+                border_color: gpui::red(),
+                label_color: gpui::red(),
+                icon_color: gpui::red(),
+            },
+            ButtonStyle::Subtle => ButtonLikeStyles {
+                background: cx.theme().colors().ghost_element_background,
+                border_color: cx.theme().colors().border_focused,
+                label_color: Color::Default.color(cx),
+                icon_color: Color::Default.color(cx),
+            },
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: cx.theme().colors().border_focused,
+                label_color: Color::Accent.color(cx),
+                icon_color: Color::Accent.color(cx),
+            },
+        }
+    }
+
+    pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
+        match self {
+            ButtonStyle::Filled => ButtonLikeStyles {
+                background: cx.theme().colors().element_disabled,
+                border_color: cx.theme().colors().border_disabled,
+                label_color: Color::Disabled.color(cx),
+                icon_color: Color::Disabled.color(cx),
+            },
+            ButtonStyle::Tinted => ButtonLikeStyles {
+                background: gpui::red(),
+                border_color: gpui::red(),
+                label_color: gpui::red(),
+                icon_color: gpui::red(),
+            },
+            ButtonStyle::Subtle => ButtonLikeStyles {
+                background: cx.theme().colors().ghost_element_disabled,
+                border_color: cx.theme().colors().border_disabled,
+                label_color: Color::Disabled.color(cx),
+                icon_color: Color::Disabled.color(cx),
+            },
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
+                label_color: Color::Disabled.color(cx),
+                icon_color: Color::Disabled.color(cx),
+            },
+        }
+    }
+}
+
+/// ButtonSize can also be used to help build  non-button elements
+/// that are consistently sized with buttons.
+#[derive(Default, PartialEq, Clone, Copy)]
+pub enum ButtonSize {
+    #[default]
+    Default,
+    Compact,
+    None,
+}
+
+impl ButtonSize {
+    fn height(self) -> Rems {
+        match self {
+            ButtonSize::Default => rems(22. / 16.),
+            ButtonSize::Compact => rems(18. / 16.),
+            ButtonSize::None => rems(16. / 16.),
+        }
+    }
+}
+
+/// A button-like element that can be used to create a custom button when
+/// prebuilt buttons are not sufficient. Use this sparingly, as it is
+/// unconstrained and may make the UI feel less consistent.
+///
+/// This is also used to build the prebuilt buttons.
+#[derive(IntoElement)]
+pub struct ButtonLike {
+    id: ElementId,
+    pub(super) style: ButtonStyle,
+    pub(super) disabled: bool,
+    pub(super) selected: bool,
+    size: ButtonSize,
+    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
+    on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
+    children: SmallVec<[AnyElement; 2]>,
+}
+
+impl ButtonLike {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self {
+            id: id.into(),
+            style: ButtonStyle::default(),
+            disabled: false,
+            selected: false,
+            size: ButtonSize::Default,
+            tooltip: None,
+            children: SmallVec::new(),
+            on_click: None,
+        }
+    }
+}
+
+impl Disableable for ButtonLike {
+    fn disabled(mut self, disabled: bool) -> Self {
+        self.disabled = disabled;
+        self
+    }
+}
+
+impl Selectable for ButtonLike {
+    fn selected(mut self, selected: bool) -> Self {
+        self.selected = selected;
+        self
+    }
+}
+
+impl Clickable for ButtonLike {
+    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
+        self.on_click = Some(Box::new(handler));
+        self
+    }
+}
+
+impl ButtonCommon for ButtonLike {
+    fn id(&self) -> &ElementId {
+        &self.id
+    }
+
+    fn style(mut self, style: ButtonStyle) -> Self {
+        self.style = style;
+        self
+    }
+
+    fn size(mut self, size: ButtonSize) -> Self {
+        self.size = size;
+        self
+    }
+
+    fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+        self.tooltip = Some(Box::new(tooltip));
+        self
+    }
+}
+
+impl ParentElement for ButtonLike {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
+impl RenderOnce for ButtonLike {
+    type Rendered = Stateful<Div>;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        h_stack()
+            .id(self.id.clone())
+            .h(self.size.height())
+            .rounded_md()
+            .when(!self.disabled, |el| el.cursor_pointer())
+            .gap_1()
+            .px_1()
+            .bg(self.style.enabled(cx).background)
+            .hover(|hover| hover.bg(self.style.hovered(cx).background))
+            .active(|active| active.bg(self.style.active(cx).background))
+            .when_some(
+                self.on_click.filter(|_| !self.disabled),
+                |this, on_click| {
+                    this.on_click(move |event, cx| {
+                        cx.stop_propagation();
+                        (on_click)(event, cx)
+                    })
+                },
+            )
+            .when_some(self.tooltip, |this, tooltip| {
+                if !self.selected {
+                    this.tooltip(move |cx| tooltip(cx))
+                } else {
+                    this
+                }
+            })
+            .children(self.children)
+    }
+}

crates/ui2/src/components/button/icon_button.rs πŸ”—

@@ -0,0 +1,109 @@
+use gpui::{Action, AnyView};
+
+use crate::prelude::*;
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
+
+use super::button_icon::ButtonIcon;
+
+#[derive(IntoElement)]
+pub struct IconButton {
+    base: ButtonLike,
+    icon: Icon,
+    icon_size: IconSize,
+    icon_color: Color,
+    selected_icon: Option<Icon>,
+}
+
+impl IconButton {
+    pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
+        Self {
+            base: ButtonLike::new(id),
+            icon,
+            icon_size: IconSize::default(),
+            icon_color: Color::Default,
+            selected_icon: None,
+        }
+    }
+
+    pub fn icon_size(mut self, icon_size: IconSize) -> Self {
+        self.icon_size = icon_size;
+        self
+    }
+
+    pub fn icon_color(mut self, icon_color: Color) -> Self {
+        self.icon_color = icon_color;
+        self
+    }
+
+    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+        self.selected_icon = icon.into();
+        self
+    }
+
+    pub fn action(self, action: Box<dyn Action>) -> Self {
+        self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
+    }
+}
+
+impl Disableable for IconButton {
+    fn disabled(mut self, disabled: bool) -> Self {
+        self.base = self.base.disabled(disabled);
+        self
+    }
+}
+
+impl Selectable for IconButton {
+    fn selected(mut self, selected: bool) -> Self {
+        self.base = self.base.selected(selected);
+        self
+    }
+}
+
+impl Clickable for IconButton {
+    fn on_click(
+        mut self,
+        handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
+    ) -> Self {
+        self.base = self.base.on_click(handler);
+        self
+    }
+}
+
+impl ButtonCommon for IconButton {
+    fn id(&self) -> &ElementId {
+        self.base.id()
+    }
+
+    fn style(mut self, style: ButtonStyle) -> Self {
+        self.base = self.base.style(style);
+        self
+    }
+
+    fn size(mut self, size: ButtonSize) -> Self {
+        self.base = self.base.size(size);
+        self
+    }
+
+    fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+        self.base = self.base.tooltip(tooltip);
+        self
+    }
+}
+
+impl RenderOnce for IconButton {
+    type Rendered = ButtonLike;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        let is_disabled = self.base.disabled;
+        let is_selected = self.base.selected;
+
+        self.base.child(
+            ButtonIcon::new(self.icon)
+                .disabled(is_disabled)
+                .selected(is_selected)
+                .selected_icon(self.selected_icon)
+                .size(self.icon_size)
+                .color(self.icon_color),
+        )
+    }
+}

crates/ui2/src/components/button2.rs πŸ”—

@@ -1,405 +0,0 @@
-use gpui::{
-    rems, AnyElement, AnyView, ClickEvent, Div, Hsla, IntoElement, Rems, Stateful,
-    StatefulInteractiveElement, WindowContext,
-};
-use smallvec::SmallVec;
-
-use crate::{h_stack, prelude::*};
-
-// 🚧 Heavily WIP 🚧
-
-// #[derive(Default, PartialEq, Clone, Copy)]
-// pub enum ButtonType2 {
-//     #[default]
-//     DefaultButton,
-//     IconButton,
-//     ButtonLike,
-//     SplitButton,
-//     ToggleButton,
-// }
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum IconPosition2 {
-    #[default]
-    Before,
-    After,
-}
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum ButtonStyle2 {
-    #[default]
-    Filled,
-    // Tinted,
-    Subtle,
-    Transparent,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct ButtonStyle {
-    pub background: Hsla,
-    pub border_color: Hsla,
-    pub label_color: Hsla,
-    pub icon_color: Hsla,
-}
-
-impl ButtonStyle2 {
-    pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle {
-        match self {
-            ButtonStyle2::Filled => ButtonStyle {
-                background: cx.theme().colors().element_background,
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Subtle => ButtonStyle {
-                background: cx.theme().colors().ghost_element_background,
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-        }
-    }
-
-    pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle {
-        match self {
-            ButtonStyle2::Filled => ButtonStyle {
-                background: cx.theme().colors().element_hover,
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Subtle => ButtonStyle {
-                background: cx.theme().colors().ghost_element_hover,
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
-                // TODO: These are not great
-                label_color: Color::Muted.color(cx),
-                // TODO: These are not great
-                icon_color: Color::Muted.color(cx),
-            },
-        }
-    }
-
-    pub fn active(self, cx: &mut WindowContext) -> ButtonStyle {
-        match self {
-            ButtonStyle2::Filled => ButtonStyle {
-                background: cx.theme().colors().element_active,
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Subtle => ButtonStyle {
-                background: cx.theme().colors().ghost_element_active,
-                border_color: gpui::transparent_black(),
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
-                // TODO: These are not great
-                label_color: Color::Muted.color(cx),
-                // TODO: These are not great
-                icon_color: Color::Muted.color(cx),
-            },
-        }
-    }
-
-    pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle {
-        match self {
-            ButtonStyle2::Filled => ButtonStyle {
-                background: cx.theme().colors().element_background,
-                border_color: cx.theme().colors().border_focused,
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Subtle => ButtonStyle {
-                background: cx.theme().colors().ghost_element_background,
-                border_color: cx.theme().colors().border_focused,
-                label_color: Color::Default.color(cx),
-                icon_color: Color::Default.color(cx),
-            },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: cx.theme().colors().border_focused,
-                label_color: Color::Accent.color(cx),
-                icon_color: Color::Accent.color(cx),
-            },
-        }
-    }
-
-    pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle {
-        match self {
-            ButtonStyle2::Filled => ButtonStyle {
-                background: cx.theme().colors().element_disabled,
-                border_color: cx.theme().colors().border_disabled,
-                label_color: Color::Disabled.color(cx),
-                icon_color: Color::Disabled.color(cx),
-            },
-            ButtonStyle2::Subtle => ButtonStyle {
-                background: cx.theme().colors().ghost_element_disabled,
-                border_color: cx.theme().colors().border_disabled,
-                label_color: Color::Disabled.color(cx),
-                icon_color: Color::Disabled.color(cx),
-            },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
-                label_color: Color::Disabled.color(cx),
-                icon_color: Color::Disabled.color(cx),
-            },
-        }
-    }
-}
-
-#[derive(Default, PartialEq, Clone, Copy)]
-pub enum ButtonSize2 {
-    #[default]
-    Default,
-    Compact,
-    None,
-}
-
-impl ButtonSize2 {
-    fn height(self) -> Rems {
-        match self {
-            ButtonSize2::Default => rems(22. / 16.),
-            ButtonSize2::Compact => rems(18. / 16.),
-            ButtonSize2::None => rems(16. / 16.),
-        }
-    }
-}
-
-// pub struct Button {
-//     id: ElementId,
-//     icon: Option<Icon>,
-//     icon_color: Option<Color>,
-//     icon_position: Option<IconPosition2>,
-//     label: Option<Label>,
-//     label_color: Option<Color>,
-//     appearance: ButtonAppearance2,
-//     state: InteractionState,
-//     selected: bool,
-//     disabled: bool,
-//     tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
-//     width: Option<DefiniteLength>,
-//     action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
-//     secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
-//     /// Used to pass down some content to the button
-//     /// to enable creating custom buttons.
-//     children: SmallVec<[AnyElement; 2]>,
-// }
-
-pub trait ButtonCommon: Clickable + Disableable {
-    fn id(&self) -> &ElementId;
-    fn style(self, style: ButtonStyle2) -> Self;
-    fn size(self, size: ButtonSize2) -> Self;
-    fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
-}
-
-// pub struct LabelButton {
-//     // Base properties...
-//     id: ElementId,
-//     appearance: ButtonAppearance,
-//     state: InteractionState,
-//     disabled: bool,
-//     size: ButtonSize,
-//     tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
-//     width: Option<DefiniteLength>,
-//     // Button-specific properties...
-//     label: Option<SharedString>,
-//     label_color: Option<Color>,
-//     icon: Option<Icon>,
-//     icon_color: Option<Color>,
-//     icon_position: Option<IconPosition>,
-//     // Define more fields for additional properties as needed
-// }
-
-// impl ButtonCommon for LabelButton {
-//     fn id(&self) -> &ElementId {
-//         &self.id
-//     }
-
-//     fn appearance(&mut self, appearance: ButtonAppearance) -> &mut Self {
-//         self.style= style;
-//         self
-//     }
-//     // implement methods from ButtonCommon trait...
-// }
-
-// impl LabelButton {
-//     pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
-//         Self {
-//             id: id.into(),
-//             label: Some(label.into()),
-//             // initialize other fields with default values...
-//         }
-//     }
-
-//     // ... Define other builder methods specific to Button type...
-// }
-
-// TODO: Icon Button
-
-#[derive(IntoElement)]
-pub struct ButtonLike {
-    id: ElementId,
-    style: ButtonStyle2,
-    disabled: bool,
-    size: ButtonSize2,
-    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
-    on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-    children: SmallVec<[AnyElement; 2]>,
-}
-
-impl ButtonLike {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            style: ButtonStyle2::default(),
-            disabled: false,
-            size: ButtonSize2::Default,
-            tooltip: None,
-            children: SmallVec::new(),
-            on_click: None,
-        }
-    }
-}
-
-impl Disableable for ButtonLike {
-    fn disabled(mut self, disabled: bool) -> Self {
-        self.disabled = disabled;
-        self
-    }
-}
-
-impl Clickable for ButtonLike {
-    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
-        self.on_click = Some(Box::new(handler));
-        self
-    }
-}
-
-// impl Selectable for ButtonLike {
-//     fn selected(&mut self, selected: bool) -> &mut Self {
-//         todo!()
-//     }
-
-//     fn selected_tooltip(
-//         &mut self,
-//         tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
-//     ) -> &mut Self {
-//         todo!()
-//     }
-// }
-
-impl ButtonCommon for ButtonLike {
-    fn id(&self) -> &ElementId {
-        &self.id
-    }
-
-    fn style(mut self, style: ButtonStyle2) -> Self {
-        self.style = style;
-        self
-    }
-
-    fn size(mut self, size: ButtonSize2) -> Self {
-        self.size = size;
-        self
-    }
-
-    fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
-        self.tooltip = Some(Box::new(tooltip));
-        self
-    }
-}
-
-impl RenderOnce for ButtonLike {
-    type Rendered = Stateful<Div>;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        h_stack()
-            .id(self.id.clone())
-            .h(self.size.height())
-            .rounded_md()
-            .cursor_pointer()
-            .gap_1()
-            .px_1()
-            .bg(self.style.enabled(cx).background)
-            .hover(|hover| hover.bg(self.style.hovered(cx).background))
-            .active(|active| active.bg(self.style.active(cx).background))
-            .when_some(
-                self.on_click.filter(|_| !self.disabled),
-                |this, on_click| this.on_click(move |event, cx| (on_click)(event, cx)),
-            )
-            .when_some(self.tooltip, |this, tooltip| {
-                this.tooltip(move |cx| tooltip(cx))
-            })
-            .children(self.children)
-    }
-}
-
-impl ParentElement for ButtonLike {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
-        &mut self.children
-    }
-}
-
-// pub struct ToggleButton {
-//     // based on either IconButton2 or Button, with additional 'selected: bool' property
-// }
-
-// impl ButtonCommon for ToggleButton {
-//     fn id(&self) -> &ElementId {
-//         &self.id
-//     }
-//     // ... Implement other methods from ButtonCommon trait with builder patterns...
-// }
-
-// impl ToggleButton {
-//     pub fn new() -> Self {
-//         // Initialize with default values
-//         Self {
-//             // ... initialize fields, possibly with defaults or required parameters...
-//         }
-//     }
-
-//     // ... Define other builder methods specific to ToggleButton type...
-// }
-
-// pub struct SplitButton {
-//     // Base properties...
-//     id: ElementId,
-//     // Button-specific properties, possibly including a DefaultButton
-//     secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
-//     // More fields as necessary...
-// }
-
-// impl ButtonCommon for SplitButton {
-//     fn id(&self) -> &ElementId {
-//         &self.id
-//     }
-//     // ... Implement other methods from ButtonCommon trait with builder patterns...
-// }
-
-// impl SplitButton {
-//     pub fn new(id: impl Into<ElementId>) -> Self {
-//         Self {
-//             id: id.into(),
-//             // ... initialize other fields with default values...
-//         }
-//     }
-
-//     // ... Define other builder methods specific to SplitButton type...
-// }

crates/ui2/src/components/context_menu.rs πŸ”—

@@ -2,12 +2,11 @@ use crate::{
     h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader,
 };
 use gpui::{
-    overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DismissEvent, DispatchPhase,
-    Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton,
-    MouseDownEvent, Pixels, Point, Render, View, VisualContext,
+    px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
+    IntoElement, Render, View, VisualContext,
 };
 use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
-use std::{cell::RefCell, rc::Rc};
+use std::rc::Rc;
 
 pub enum ContextMenuItem {
     Separator,
@@ -208,174 +207,3 @@ impl Render for ContextMenu {
         )
     }
 }
-
-pub struct MenuHandle<M: ManagedView> {
-    id: ElementId,
-    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<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: IntoElement>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
-        self.child_builder = Some(Box::new(|b| f(b).into_element().into_any()));
-        self
-    }
-
-    /// anchor defines which corner of the menu to anchor to the attachment point
-    /// (by default the cursor position, but see attach)
-    pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
-        self.anchor = Some(anchor);
-        self
-    }
-
-    /// attach defines which corner of the handle to attach the menu's anchor to
-    pub fn attach(mut self, attach: AnchorCorner) -> Self {
-        self.attach = Some(attach);
-        self
-    }
-}
-
-pub fn menu_handle<M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<M> {
-    MenuHandle {
-        id: id.into(),
-        child_builder: None,
-        menu_builder: None,
-        anchor: None,
-        attach: None,
-    }
-}
-
-pub struct MenuHandleState<M> {
-    menu: Rc<RefCell<Option<View<M>>>>,
-    position: Rc<RefCell<Point<Pixels>>>,
-    child_layout_id: Option<LayoutId>,
-    child_element: Option<AnyElement>,
-    menu_element: Option<AnyElement>,
-}
-
-impl<M: ManagedView> Element for MenuHandle<M> {
-    type State = MenuHandleState<M>;
-
-    fn layout(
-        &mut self,
-        element_state: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (gpui::LayoutId, Self::State) {
-        let (menu, position) = if let Some(element_state) = element_state {
-            (element_state.menu, element_state.position)
-        } else {
-            (Rc::default(), Rc::default())
-        };
-
-        let mut menu_layout_id = None;
-
-        let menu_element = menu.borrow_mut().as_mut().map(|menu| {
-            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(cx));
-            element
-        });
-
-        let mut child_element = self
-            .child_builder
-            .take()
-            .map(|child_builder| (child_builder)(menu.borrow().is_some()));
-
-        let child_layout_id = child_element
-            .as_mut()
-            .map(|child_element| child_element.layout(cx));
-
-        let layout_id = cx.request_layout(
-            &gpui::Style::default(),
-            menu_layout_id.into_iter().chain(child_layout_id),
-        );
-
-        (
-            layout_id,
-            MenuHandleState {
-                menu,
-                position,
-                child_element,
-                child_layout_id,
-                menu_element,
-            },
-        )
-    }
-
-    fn paint(
-        self,
-        bounds: Bounds<gpui::Pixels>,
-        element_state: &mut Self::State,
-        cx: &mut WindowContext,
-    ) {
-        if let Some(child) = element_state.child_element.take() {
-            child.paint(cx);
-        }
-
-        if let Some(menu) = element_state.menu_element.take() {
-            menu.paint(cx);
-            return;
-        }
-
-        let Some(builder) = self.menu_builder else {
-            return;
-        };
-        let menu = element_state.menu.clone();
-        let position = element_state.position.clone();
-        let attach = self.attach.clone();
-        let child_layout_id = element_state.child_layout_id.clone();
-
-        cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
-            if phase == DispatchPhase::Bubble
-                && event.button == MouseButton::Right
-                && bounds.contains_point(&event.position)
-            {
-                cx.stop_propagation();
-                cx.prevent_default();
-
-                let new_menu = (builder)(cx);
-                let menu2 = menu.clone();
-                cx.subscribe(&new_menu, move |_modal, _: &DismissEvent, cx| {
-                    *menu2.borrow_mut() = None;
-                    cx.notify();
-                })
-                .detach();
-                cx.focus_view(&new_menu);
-                *menu.borrow_mut() = Some(new_menu);
-
-                *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
-                    attach
-                        .unwrap()
-                        .corner(cx.layout_bounds(child_layout_id.unwrap()))
-                } else {
-                    cx.mouse_position()
-                };
-                cx.notify();
-            }
-        });
-    }
-}
-
-impl<M: ManagedView> IntoElement for MenuHandle<M> {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<gpui::ElementId> {
-        Some(self.id.clone())
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}

crates/ui2/src/components/disclosure.rs πŸ”—

@@ -1,30 +1,48 @@
 use std::rc::Rc;
 
-use gpui::{div, ClickEvent, Element, IntoElement, ParentElement, WindowContext};
+use gpui::ClickEvent;
 
-use crate::{Color, Icon, IconButton, IconSize, Toggle};
+use crate::prelude::*;
+use crate::{Color, Icon, IconButton, IconSize};
 
-pub fn disclosure_control(
-    toggle: Toggle,
+#[derive(IntoElement)]
+pub struct Disclosure {
+    is_open: bool,
     on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-) -> impl Element {
-    match (toggle.is_toggleable(), toggle.is_toggled()) {
-        (false, _) => div(),
-        (_, true) => div().child(
-            IconButton::new("toggle", Icon::ChevronDown)
-                .color(Color::Muted)
-                .size(IconSize::Small)
-                .when_some(on_toggle, move |el, on_toggle| {
-                    el.on_click(move |e, cx| on_toggle(e, cx))
-                }),
-        ),
-        (_, false) => div().child(
-            IconButton::new("toggle", Icon::ChevronRight)
-                .color(Color::Muted)
-                .size(IconSize::Small)
-                .when_some(on_toggle, move |el, on_toggle| {
-                    el.on_click(move |e, cx| on_toggle(e, cx))
-                }),
-        ),
+}
+
+impl Disclosure {
+    pub fn new(is_open: bool) -> Self {
+        Self {
+            is_open,
+            on_toggle: None,
+        }
+    }
+
+    pub fn on_toggle(
+        mut self,
+        handler: impl Into<Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
+    ) -> Self {
+        self.on_toggle = handler.into();
+        self
+    }
+}
+
+impl RenderOnce for Disclosure {
+    type Rendered = IconButton;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        IconButton::new(
+            "toggle",
+            match self.is_open {
+                true => Icon::ChevronDown,
+                false => Icon::ChevronRight,
+            },
+        )
+        .icon_color(Color::Muted)
+        .icon_size(IconSize::Small)
+        .when_some(self.on_toggle, move |this, on_toggle| {
+            this.on_click(move |event, cx| on_toggle(event, cx))
+        })
     }
 }

crates/ui2/src/components/icon_button.rs πŸ”—

@@ -1,135 +0,0 @@
-use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
-use gpui::{prelude::*, Action, AnyView, ClickEvent, Div, Stateful};
-
-#[derive(IntoElement)]
-pub struct IconButton {
-    id: ElementId,
-    icon: Icon,
-    color: Color,
-    size: IconSize,
-    variant: ButtonVariant,
-    disabled: bool,
-    selected: bool,
-    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
-    on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-}
-
-impl RenderOnce for IconButton {
-    type Rendered = Stateful<Div>;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let icon_color = match (self.disabled, self.selected, self.color) {
-            (true, _, _) => Color::Disabled,
-            (false, true, _) => Color::Selected,
-            _ => self.color,
-        };
-
-        let (mut bg_color, bg_active_color) = match self.variant {
-            ButtonVariant::Filled => (
-                cx.theme().colors().element_background,
-                cx.theme().colors().element_active,
-            ),
-            ButtonVariant::Ghost => (
-                cx.theme().colors().ghost_element_background,
-                cx.theme().colors().ghost_element_active,
-            ),
-        };
-
-        if self.selected {
-            bg_color = cx.theme().colors().element_selected;
-        }
-
-        let mut button = h_stack()
-            .id(self.id.clone())
-            .justify_center()
-            .rounded_md()
-            .p_1()
-            .bg(bg_color)
-            .cursor_pointer()
-            // Nate: Trying to figure out the right places we want to show a
-            // hover state here. I think it is a bit heavy to have it on every
-            // place we use an icon button.
-            // .hover(|style| style.bg(bg_hover_color))
-            .active(|style| style.bg(bg_active_color))
-            .child(
-                IconElement::new(self.icon)
-                    .size(self.size)
-                    .color(icon_color),
-            );
-
-        if let Some(click_handler) = self.on_click {
-            button = button.on_click(move |event, cx| {
-                cx.stop_propagation();
-                click_handler(event, cx);
-            })
-        }
-
-        if let Some(tooltip) = self.tooltip {
-            if !self.selected {
-                button = button.tooltip(move |cx| tooltip(cx))
-            }
-        }
-
-        button
-    }
-}
-
-impl IconButton {
-    pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
-        Self {
-            id: id.into(),
-            icon,
-            color: Color::default(),
-            size: Default::default(),
-            variant: ButtonVariant::default(),
-            selected: false,
-            disabled: false,
-            tooltip: None,
-            on_click: None,
-        }
-    }
-
-    pub fn icon(mut self, icon: Icon) -> Self {
-        self.icon = icon;
-        self
-    }
-
-    pub fn color(mut self, color: Color) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn size(mut self, size: IconSize) -> Self {
-        self.size = size;
-        self
-    }
-
-    pub fn variant(mut self, variant: ButtonVariant) -> Self {
-        self.variant = variant;
-        self
-    }
-
-    pub fn selected(mut self, selected: bool) -> Self {
-        self.selected = selected;
-        self
-    }
-
-    pub fn disabled(mut self, disabled: bool) -> Self {
-        self.disabled = disabled;
-        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(&ClickEvent, &mut WindowContext)) -> Self {
-        self.on_click = Some(Box::new(handler));
-        self
-    }
-
-    pub fn action(self, action: Box<dyn Action>) -> Self {
-        self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
-    }
-}

crates/ui2/src/components/label.rs πŸ”—

@@ -1,6 +1,8 @@
+use std::ops::Range;
+
 use crate::prelude::*;
 use crate::styled_ext::StyledExt;
-use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
+use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 pub enum LabelSize {
@@ -99,38 +101,32 @@ impl RenderOnce for HighlightedLabel {
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         let highlight_color = cx.theme().colors().text_accent;
-        let mut text_style = cx.text_style().clone();
 
         let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
-
-        let mut runs: Vec<TextRun> = Vec::new();
-
-        for (char_ix, char) in self.label.char_indices() {
-            let mut color = self.color.color(cx);
-
-            if let Some(highlight_ix) = highlight_indices.peek() {
-                if char_ix == *highlight_ix {
-                    color = highlight_color;
-                    highlight_indices.next();
+        let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
+
+        while let Some(start_ix) = highlight_indices.next() {
+            let mut end_ix = start_ix;
+
+            loop {
+                end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
+                if let Some(&next_ix) = highlight_indices.peek() {
+                    if next_ix == end_ix {
+                        end_ix = next_ix;
+                        highlight_indices.next();
+                        continue;
+                    }
                 }
+                break;
             }
 
-            let last_run = runs.last_mut();
-            let start_new_run = if let Some(last_run) = last_run {
-                if color == last_run.color {
-                    last_run.len += char.len_utf8();
-                    false
-                } else {
-                    true
-                }
-            } else {
-                true
-            };
-
-            if start_new_run {
-                text_style.color = color;
-                runs.push(text_style.to_run(char.len_utf8()))
-            }
+            highlights.push((
+                start_ix..end_ix,
+                HighlightStyle {
+                    color: Some(highlight_color),
+                    ..Default::default()
+                },
+            ));
         }
 
         div()
@@ -150,7 +146,7 @@ impl RenderOnce for HighlightedLabel {
                 LabelSize::Default => this.text_ui(),
                 LabelSize::Small => this.text_ui_sm(),
             })
-            .child(StyledText::new(self.label).with_runs(runs))
+            .child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights))
     }
 }
 

crates/ui2/src/components/list.rs πŸ”—

@@ -1,73 +1,11 @@
+mod list;
 mod list_header;
 mod list_item;
 mod list_separator;
 mod list_sub_header;
 
-use gpui::{AnyElement, Div};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-use crate::{v_stack, Label, Toggle};
-
+pub use list::*;
 pub use list_header::*;
 pub use list_item::*;
 pub use list_separator::*;
 pub use list_sub_header::*;
-
-#[derive(IntoElement)]
-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; 2]>,
-}
-
-impl List {
-    pub fn new() -> Self {
-        Self {
-            empty_message: "No items".into(),
-            header: None,
-            toggle: Toggle::NotToggleable,
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
-        self.empty_message = empty_message.into();
-        self
-    }
-
-    pub fn header(mut self, header: ListHeader) -> Self {
-        self.header = Some(header);
-        self
-    }
-
-    pub fn toggle(mut self, toggle: Toggle) -> Self {
-        self.toggle = toggle;
-        self
-    }
-}
-
-impl ParentElement for List {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
-        &mut self.children
-    }
-}
-
-impl RenderOnce for List {
-    type Rendered = Div;
-
-    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
-        v_stack()
-            .w_full()
-            .py_1()
-            .children(self.header.map(|header| header))
-            .map(|this| match (self.children.is_empty(), self.toggle) {
-                (false, _) => this.children(self.children),
-                (true, Toggle::Toggled(false)) => this,
-                (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
-            })
-    }
-}

crates/ui2/src/components/list/list.rs πŸ”—

@@ -0,0 +1,60 @@
+use gpui::{AnyElement, Div};
+use smallvec::SmallVec;
+
+use crate::{prelude::*, v_stack, Label, ListHeader};
+
+#[derive(IntoElement)]
+pub struct List {
+    /// Message to display when the list is empty
+    /// Defaults to "No items"
+    empty_message: SharedString,
+    header: Option<ListHeader>,
+    toggle: Option<bool>,
+    children: SmallVec<[AnyElement; 2]>,
+}
+
+impl List {
+    pub fn new() -> Self {
+        Self {
+            empty_message: "No items".into(),
+            header: None,
+            toggle: None,
+            children: SmallVec::new(),
+        }
+    }
+
+    pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
+        self.empty_message = empty_message.into();
+        self
+    }
+
+    pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
+        self.header = header.into();
+        self
+    }
+
+    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+        self.toggle = toggle.into();
+        self
+    }
+}
+
+impl ParentElement for List {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
+impl RenderOnce for List {
+    type Rendered = Div;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        v_stack().w_full().py_1().children(self.header).map(|this| {
+            match (self.children.is_empty(), self.toggle) {
+                (false, _) => this.children(self.children),
+                (true, Some(false)) => this,
+                (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
+            }
+        })
+    }
+}

crates/ui2/src/components/list/list_header.rs πŸ”—

@@ -1,23 +1,17 @@
 use std::rc::Rc;
 
-use gpui::{ClickEvent, Div};
+use gpui::{AnyElement, ClickEvent, Div};
+use smallvec::SmallVec;
 
 use crate::prelude::*;
-use crate::{disclosure_control, h_stack, Icon, IconButton, IconElement, IconSize, Label, Toggle};
-
-pub enum ListHeaderMeta {
-    Tools(Vec<IconButton>),
-    // TODO: This should be a button
-    Button(Label),
-    Text(Label),
-}
+use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
 
 #[derive(IntoElement)]
 pub struct ListHeader {
     label: SharedString,
     left_icon: Option<Icon>,
-    meta: Option<ListHeaderMeta>,
-    toggle: Toggle,
+    meta: SmallVec<[AnyElement; 2]>,
+    toggle: Option<bool>,
     on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
     inset: bool,
     selected: bool,
@@ -28,16 +22,16 @@ impl ListHeader {
         Self {
             label: label.into(),
             left_icon: None,
-            meta: None,
+            meta: SmallVec::new(),
             inset: false,
-            toggle: Toggle::NotToggleable,
+            toggle: None,
             on_toggle: None,
             selected: false,
         }
     }
 
-    pub fn toggle(mut self, toggle: Toggle) -> Self {
-        self.toggle = toggle;
+    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+        self.toggle = toggle.into();
         self
     }
 
@@ -49,21 +43,19 @@ impl ListHeader {
         self
     }
 
-    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
-        self.left_icon = left_icon;
+    pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
+        self.left_icon = left_icon.into();
         self
     }
 
-    pub fn right_button(self, button: IconButton) -> Self {
-        self.meta(Some(ListHeaderMeta::Tools(vec![button])))
-    }
-
-    pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
-        self.meta = meta;
+    pub fn meta(mut self, meta: impl IntoElement) -> Self {
+        self.meta.push(meta.into_any_element());
         self
     }
+}
 
-    pub fn selected(mut self, selected: bool) -> Self {
+impl Selectable for ListHeader {
+    fn selected(mut self, selected: bool) -> Self {
         self.selected = selected;
         self
     }
@@ -73,20 +65,6 @@ impl RenderOnce for ListHeader {
     type Rendered = Div;
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let disclosure_control = disclosure_control(self.toggle, self.on_toggle);
-
-        let meta = match self.meta {
-            Some(ListHeaderMeta::Tools(icons)) => div().child(
-                h_stack()
-                    .gap_2()
-                    .items_center()
-                    .children(icons.into_iter().map(|i| i.color(Color::Muted))),
-            ),
-            Some(ListHeaderMeta::Button(label)) => div().child(label),
-            Some(ListHeaderMeta::Text(label)) => div().child(label),
-            None => div(),
-        };
-
         h_stack().w_full().relative().child(
             div()
                 .h_5()
@@ -115,9 +93,12 @@ impl RenderOnce for ListHeader {
                                 }))
                                 .child(Label::new(self.label.clone()).color(Color::Muted)),
                         )
-                        .child(disclosure_control),
+                        .children(
+                            self.toggle
+                                .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
+                        ),
                 )
-                .child(meta),
+                .child(h_stack().gap_2().items_center().children(self.meta)),
         )
     }
 }

crates/ui2/src/components/list/list_item.rs πŸ”—

@@ -6,7 +6,7 @@ use gpui::{
 use smallvec::SmallVec;
 
 use crate::prelude::*;
-use crate::{disclosure_control, Avatar, GraphicSlot, Icon, IconElement, IconSize, Toggle};
+use crate::{Avatar, Disclosure, Icon, IconElement, IconSize};
 
 #[derive(IntoElement)]
 pub struct ListItem {
@@ -16,8 +16,8 @@ pub struct ListItem {
     // disclosure_control_style: DisclosureControlVisibility,
     indent_level: usize,
     indent_step_size: Pixels,
-    left_slot: Option<GraphicSlot>,
-    toggle: Toggle,
+    left_slot: Option<AnyElement>,
+    toggle: Option<bool>,
     inset: bool,
     on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
     on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
@@ -33,7 +33,7 @@ impl ListItem {
             indent_level: 0,
             indent_step_size: px(12.),
             left_slot: None,
-            toggle: Toggle::NotToggleable,
+            toggle: None,
             inset: false,
             on_click: None,
             on_secondary_mouse_down: None,
@@ -70,8 +70,8 @@ impl ListItem {
         self
     }
 
-    pub fn toggle(mut self, toggle: Toggle) -> Self {
-        self.toggle = toggle;
+    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+        self.toggle = toggle.into();
         self
     }
 
@@ -83,23 +83,30 @@ impl ListItem {
         self
     }
 
-    pub fn selected(mut self, selected: bool) -> Self {
-        self.selected = selected;
+    pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
+        self.left_slot = Some(left_content.into_any_element());
         self
     }
 
-    pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
-        self.left_slot = Some(left_content);
+    pub fn left_icon(mut self, left_icon: Icon) -> Self {
+        self.left_slot = Some(
+            IconElement::new(left_icon)
+                .size(IconSize::Small)
+                .color(Color::Muted)
+                .into_any_element(),
+        );
         self
     }
 
-    pub fn left_icon(mut self, left_icon: Icon) -> Self {
-        self.left_slot = Some(GraphicSlot::Icon(left_icon));
+    pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
+        self.left_slot = Some(Avatar::source(left_avatar.into()).into_any_element());
         self
     }
+}
 
-    pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
-        self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
+impl Selectable for ListItem {
+    fn selected(mut self, selected: bool) -> Self {
+        self.selected = selected;
         self
     }
 }
@@ -150,17 +157,11 @@ impl RenderOnce for ListItem {
                     .gap_1()
                     .items_center()
                     .relative()
-                    .child(disclosure_control(self.toggle, self.on_toggle))
-                    .map(|this| match self.left_slot {
-                        Some(GraphicSlot::Icon(i)) => this.child(
-                            IconElement::new(i)
-                                .size(IconSize::Small)
-                                .color(Color::Muted),
-                        ),
-                        Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)),
-                        Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)),
-                        None => this,
-                    })
+                    .children(
+                        self.toggle
+                            .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
+                    )
+                    .children(self.left_slot)
                     .children(self.children),
             )
     }

crates/ui2/src/components/popover_menu.rs πŸ”—

@@ -0,0 +1,231 @@
+use std::{cell::RefCell, rc::Rc};
+
+use gpui::{
+    overlay, point, px, rems, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase,
+    Element, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseDownEvent,
+    ParentElement, Pixels, Point, View, VisualContext, WindowContext,
+};
+
+use crate::{Clickable, Selectable};
+
+pub trait PopoverTrigger: IntoElement + Clickable + Selectable + 'static {}
+
+impl<T: IntoElement + Clickable + Selectable + 'static> PopoverTrigger for T {}
+
+pub struct PopoverMenu<M: ManagedView> {
+    id: ElementId,
+    child_builder: Option<
+        Box<
+            dyn FnOnce(
+                    Rc<RefCell<Option<View<M>>>>,
+                    Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
+                ) -> AnyElement
+                + 'static,
+        >,
+    >,
+    menu_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
+    anchor: AnchorCorner,
+    attach: Option<AnchorCorner>,
+    offset: Option<Point<Pixels>>,
+}
+
+impl<M: ManagedView> PopoverMenu<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 trigger<T: PopoverTrigger>(mut self, t: T) -> Self {
+        self.child_builder = Some(Box::new(|menu, builder| {
+            let open = menu.borrow().is_some();
+            t.selected(open)
+                .when_some(builder, |el, builder| {
+                    el.on_click({
+                        move |_, cx| {
+                            let new_menu = (builder)(cx);
+                            let menu2 = menu.clone();
+                            let previous_focus_handle = cx.focused();
+
+                            cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
+                                if modal.focus_handle(cx).contains_focused(cx) {
+                                    if previous_focus_handle.is_some() {
+                                        cx.focus(&previous_focus_handle.as_ref().unwrap())
+                                    }
+                                }
+                                *menu2.borrow_mut() = None;
+                                cx.notify();
+                            })
+                            .detach();
+                            cx.focus_view(&new_menu);
+                            *menu.borrow_mut() = Some(new_menu);
+                        }
+                    })
+                })
+                .into_any_element()
+        }));
+        self
+    }
+
+    /// anchor defines which corner of the menu to anchor to the attachment point
+    /// (by default the cursor position, but see attach)
+    pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
+        self.anchor = anchor;
+        self
+    }
+
+    /// attach defines which corner of the handle to attach the menu's anchor to
+    pub fn attach(mut self, attach: AnchorCorner) -> Self {
+        self.attach = Some(attach);
+        self
+    }
+
+    /// offset offsets the position of the content by that many pixels.
+    pub fn offset(mut self, offset: Point<Pixels>) -> Self {
+        self.offset = Some(offset);
+        self
+    }
+
+    fn resolved_attach(&self) -> AnchorCorner {
+        self.attach.unwrap_or_else(|| match self.anchor {
+            AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
+            AnchorCorner::TopRight => AnchorCorner::BottomRight,
+            AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
+            AnchorCorner::BottomRight => AnchorCorner::TopRight,
+        })
+    }
+
+    fn resolved_offset(&self, cx: &WindowContext) -> Point<Pixels> {
+        self.offset.unwrap_or_else(|| {
+            // Default offset = 4px padding + 1px border
+            let offset = rems(5. / 16.) * cx.rem_size();
+            match self.anchor {
+                AnchorCorner::TopRight | AnchorCorner::BottomRight => point(offset, px(0.)),
+                AnchorCorner::TopLeft | AnchorCorner::BottomLeft => point(-offset, px(0.)),
+            }
+        })
+    }
+}
+
+pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {
+    PopoverMenu {
+        id: id.into(),
+        child_builder: None,
+        menu_builder: None,
+        anchor: AnchorCorner::TopLeft,
+        attach: None,
+        offset: None,
+    }
+}
+
+pub struct PopoverMenuState<M> {
+    child_layout_id: Option<LayoutId>,
+    child_element: Option<AnyElement>,
+    child_bounds: Option<Bounds<Pixels>>,
+    menu_element: Option<AnyElement>,
+    menu: Rc<RefCell<Option<View<M>>>>,
+}
+
+impl<M: ManagedView> Element for PopoverMenu<M> {
+    type State = PopoverMenuState<M>;
+
+    fn layout(
+        &mut self,
+        element_state: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (gpui::LayoutId, Self::State) {
+        let mut menu_layout_id = None;
+
+        let (menu, child_bounds) = if let Some(element_state) = element_state {
+            (element_state.menu, element_state.child_bounds)
+        } else {
+            (Rc::default(), None)
+        };
+
+        let menu_element = menu.borrow_mut().as_mut().map(|menu| {
+            let mut overlay = overlay().snap_to_window().anchor(self.anchor);
+
+            if let Some(child_bounds) = child_bounds {
+                overlay = overlay.position(
+                    self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx),
+                );
+            }
+
+            let mut element = overlay.child(menu.clone()).into_any();
+            menu_layout_id = Some(element.layout(cx));
+            element
+        });
+
+        let mut child_element = self
+            .child_builder
+            .take()
+            .map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone()));
+
+        let child_layout_id = child_element
+            .as_mut()
+            .map(|child_element| child_element.layout(cx));
+
+        let layout_id = cx.request_layout(
+            &gpui::Style::default(),
+            menu_layout_id.into_iter().chain(child_layout_id),
+        );
+
+        (
+            layout_id,
+            PopoverMenuState {
+                menu,
+                child_element,
+                child_layout_id,
+                menu_element,
+                child_bounds,
+            },
+        )
+    }
+
+    fn paint(
+        self,
+        _: Bounds<gpui::Pixels>,
+        element_state: &mut Self::State,
+        cx: &mut WindowContext,
+    ) {
+        if let Some(child) = element_state.child_element.take() {
+            child.paint(cx);
+        }
+
+        if let Some(child_layout_id) = element_state.child_layout_id.take() {
+            element_state.child_bounds = Some(cx.layout_bounds(child_layout_id));
+        }
+
+        if let Some(menu) = element_state.menu_element.take() {
+            menu.paint(cx);
+
+            if let Some(child_bounds) = element_state.child_bounds {
+                let interactive_bounds = InteractiveBounds {
+                    bounds: child_bounds,
+                    stacking_order: cx.stacking_order().clone(),
+                };
+
+                // Mouse-downing outside the menu dismisses it, so we don't
+                // want a click on the toggle to re-open it.
+                cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble
+                        && interactive_bounds.visibly_contains(&e.position, cx)
+                    {
+                        cx.stop_propagation()
+                    }
+                })
+            }
+        }
+    }
+}
+
+impl<M: ManagedView> IntoElement for PopoverMenu<M> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<gpui::ElementId> {
+        Some(self.id.clone())
+    }
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/ui2/src/components/right_click_menu.rs πŸ”—

@@ -0,0 +1,185 @@
+use std::{cell::RefCell, rc::Rc};
+
+use gpui::{
+    overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, ElementId,
+    IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
+    View, VisualContext, WindowContext,
+};
+
+pub struct RightClickMenu<M: ManagedView> {
+    id: ElementId,
+    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<M: ManagedView> RightClickMenu<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 trigger<E: IntoElement + 'static>(mut self, e: E) -> Self {
+        self.child_builder = Some(Box::new(move |_| e.into_any_element()));
+        self
+    }
+
+    /// anchor defines which corner of the menu to anchor to the attachment point
+    /// (by default the cursor position, but see attach)
+    pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
+        self.anchor = Some(anchor);
+        self
+    }
+
+    /// attach defines which corner of the handle to attach the menu's anchor to
+    pub fn attach(mut self, attach: AnchorCorner) -> Self {
+        self.attach = Some(attach);
+        self
+    }
+}
+
+pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
+    RightClickMenu {
+        id: id.into(),
+        child_builder: None,
+        menu_builder: None,
+        anchor: None,
+        attach: None,
+    }
+}
+
+pub struct MenuHandleState<M> {
+    menu: Rc<RefCell<Option<View<M>>>>,
+    position: Rc<RefCell<Point<Pixels>>>,
+    child_layout_id: Option<LayoutId>,
+    child_element: Option<AnyElement>,
+    menu_element: Option<AnyElement>,
+}
+
+impl<M: ManagedView> Element for RightClickMenu<M> {
+    type State = MenuHandleState<M>;
+
+    fn layout(
+        &mut self,
+        element_state: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (gpui::LayoutId, Self::State) {
+        let (menu, position) = if let Some(element_state) = element_state {
+            (element_state.menu, element_state.position)
+        } else {
+            (Rc::default(), Rc::default())
+        };
+
+        let mut menu_layout_id = None;
+
+        let menu_element = menu.borrow_mut().as_mut().map(|menu| {
+            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(cx));
+            element
+        });
+
+        let mut child_element = self
+            .child_builder
+            .take()
+            .map(|child_builder| (child_builder)(menu.borrow().is_some()));
+
+        let child_layout_id = child_element
+            .as_mut()
+            .map(|child_element| child_element.layout(cx));
+
+        let layout_id = cx.request_layout(
+            &gpui::Style::default(),
+            menu_layout_id.into_iter().chain(child_layout_id),
+        );
+
+        (
+            layout_id,
+            MenuHandleState {
+                menu,
+                position,
+                child_element,
+                child_layout_id,
+                menu_element,
+            },
+        )
+    }
+
+    fn paint(
+        self,
+        bounds: Bounds<gpui::Pixels>,
+        element_state: &mut Self::State,
+        cx: &mut WindowContext,
+    ) {
+        if let Some(child) = element_state.child_element.take() {
+            child.paint(cx);
+        }
+
+        if let Some(menu) = element_state.menu_element.take() {
+            menu.paint(cx);
+            return;
+        }
+
+        let Some(builder) = self.menu_builder else {
+            return;
+        };
+        let menu = element_state.menu.clone();
+        let position = element_state.position.clone();
+        let attach = self.attach.clone();
+        let child_layout_id = element_state.child_layout_id.clone();
+
+        cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+            if phase == DispatchPhase::Bubble
+                && event.button == MouseButton::Right
+                && bounds.contains_point(&event.position)
+            {
+                cx.stop_propagation();
+                cx.prevent_default();
+
+                let new_menu = (builder)(cx);
+                let menu2 = menu.clone();
+                let previous_focus_handle = cx.focused();
+
+                cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
+                    if modal.focus_handle(cx).contains_focused(cx) {
+                        if previous_focus_handle.is_some() {
+                            cx.focus(&previous_focus_handle.as_ref().unwrap())
+                        }
+                    }
+                    *menu2.borrow_mut() = None;
+                    cx.notify();
+                })
+                .detach();
+                cx.focus_view(&new_menu);
+                *menu.borrow_mut() = Some(new_menu);
+
+                *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
+                    attach
+                        .unwrap()
+                        .corner(cx.layout_bounds(child_layout_id.unwrap()))
+                } else {
+                    cx.mouse_position()
+                };
+                cx.notify();
+            }
+        });
+    }
+}
+
+impl<M: ManagedView> IntoElement for RightClickMenu<M> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<gpui::ElementId> {
+        Some(self.id.clone())
+    }
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/ui2/src/components/slot.rs πŸ”—

@@ -1,14 +0,0 @@
-use gpui::{ImageSource, SharedString};
-
-use crate::Icon;
-
-#[derive(Debug, Clone)]
-/// A slot utility that provides a way to to pass either
-/// an icon or an image to a component.
-///
-/// Can be filled with a []
-pub enum GraphicSlot {
-    Icon(Icon),
-    Avatar(ImageSource),
-    PublicActor(SharedString),
-}

crates/ui2/src/components/stories.rs πŸ”—

@@ -2,20 +2,24 @@ mod avatar;
 mod button;
 mod checkbox;
 mod context_menu;
+mod disclosure;
 mod icon;
 mod icon_button;
 mod keybinding;
 mod label;
 mod list;
+mod list_header;
 mod list_item;
 
 pub use avatar::*;
 pub use button::*;
 pub use checkbox::*;
 pub use context_menu::*;
+pub use disclosure::*;
 pub use icon::*;
 pub use icon_button::*;
 pub use keybinding::*;
 pub use label::*;
 pub use list::*;
+pub use list_header::*;
 pub use list_item::*;

crates/ui2/src/components/stories/avatar.rs πŸ”—

@@ -19,5 +19,13 @@ impl Render for AvatarStory {
             .child(Avatar::uri(
                 "https://avatars.githubusercontent.com/u/326587?v=4",
             ))
+            .child(
+                Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4")
+                    .availability_indicator(true),
+            )
+            .child(
+                Avatar::uri("https://avatars.githubusercontent.com/u/326587?v=4")
+                    .availability_indicator(false),
+            )
     }
 }

crates/ui2/src/components/stories/button.rs πŸ”—

@@ -1,8 +1,8 @@
 use gpui::{Div, Render};
 use story::Story;
 
-use crate::prelude::*;
-use crate::{h_stack, Button, Icon, IconPosition};
+use crate::{prelude::*, Icon};
+use crate::{Button, ButtonStyle};
 
 pub struct ButtonStory;
 
@@ -12,66 +12,29 @@ impl Render for ButtonStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
         Story::container()
             .child(Story::title_for::<Button>())
+            .child(Story::label("Default"))
+            .child(Button::new("default_filled", "Click me"))
+            .child(Story::label("Selected"))
+            .child(Button::new("selected_filled", "Click me").selected(true))
+            .child(Story::label("Selected with `selected_label`"))
             .child(
-                div()
-                    .flex()
-                    .gap_8()
-                    .child(
-                        div().child(Story::label("Ghost (Default)")).child(
-                            h_stack()
-                                .gap_2()
-                                .child(Button::new("Label").variant(ButtonVariant::Ghost)),
-                        ),
-                    )
-                    .child(Story::label("Ghost – Left Icon"))
-                    .child(
-                        h_stack().gap_2().child(
-                            Button::new("Label")
-                                .variant(ButtonVariant::Ghost)
-                                .icon(Icon::Plus)
-                                .icon_position(IconPosition::Left),
-                        ),
-                    ),
+                Button::new("selected_label_filled", "Click me")
+                    .selected(true)
+                    .selected_label("I have been selected"),
             )
-            .child(Story::label("Ghost – Right Icon"))
+            .child(Story::label("With `label_color`"))
+            .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
+            .child(Story::label("With `icon`"))
+            .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit))
+            .child(Story::label("Selected with `icon`"))
             .child(
-                h_stack().gap_2().child(
-                    Button::new("Label")
-                        .variant(ButtonVariant::Ghost)
-                        .icon(Icon::Plus)
-                        .icon_position(IconPosition::Right),
-                ),
-            )
-            .child(
-                div().child(Story::label("Filled")).child(
-                    h_stack()
-                        .gap_2()
-                        .child(Button::new("Label").variant(ButtonVariant::Filled)),
-                ),
-            )
-            .child(Story::label("Filled – Left Button"))
-            .child(
-                h_stack().gap_2().child(
-                    Button::new("Label")
-                        .variant(ButtonVariant::Filled)
-                        .icon(Icon::Plus)
-                        .icon_position(IconPosition::Left),
-                ),
-            )
-            .child(Story::label("Filled – Right Button"))
-            .child(
-                h_stack().gap_2().child(
-                    Button::new("Label")
-                        .variant(ButtonVariant::Filled)
-                        .icon(Icon::Plus)
-                        .icon_position(IconPosition::Right),
-                ),
-            )
-            .child(Story::label("Button with `on_click`"))
-            .child(
-                Button::new("Label")
-                    .variant(ButtonVariant::Ghost)
-                    .on_click(|_, _cx| println!("Button clicked.")),
+                Button::new("filled_and_selected_with_icon", "Click me")
+                    .selected(true)
+                    .icon(Icon::FileGit),
             )
+            .child(Story::label("Default (Subtle)"))
+            .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
+            .child(Story::label("Default (Transparent)"))
+            .child(Button::new("default_transparent", "Click me").style(ButtonStyle::Transparent))
     }
 }

crates/ui2/src/components/stories/context_menu.rs πŸ”—

@@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View};
 use story::Story;
 
 use crate::prelude::*;
-use crate::{menu_handle, ContextMenu, Label};
+use crate::{right_click_menu, ContextMenu, Label};
 
 actions!(PrintCurrentDate, PrintBestFood);
 
@@ -45,25 +45,13 @@ impl Render for ContextMenuStory {
                     .flex_col()
                     .justify_between()
                     .child(
-                        menu_handle("test2")
-                            .child(|is_open| {
-                                Label::new(if is_open {
-                                    "TOP LEFT"
-                                } else {
-                                    "RIGHT CLICK ME"
-                                })
-                            })
+                        right_click_menu("test2")
+                            .trigger(Label::new("TOP LEFT"))
                             .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"
-                                })
-                            })
+                        right_click_menu("test1")
+                            .trigger(Label::new("BOTTOM LEFT"))
                             .anchor(AnchorCorner::BottomLeft)
                             .attach(AnchorCorner::TopLeft)
                             .menu(move |cx| build_menu(cx, "bottom left")),
@@ -75,26 +63,14 @@ impl Render for ContextMenuStory {
                     .flex_col()
                     .justify_between()
                     .child(
-                        menu_handle("test3")
-                            .child(|is_open| {
-                                Label::new(if is_open {
-                                    "TOP RIGHT"
-                                } else {
-                                    "RIGHT CLICK ME"
-                                })
-                            })
+                        right_click_menu("test3")
+                            .trigger(Label::new("TOP RIGHT"))
                             .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"
-                                })
-                            })
+                        right_click_menu("test4")
+                            .trigger(Label::new("BOTTOM RIGHT"))
                             .anchor(AnchorCorner::BottomRight)
                             .attach(AnchorCorner::TopRight)
                             .menu(move |cx| build_menu(cx, "bottom right")),

crates/ui2/src/components/stories/disclosure.rs πŸ”—

@@ -0,0 +1,20 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::prelude::*;
+use crate::Disclosure;
+
+pub struct DisclosureStory;
+
+impl Render for DisclosureStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<Disclosure>())
+            .child(Story::label("Toggled"))
+            .child(Disclosure::new(true))
+            .child(Story::label("Not Toggled"))
+            .child(Disclosure::new(false))
+    }
+}

crates/ui2/src/components/stories/icon_button.rs πŸ”—

@@ -14,6 +14,26 @@ impl Render for IconButtonStory {
             .child(Story::title_for::<IconButton>())
             .child(Story::label("Default"))
             .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
+            .child(Story::label("Selected"))
+            .child(
+                div()
+                    .w_8()
+                    .child(IconButton::new("icon_a", Icon::Hash).selected(true)),
+            )
+            .child(Story::label("Selected with `selected_icon`"))
+            .child(
+                div().w_8().child(
+                    IconButton::new("icon_a", Icon::AudioOn)
+                        .selected(true)
+                        .selected_icon(Icon::AudioOff),
+                ),
+            )
+            .child(Story::label("Disabled"))
+            .child(
+                div()
+                    .w_8()
+                    .child(IconButton::new("icon_a", Icon::Hash).disabled(true)),
+            )
             .child(Story::label("With `on_click`"))
             .child(
                 div()

crates/ui2/src/components/stories/list.rs πŸ”—

@@ -22,12 +22,12 @@ impl Render for ListStory {
             .child(Story::label("With sections"))
             .child(
                 List::new()
-                    .child(ListHeader::new("Fruits"))
+                    .header(ListHeader::new("Produce"))
+                    .child(ListSubHeader::new("Fruits"))
                     .child(ListItem::new("apple").child("Apple"))
                     .child(ListItem::new("banana").child("Banana"))
                     .child(ListItem::new("cherry").child("Cherry"))
                     .child(ListSeparator)
-                    .child(ListHeader::new("Vegetables"))
                     .child(ListSubHeader::new("Root Vegetables"))
                     .child(ListItem::new("carrot").child("Carrot"))
                     .child(ListItem::new("potato").child("Potato"))

crates/ui2/src/components/stories/list_header.rs πŸ”—

@@ -0,0 +1,33 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::{prelude::*, IconButton};
+use crate::{Icon, ListHeader};
+
+pub struct ListHeaderStory;
+
+impl Render for ListHeaderStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<ListHeader>())
+            .child(Story::label("Default"))
+            .child(ListHeader::new("Section 1"))
+            .child(Story::label("With left icon"))
+            .child(ListHeader::new("Section 2").left_icon(Icon::Bell))
+            .child(Story::label("With left icon and meta"))
+            .child(
+                ListHeader::new("Section 3")
+                    .left_icon(Icon::BellOff)
+                    .meta(IconButton::new("action_1", Icon::Bolt)),
+            )
+            .child(Story::label("With multiple meta"))
+            .child(
+                ListHeader::new("Section 4")
+                    .meta(IconButton::new("action_1", Icon::Bolt))
+                    .meta(IconButton::new("action_2", Icon::ExclamationTriangle))
+                    .meta(IconButton::new("action_3", Icon::Plus)),
+            )
+    }
+}

crates/ui2/src/components/toggle.rs πŸ”—

@@ -1,41 +0,0 @@
-/// 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.
-///
-/// You can check if an element is toggleable with `.is_toggleable()`
-///
-/// Possible values:
-/// - `Toggle::NotToggleable` - The entry is not toggleable
-/// - `Toggle::Toggled(true)` - The entry is toggleable and toggled
-/// - `Toggle::Toggled(false)` - The entry is toggleable and not toggled
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum Toggle {
-    NotToggleable,
-    Toggled(bool),
-}
-
-impl Toggle {
-    /// Returns true if the entry is toggled (or is not toggleable.)
-    ///
-    /// As element that isn't toggleable is always "expanded" or "enabled"
-    /// returning true in that case makes sense.
-    pub fn is_toggled(&self) -> bool {
-        match self {
-            Self::Toggled(false) => false,
-            _ => true,
-        }
-    }
-
-    pub fn is_toggleable(&self) -> bool {
-        match self {
-            Self::Toggled(_) => true,
-            _ => false,
-        }
-    }
-}
-
-impl From<bool> for Toggle {
-    fn from(toggled: bool) -> Self {
-        Toggle::Toggled(toggled)
-    }
-}

crates/ui2/src/prelude.rs πŸ”—

@@ -8,6 +8,5 @@ pub use crate::clickable::*;
 pub use crate::disableable::*;
 pub use crate::fixed::*;
 pub use crate::selectable::*;
-pub use crate::StyledExt;
-pub use crate::{ButtonVariant, Color};
+pub use crate::{ButtonCommon, Color, StyledExt};
 pub use theme::ActiveTheme;

crates/ui2/src/selectable.rs πŸ”—

@@ -1,15 +1,7 @@
-use gpui::{AnyView, WindowContext};
-
 /// A trait for elements that can be selected.
 pub trait Selectable {
     /// Sets whether the element is selected.
     fn selected(self, selected: bool) -> Self;
-
-    /// Sets the tooltip that should be shown when the element is selected.
-    fn selected_tooltip(
-        self,
-        tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
-    ) -> Self;
 }
 
 #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]

crates/welcome2/src/base_keymap_picker.rs πŸ”—

@@ -1,14 +1,14 @@
 use super::base_keymap_setting::BaseKeymap;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
-    View, ViewContext, VisualContext, WeakView,
+    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, Task, View,
+    ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use project::Fs;
 use settings::{update_settings_file, Settings};
 use std::sync::Arc;
-use ui::ListItem;
+use ui::{prelude::*, ListItem};
 use util::ResultExt;
 use workspace::{ui::HighlightedLabel, Workspace};
 

crates/workspace2/Cargo.toml πŸ”—

@@ -10,29 +10,29 @@ doctest = false
 
 [features]
 test-support = [
-    "call2/test-support",
-    "client2/test-support",
-    "project2/test-support",
-    "settings2/test-support",
+    "call/test-support",
+    "client/test-support",
+    "project/test-support",
+    "settings/test-support",
     "gpui/test-support",
-    "fs2/test-support"
+    "fs/test-support"
 ]
 
 [dependencies]
-db2 = { path = "../db2" }
-client2 = { path = "../client2" }
+db = { path = "../db2", package = "db2" }
+client = { path = "../client2", package = "client2" }
 collections = { path = "../collections" }
 # context_menu = { path = "../context_menu" }
-fs2 = { path = "../fs2" }
+fs = { path = "../fs2", package = "fs2" }
 gpui = { package = "gpui2", path = "../gpui2" }
-install_cli2 = { path = "../install_cli2" }
-language2 = { path = "../language2" }
+install_cli = { path = "../install_cli2", package = "install_cli2" }
+language = { path = "../language2", package = "language2" }
 #menu = { path = "../menu" }
 node_runtime = { path = "../node_runtime" }
-project2 = { path = "../project2" }
-settings2 = { path = "../settings2" }
-terminal2 = { path = "../terminal2" }
-theme2 = { path = "../theme2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+terminal = { path = "../terminal2", package = "terminal2" }
+theme = { path = "../theme2", package = "theme2" }
 util = { path = "../util" }
 ui = { package = "ui2", path = "../ui2" }
 
@@ -54,13 +54,13 @@ smallvec.workspace = true
 uuid.workspace = true
 
 [dev-dependencies]
-call2 = { path = "../call2", features = ["test-support"] }
-client2 = { path = "../client2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-project2 = { path = "../project2", features = ["test-support"] }
-settings2 = { path = "../settings2", features = ["test-support"] }
-fs2 = { path = "../fs2", features = ["test-support"] }
-db2 = { path = "../db2", features = ["test-support"] }
+call = { path = "../call2", package = "call2", features = ["test-support"] }
+client = { path = "../client2", package = "client2", features = ["test-support"] }
+gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+project = { path = "../project2", package = "project2", features = ["test-support"] }
+settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
+fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
+db = { path = "../db2", package = "db2", features = ["test-support"] }
 
 indoc.workspace = true
 env_logger.workspace = true

crates/workspace2/src/dock.rs πŸ”—

@@ -7,8 +7,8 @@ use gpui::{
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
-use ui::prelude::*;
-use ui::{h_stack, menu_handle, ContextMenu, IconButton, Tooltip};
+use ui::{h_stack, ContextMenu, IconButton, Tooltip};
+use ui::{prelude::*, right_click_menu};
 
 pub enum PanelEvent {
     ChangePosition,
@@ -701,13 +701,8 @@ impl Render for PanelButtons {
                     (action, name.into())
                 };
 
-                let button = IconButton::new(name, icon)
-                    .selected(is_active_button)
-                    .action(action.boxed_clone())
-                    .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx));
-
                 Some(
-                    menu_handle(name)
+                    right_click_menu(name)
                         .menu(move |cx| {
                             const POSITIONS: [DockPosition; 3] = [
                                 DockPosition::Left,
@@ -731,7 +726,14 @@ impl Render for PanelButtons {
                         })
                         .anchor(menu_anchor)
                         .attach(menu_attach)
-                        .child(|is_open| button.selected(is_open)),
+                        .trigger(
+                            IconButton::new(name, icon)
+                                .selected(is_active_button)
+                                .action(action.boxed_clone())
+                                .tooltip(move |cx| {
+                                    Tooltip::for_action(tooltip.clone(), &*action, cx)
+                                }),
+                        ),
                 )
             });
 

crates/workspace2/src/item.rs πŸ”—

@@ -7,7 +7,7 @@ use crate::{
     ViewId, Workspace, WorkspaceId,
 };
 use anyhow::Result;
-use client2::{
+use client::{
     proto::{self, PeerId},
     Client,
 };
@@ -16,10 +16,10 @@ use gpui::{
     HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
     WindowContext,
 };
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -33,7 +33,7 @@ use std::{
     },
     time::Duration,
 };
-use theme2::Theme;
+use theme::Theme;
 
 #[derive(Deserialize)]
 pub struct ItemSettings {
@@ -110,7 +110,7 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
     fn for_each_project_item(
         &self,
         _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
     ) {
     }
     fn is_singleton(&self, _cx: &AppContext) -> bool {
@@ -222,7 +222,7 @@ pub trait ItemHandle: 'static + Send {
     fn for_each_project_item(
         &self,
         _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
     );
     fn is_singleton(&self, cx: &AppContext) -> bool;
     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
@@ -347,7 +347,7 @@ impl<T: Item> ItemHandle for View<T> {
     fn for_each_project_item(
         &self,
         cx: &AppContext,
-        f: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        f: &mut dyn FnMut(EntityId, &dyn project::Item),
     ) {
         self.read(cx).for_each_project_item(cx, f)
     }
@@ -375,6 +375,7 @@ impl<T: Item> ItemHandle for View<T> {
         pane: View<Pane>,
         cx: &mut ViewContext<Workspace>,
     ) {
+        let weak_item = self.downgrade();
         let history = pane.read(cx).nav_history_for_item(self);
         self.update(cx, |this, cx| {
             this.set_nav_history(history, cx);
@@ -491,16 +492,15 @@ impl<T: Item> ItemHandle for View<T> {
                     }
                 }));
 
-            // todo!()
-            // cx.observe_focus(self, move |workspace, item, focused, cx| {
-            //     if !focused
-            //         && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
-            //     {
-            //         Pane::autosave_item(&item, workspace.project.clone(), cx)
-            //             .detach_and_log_err(cx);
-            //     }
-            // })
-            // .detach();
+            cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
+                if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
+                    if let Some(item) = weak_item.upgrade() {
+                        Pane::autosave_item(&item, workspace.project.clone(), cx)
+                            .detach_and_log_err(cx);
+                    }
+                }
+            })
+            .detach();
 
             let item_id = self.item_id();
             cx.observe_release(self, move |workspace, _, _| {
@@ -640,7 +640,7 @@ impl<T: Item> WeakItemHandle for WeakView<T> {
 }
 
 pub trait ProjectItem: Item {
-    type Item: project2::Item;
+    type Item: project::Item;
 
     fn for_project_item(
         project: Model<Project>,
@@ -662,19 +662,19 @@ pub trait FollowableEvents {
 pub trait FollowableItem: Item {
     type FollowableEvent: FollowableEvents;
     fn remote_id(&self) -> Option<ViewId>;
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
     fn from_state_proto(
         pane: View<Pane>,
         project: View<Workspace>,
         id: ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<Task<Result<View<Self>>>>;
     fn add_event_to_update_proto(
         &self,
         event: &Self::FollowableEvent,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool;
     fn apply_update_proto(
         &mut self,
@@ -682,20 +682,20 @@ pub trait FollowableItem: Item {
         message: proto::update_view::Variant,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>>;
-    fn is_project_item(&self, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &WindowContext) -> bool;
 
     fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
 }
 
 pub trait FollowableItemHandle: ItemHandle {
-    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+    fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
     fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
     fn add_event_to_update_proto(
         &self,
         event: &dyn Any,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool;
     fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
     fn apply_update_proto(
@@ -704,11 +704,11 @@ pub trait FollowableItemHandle: ItemHandle {
         message: proto::update_view::Variant,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn is_project_item(&self, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &WindowContext) -> bool;
 }
 
 impl<T: FollowableItem> FollowableItemHandle for View<T> {
-    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+    fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId> {
         self.read(cx).remote_id().or_else(|| {
             client.peer_id().map(|creator| ViewId {
                 creator,
@@ -721,7 +721,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
     }
 
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
         self.read(cx).to_state_proto(cx)
     }
 
@@ -729,7 +729,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         &self,
         event: &dyn Any,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool {
         if let Some(event) = event.downcast_ref() {
             self.read(cx).add_event_to_update_proto(event, update, cx)
@@ -754,305 +754,315 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
     }
 
-    fn is_project_item(&self, cx: &AppContext) -> bool {
+    fn is_project_item(&self, cx: &WindowContext) -> bool {
         self.read(cx).is_project_item(cx)
     }
 }
 
-// #[cfg(any(test, feature = "test-support"))]
-// pub mod test {
-//     use super::{Item, ItemEvent};
-//     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
-//     use gpui::{
-//         elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
-//         ViewContext, View, WeakViewHandle,
-//     };
-//     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
-//     use smallvec::SmallVec;
-//     use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
-
-//     pub struct TestProjectItem {
-//         pub entry_id: Option<ProjectEntryId>,
-//         pub project_path: Option<ProjectPath>,
-//     }
-
-//     pub struct TestItem {
-//         pub workspace_id: WorkspaceId,
-//         pub state: String,
-//         pub label: String,
-//         pub save_count: usize,
-//         pub save_as_count: usize,
-//         pub reload_count: usize,
-//         pub is_dirty: bool,
-//         pub is_singleton: bool,
-//         pub has_conflict: bool,
-//         pub project_items: Vec<Model<TestProjectItem>>,
-//         pub nav_history: Option<ItemNavHistory>,
-//         pub tab_descriptions: Option<Vec<&'static str>>,
-//         pub tab_detail: Cell<Option<usize>>,
-//     }
-
-//     impl Entity for TestProjectItem {
-//         type Event = ();
-//     }
-
-//     impl project2::Item for TestProjectItem {
-//         fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-//             self.entry_id
-//         }
-
-//         fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
-//             self.project_path.clone()
-//         }
-//     }
-
-//     pub enum TestItemEvent {
-//         Edit,
-//     }
-
-//     impl Clone for TestItem {
-//         fn clone(&self) -> Self {
-//             Self {
-//                 state: self.state.clone(),
-//                 label: self.label.clone(),
-//                 save_count: self.save_count,
-//                 save_as_count: self.save_as_count,
-//                 reload_count: self.reload_count,
-//                 is_dirty: self.is_dirty,
-//                 is_singleton: self.is_singleton,
-//                 has_conflict: self.has_conflict,
-//                 project_items: self.project_items.clone(),
-//                 nav_history: None,
-//                 tab_descriptions: None,
-//                 tab_detail: Default::default(),
-//                 workspace_id: self.workspace_id,
-//             }
-//         }
-//     }
-
-//     impl TestProjectItem {
-//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
-//             let entry_id = Some(ProjectEntryId::from_proto(id));
-//             let project_path = Some(ProjectPath {
-//                 worktree_id: WorktreeId::from_usize(0),
-//                 path: Path::new(path).into(),
-//             });
-//             cx.add_model(|_| Self {
-//                 entry_id,
-//                 project_path,
-//             })
-//         }
-
-//         pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
-//             cx.add_model(|_| Self {
-//                 project_path: None,
-//                 entry_id: None,
-//             })
-//         }
-//     }
-
-//     impl TestItem {
-//         pub fn new() -> Self {
-//             Self {
-//                 state: String::new(),
-//                 label: String::new(),
-//                 save_count: 0,
-//                 save_as_count: 0,
-//                 reload_count: 0,
-//                 is_dirty: false,
-//                 has_conflict: false,
-//                 project_items: Vec::new(),
-//                 is_singleton: true,
-//                 nav_history: None,
-//                 tab_descriptions: None,
-//                 tab_detail: Default::default(),
-//                 workspace_id: 0,
-//             }
-//         }
-
-//         pub fn new_deserialized(id: WorkspaceId) -> Self {
-//             let mut this = Self::new();
-//             this.workspace_id = id;
-//             this
-//         }
-
-//         pub fn with_label(mut self, state: &str) -> Self {
-//             self.label = state.to_string();
-//             self
-//         }
-
-//         pub fn with_singleton(mut self, singleton: bool) -> Self {
-//             self.is_singleton = singleton;
-//             self
-//         }
-
-//         pub fn with_dirty(mut self, dirty: bool) -> Self {
-//             self.is_dirty = dirty;
-//             self
-//         }
-
-//         pub fn with_conflict(mut self, has_conflict: bool) -> Self {
-//             self.has_conflict = has_conflict;
-//             self
-//         }
-
-//         pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
-//             self.project_items.clear();
-//             self.project_items.extend(items.iter().cloned());
-//             self
-//         }
-
-//         pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
-//             self.push_to_nav_history(cx);
-//             self.state = state;
-//         }
-
-//         fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
-//             if let Some(history) = &mut self.nav_history {
-//                 history.push(Some(Box::new(self.state.clone())), cx);
-//             }
-//         }
-//     }
-
-//     impl Entity for TestItem {
-//         type Event = TestItemEvent;
-//     }
-
-//     impl View for TestItem {
-//         fn ui_name() -> &'static str {
-//             "TestItem"
-//         }
-
-//         fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-//             Empty::new().into_any()
-//         }
-//     }
-
-//     impl Item for TestItem {
-//         fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
-//             self.tab_descriptions.as_ref().and_then(|descriptions| {
-//                 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
-//                 Some(description.into())
-//             })
-//         }
-
-//         fn tab_content<V: 'static>(
-//             &self,
-//             detail: Option<usize>,
-//             _: &theme2::Tab,
-//             _: &AppContext,
-//         ) -> AnyElement<V> {
-//             self.tab_detail.set(detail);
-//             Empty::new().into_any()
-//         }
-
-//         fn for_each_project_item(
-//             &self,
-//             cx: &AppContext,
-//             f: &mut dyn FnMut(usize, &dyn project2::Item),
-//         ) {
-//             self.project_items
-//                 .iter()
-//                 .for_each(|item| f(item.id(), item.read(cx)))
-//         }
-
-// fn is_singleton(&self, _: &AppContext) -> bool {
-//     self.is_singleton
-// }
-
-//         fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
-//             self.nav_history = Some(history);
-//         }
-
-//         fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-//             let state = *state.downcast::<String>().unwrap_or_default();
-//             if state != self.state {
-//                 self.state = state;
-//                 true
-//             } else {
-//                 false
-//             }
-//         }
-
-//         fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-//             self.push_to_nav_history(cx);
-//         }
-
-//         fn clone_on_split(
-//             &self,
-//             _workspace_id: WorkspaceId,
-//             _: &mut ViewContext<Self>,
-//         ) -> Option<Self>
-//         where
-//             Self: Sized,
-//         {
-//             Some(self.clone())
-//         }
-
-//         fn is_dirty(&self, _: &AppContext) -> bool {
-//             self.is_dirty
-//         }
-
-//         fn has_conflict(&self, _: &AppContext) -> bool {
-//             self.has_conflict
-//         }
-
-//         fn can_save(&self, cx: &AppContext) -> bool {
-//             !self.project_items.is_empty()
-//                 && self
-//                     .project_items
-//                     .iter()
-//                     .all(|item| item.read(cx).entry_id.is_some())
-//         }
-
-//         fn save(
-//             &mut self,
-//             _: Model<Project>,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.save_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn save_as(
-//             &mut self,
-//             _: Model<Project>,
-//             _: std::path::PathBuf,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.save_as_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn reload(
-//             &mut self,
-//             _: Model<Project>,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.reload_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-//             [ItemEvent::UpdateTab, ItemEvent::Edit].into()
-//         }
-
-//         fn serialized_item_kind() -> Option<&'static str> {
-//             Some("TestItem")
-//         }
-
-//         fn deserialize(
-//             _project: Model<Project>,
-//             _workspace: WeakViewHandle<Workspace>,
-//             workspace_id: WorkspaceId,
-//             _item_id: ItemId,
-//             cx: &mut ViewContext<Pane>,
-//         ) -> Task<anyhow::Result<View<Self>>> {
-//             let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
-//             Task::Ready(Some(anyhow::Ok(view)))
-//         }
-//     }
-// }
+#[cfg(any(test, feature = "test-support"))]
+pub mod test {
+    use super::{Item, ItemEvent};
+    use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
+    use gpui::{
+        AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView,
+        IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView,
+    };
+    use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
+    use std::{any::Any, cell::Cell, path::Path};
+
+    pub struct TestProjectItem {
+        pub entry_id: Option<ProjectEntryId>,
+        pub project_path: Option<ProjectPath>,
+    }
+
+    pub struct TestItem {
+        pub workspace_id: WorkspaceId,
+        pub state: String,
+        pub label: String,
+        pub save_count: usize,
+        pub save_as_count: usize,
+        pub reload_count: usize,
+        pub is_dirty: bool,
+        pub is_singleton: bool,
+        pub has_conflict: bool,
+        pub project_items: Vec<Model<TestProjectItem>>,
+        pub nav_history: Option<ItemNavHistory>,
+        pub tab_descriptions: Option<Vec<&'static str>>,
+        pub tab_detail: Cell<Option<usize>>,
+        focus_handle: gpui::FocusHandle,
+    }
+
+    impl project::Item for TestProjectItem {
+        fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
+            self.entry_id
+        }
+
+        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+            self.project_path.clone()
+        }
+    }
+
+    pub enum TestItemEvent {
+        Edit,
+    }
+
+    // impl Clone for TestItem {
+    //     fn clone(&self) -> Self {
+    //         Self {
+    //             state: self.state.clone(),
+    //             label: self.label.clone(),
+    //             save_count: self.save_count,
+    //             save_as_count: self.save_as_count,
+    //             reload_count: self.reload_count,
+    //             is_dirty: self.is_dirty,
+    //             is_singleton: self.is_singleton,
+    //             has_conflict: self.has_conflict,
+    //             project_items: self.project_items.clone(),
+    //             nav_history: None,
+    //             tab_descriptions: None,
+    //             tab_detail: Default::default(),
+    //             workspace_id: self.workspace_id,
+    //             focus_handle: self.focus_handle.clone(),
+    //         }
+    //     }
+    // }
+
+    impl TestProjectItem {
+        pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
+            let entry_id = Some(ProjectEntryId::from_proto(id));
+            let project_path = Some(ProjectPath {
+                worktree_id: WorktreeId::from_usize(0),
+                path: Path::new(path).into(),
+            });
+            cx.build_model(|_| Self {
+                entry_id,
+                project_path,
+            })
+        }
+
+        pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
+            cx.build_model(|_| Self {
+                project_path: None,
+                entry_id: None,
+            })
+        }
+    }
+
+    impl TestItem {
+        pub fn new(cx: &mut ViewContext<Self>) -> Self {
+            Self {
+                state: String::new(),
+                label: String::new(),
+                save_count: 0,
+                save_as_count: 0,
+                reload_count: 0,
+                is_dirty: false,
+                has_conflict: false,
+                project_items: Vec::new(),
+                is_singleton: true,
+                nav_history: None,
+                tab_descriptions: None,
+                tab_detail: Default::default(),
+                workspace_id: 0,
+                focus_handle: cx.focus_handle(),
+            }
+        }
+
+        pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
+            let mut this = Self::new(cx);
+            this.workspace_id = id;
+            this
+        }
+
+        pub fn with_label(mut self, state: &str) -> Self {
+            self.label = state.to_string();
+            self
+        }
+
+        pub fn with_singleton(mut self, singleton: bool) -> Self {
+            self.is_singleton = singleton;
+            self
+        }
+
+        pub fn with_dirty(mut self, dirty: bool) -> Self {
+            self.is_dirty = dirty;
+            self
+        }
+
+        pub fn with_conflict(mut self, has_conflict: bool) -> Self {
+            self.has_conflict = has_conflict;
+            self
+        }
+
+        pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
+            self.project_items.clear();
+            self.project_items.extend(items.iter().cloned());
+            self
+        }
+
+        pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+            self.push_to_nav_history(cx);
+            self.state = state;
+        }
+
+        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+            if let Some(history) = &mut self.nav_history {
+                history.push(Some(Box::new(self.state.clone())), cx);
+            }
+        }
+    }
+
+    impl Render for TestItem {
+        type Element = Div;
+
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+            gpui::div()
+        }
+    }
+
+    impl EventEmitter<ItemEvent> for TestItem {}
+
+    impl FocusableView for TestItem {
+        fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+            self.focus_handle.clone()
+        }
+    }
+
+    impl Item for TestItem {
+        fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
+            self.tab_descriptions.as_ref().and_then(|descriptions| {
+                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+                Some(description.into())
+            })
+        }
+
+        fn tab_content(
+            &self,
+            detail: Option<usize>,
+            cx: &ui::prelude::WindowContext,
+        ) -> AnyElement {
+            self.tab_detail.set(detail);
+            gpui::div().into_any_element()
+        }
+
+        fn for_each_project_item(
+            &self,
+            cx: &AppContext,
+            f: &mut dyn FnMut(EntityId, &dyn project::Item),
+        ) {
+            self.project_items
+                .iter()
+                .for_each(|item| f(item.entity_id(), item.read(cx)))
+        }
+
+        fn is_singleton(&self, _: &AppContext) -> bool {
+            self.is_singleton
+        }
+
+        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+            self.nav_history = Some(history);
+        }
+
+        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+            let state = *state.downcast::<String>().unwrap_or_default();
+            if state != self.state {
+                self.state = state;
+                true
+            } else {
+                false
+            }
+        }
+
+        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+            self.push_to_nav_history(cx);
+        }
+
+        fn clone_on_split(
+            &self,
+            _workspace_id: WorkspaceId,
+            cx: &mut ViewContext<Self>,
+        ) -> Option<View<Self>>
+        where
+            Self: Sized,
+        {
+            Some(cx.build_view(|cx| Self {
+                state: self.state.clone(),
+                label: self.label.clone(),
+                save_count: self.save_count,
+                save_as_count: self.save_as_count,
+                reload_count: self.reload_count,
+                is_dirty: self.is_dirty,
+                is_singleton: self.is_singleton,
+                has_conflict: self.has_conflict,
+                project_items: self.project_items.clone(),
+                nav_history: None,
+                tab_descriptions: None,
+                tab_detail: Default::default(),
+                workspace_id: self.workspace_id,
+                focus_handle: cx.focus_handle(),
+            }))
+        }
+
+        fn is_dirty(&self, _: &AppContext) -> bool {
+            self.is_dirty
+        }
+
+        fn has_conflict(&self, _: &AppContext) -> bool {
+            self.has_conflict
+        }
+
+        fn can_save(&self, cx: &AppContext) -> bool {
+            !self.project_items.is_empty()
+                && self
+                    .project_items
+                    .iter()
+                    .all(|item| item.read(cx).entry_id.is_some())
+        }
+
+        fn save(
+            &mut self,
+            _: Model<Project>,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.save_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn save_as(
+            &mut self,
+            _: Model<Project>,
+            _: std::path::PathBuf,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.save_as_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn reload(
+            &mut self,
+            _: Model<Project>,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.reload_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn serialized_item_kind() -> Option<&'static str> {
+            Some("TestItem")
+        }
+
+        fn deserialize(
+            _project: Model<Project>,
+            _workspace: WeakView<Workspace>,
+            workspace_id: WorkspaceId,
+            _item_id: ItemId,
+            cx: &mut ViewContext<Pane>,
+        ) -> Task<anyhow::Result<View<Self>>> {
+            let view = cx.build_view(|cx| Self::new_deserialized(workspace_id, cx));
+            Task::Ready(Some(anyhow::Ok(view)))
+        }
+    }
+}

crates/workspace2/src/notifications.rs πŸ”—

@@ -181,6 +181,7 @@ pub mod simple_message_notification {
     };
     use serde::Deserialize;
     use std::{borrow::Cow, sync::Arc};
+    use ui::prelude::*;
     use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
 
     #[derive(Clone, Default, Deserialize, PartialEq)]
@@ -287,12 +288,14 @@ pub mod simple_message_notification {
                         ),
                 )
                 .children(self.click_message.iter().map(|message| {
-                    Button::new(message.clone()).on_click(cx.listener(|this, _, cx| {
-                        if let Some(on_click) = this.on_click.as_ref() {
-                            (on_click)(cx)
-                        };
-                        this.dismiss(cx)
-                    }))
+                    Button::new(message.clone(), message.clone()).on_click(cx.listener(
+                        |this, _, cx| {
+                            if let Some(on_click) = this.on_click.as_ref() {
+                                (on_click)(cx)
+                            };
+                            this.dismiss(cx)
+                        },
+                    ))
                 }))
         }
     }

crates/workspace2/src/pane.rs πŸ”—

@@ -2,19 +2,20 @@ use crate::{
     item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
     toolbar::Toolbar,
     workspace_settings::{AutosaveSetting, WorkspaceSettings},
-    SplitDirection, Workspace,
+    NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter,
-    FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View,
-    ViewContext, VisualContext, WeakView, WindowContext,
+    actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext,
+    AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
+    FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
 use std::{
     any::Any,
     cmp, fmt, mem,
@@ -25,8 +26,8 @@ use std::{
     },
 };
 
-use ui::v_stack;
-use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip};
+use ui::{prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Tooltip};
+use ui::{v_stack, ContextMenu};
 use util::truncate_and_remove_front;
 
 #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
@@ -50,7 +51,7 @@ pub enum SaveIntent {
 
 //todo!("Do we need the default bound on actions? Decide soon")
 // #[register_action]
-#[derive(Clone, Deserialize, PartialEq, Debug)]
+#[derive(Action, Clone, Deserialize, PartialEq, Debug)]
 pub struct ActivateItem(pub usize);
 
 // #[derive(Clone, PartialEq)]
@@ -143,17 +144,24 @@ impl fmt::Debug for Event {
     }
 }
 
+struct FocusedView {
+    view: AnyWeakView,
+    focus_handle: FocusHandle,
+}
+
 pub struct Pane {
     focus_handle: FocusHandle,
     items: Vec<Box<dyn ItemHandle>>,
     activation_history: Vec<EntityId>,
     zoomed: bool,
     active_item_index: usize,
-    //     last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
+    last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
     autoscroll: bool,
     nav_history: NavHistory,
     toolbar: View<Toolbar>,
-    //     tab_bar_context_menu: TabBarContextMenu,
+    tab_bar_focus_handle: FocusHandle,
+    new_item_menu: Option<View<ContextMenu>>,
+    split_item_menu: Option<View<ContextMenu>>,
     //     tab_context_menu: ViewHandle<ContextMenu>,
     workspace: WeakView<Workspace>,
     project: Model<Project>,
@@ -306,7 +314,7 @@ impl Pane {
             activation_history: Vec::new(),
             zoomed: false,
             active_item_index: 0,
-            // last_focused_view_by_item: Default::default(),
+            last_focused_view_by_item: Default::default(),
             autoscroll: false,
             nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
                 mode: NavigationMode::Normal,
@@ -318,6 +326,9 @@ impl Pane {
                 next_timestamp,
             }))),
             toolbar: cx.build_view(|_| Toolbar::new()),
+            tab_bar_focus_handle: cx.focus_handle(),
+            new_item_menu: None,
+            split_item_menu: None,
             // tab_bar_context_menu: TabBarContextMenu {
             //     kind: TabBarContextMenuKind::New,
             //     handle: context_menu,
@@ -392,9 +403,48 @@ impl Pane {
     }
 
     pub fn has_focus(&self, cx: &WindowContext) -> bool {
+        // todo!(); // inline this manually
         self.focus_handle.contains_focused(cx)
     }
 
+    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+        if !self.has_focus(cx) {
+            cx.emit(Event::Focus);
+            cx.notify();
+        }
+
+        self.toolbar.update(cx, |toolbar, cx| {
+            toolbar.focus_changed(true, cx);
+        });
+
+        if let Some(active_item) = self.active_item() {
+            if self.focus_handle.is_focused(cx) {
+                // Pane was focused directly. We need to either focus a view inside the active item,
+                // or focus the active item itself
+                if let Some(weak_last_focused_view) =
+                    self.last_focused_view_by_item.get(&active_item.item_id())
+                {
+                    weak_last_focused_view.focus(cx);
+                    return;
+                }
+
+                active_item.focus_handle(cx).focus(cx);
+            } else if !self.tab_bar_focus_handle.contains_focused(cx) {
+                if let Some(focused) = cx.focused() {
+                    self.last_focused_view_by_item
+                        .insert(active_item.item_id(), focused);
+                }
+            }
+        }
+    }
+
+    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
+        self.toolbar.update(cx, |toolbar, cx| {
+            toolbar.focus_changed(false, cx);
+        });
+        cx.notify();
+    }
+
     pub fn active_item_index(&self) -> usize {
         self.active_item_index
     }
@@ -457,6 +507,28 @@ impl Pane {
         !self.nav_history.0.lock().forward_stack.is_empty()
     }
 
+    fn navigate_backward(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(workspace) = self.workspace.upgrade() {
+            let pane = cx.view().downgrade();
+            cx.window_context().defer(move |cx| {
+                workspace.update(cx, |workspace, cx| {
+                    workspace.go_back(pane, cx).detach_and_log_err(cx)
+                })
+            })
+        }
+    }
+
+    fn navigate_forward(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(workspace) = self.workspace.upgrade() {
+            let pane = cx.view().downgrade();
+            cx.window_context().defer(move |cx| {
+                workspace.update(cx, |workspace, cx| {
+                    workspace.go_forward(pane, cx).detach_and_log_err(cx)
+                })
+            })
+        }
+    }
+
     fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
         self.toolbar.update(cx, |_, cx| cx.notify());
     }
@@ -630,21 +702,16 @@ impl Pane {
             .position(|i| i.item_id() == item.item_id())
     }
 
-    // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-    //     // Potentially warn the user of the new keybinding
-    //     let workspace_handle = self.workspace().clone();
-    //     cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
-    //         .detach();
-
-    //     if self.zoomed {
-    //         cx.emit(Event::ZoomOut);
-    //     } else if !self.items.is_empty() {
-    //         if !self.has_focus {
-    //             cx.focus_self();
-    //         }
-    //         cx.emit(Event::ZoomIn);
-    //     }
-    // }
+    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
+        if self.zoomed {
+            cx.emit(Event::ZoomOut);
+        } else if !self.items.is_empty() {
+            if !self.focus_handle.contains_focused(cx) {
+                cx.focus_self();
+            }
+            cx.emit(Event::ZoomIn);
+        }
+    }
 
     pub fn activate_item(
         &mut self,
@@ -1381,7 +1448,7 @@ impl Pane {
         let close_right = ItemSettings::get_global(cx).close_position.right();
         let is_active = ix == self.active_item_index;
 
-        div()
+        let tab = div()
             .group("")
             .id(ix)
             .cursor_pointer()
@@ -1455,13 +1522,41 @@ impl Pane {
                     .children((!close_right).then(|| close_icon()))
                     .child(label)
                     .children(close_right.then(|| close_icon())),
-            )
+            );
+
+        right_click_menu(ix).trigger(tab).menu(|cx| {
+            ContextMenu::build(cx, |menu, cx| {
+                menu.action(
+                    "Close Active Item",
+                    CloseActiveItem { save_intent: None }.boxed_clone(),
+                    cx,
+                )
+                .action("Close Inactive Items", CloseInactiveItems.boxed_clone(), cx)
+                .action("Close Clean Items", CloseCleanItems.boxed_clone(), cx)
+                .action(
+                    "Close Items To The Left",
+                    CloseItemsToTheLeft.boxed_clone(),
+                    cx,
+                )
+                .action(
+                    "Close Items To The Right",
+                    CloseItemsToTheRight.boxed_clone(),
+                    cx,
+                )
+                .action(
+                    "Close All Items",
+                    CloseAllItems { save_intent: None }.boxed_clone(),
+                    cx,
+                )
+            })
+        })
     }
 
     fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
         div()
             .group("tab_bar")
             .id("tab_bar")
+            .track_focus(&self.tab_bar_focus_handle)
             .w_full()
             .flex()
             .bg(cx.theme().colors().tab_bar_background)
@@ -1483,12 +1578,20 @@ impl Pane {
                             .child(
                                 div().border().border_color(gpui::red()).child(
                                     IconButton::new("navigate_backward", Icon::ArrowLeft)
+                                        .on_click({
+                                            let view = cx.view().clone();
+                                            move |_, cx| view.update(cx, Self::navigate_backward)
+                                        })
                                         .disabled(!self.can_navigate_backward()),
                                 ),
                             )
                             .child(
                                 div().border().border_color(gpui::red()).child(
                                     IconButton::new("navigate_forward", Icon::ArrowRight)
+                                        .on_click({
+                                            let view = cx.view().clone();
+                                            move |_, cx| view.update(cx, Self::navigate_backward)
+                                        })
                                         .disabled(!self.can_navigate_forward()),
                                 ),
                             ),
@@ -1520,20 +1623,87 @@ impl Pane {
                             .gap_px()
                             .child(
                                 div()
+                                    .bg(gpui::blue())
                                     .border()
                                     .border_color(gpui::red())
-                                    .child(IconButton::new("plus", Icon::Plus)),
+                                    .child(IconButton::new("plus", Icon::Plus).on_click(
+                                        cx.listener(|this, _, cx| {
+                                            let menu = ContextMenu::build(cx, |menu, cx| {
+                                                menu.action("New File", NewFile.boxed_clone(), cx)
+                                                    .action(
+                                                        "New Terminal",
+                                                        NewCenterTerminal.boxed_clone(),
+                                                        cx,
+                                                    )
+                                                    .action(
+                                                        "New Search",
+                                                        NewSearch.boxed_clone(),
+                                                        cx,
+                                                    )
+                                            });
+                                            cx.subscribe(
+                                                &menu,
+                                                |this, _, event: &DismissEvent, cx| {
+                                                    this.focus(cx);
+                                                    this.new_item_menu = None;
+                                                },
+                                            )
+                                            .detach();
+                                            this.new_item_menu = Some(menu);
+                                        }),
+                                    ))
+                                    .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
+                                        el.child(Self::render_menu_overlay(new_item_menu))
+                                    }),
                             )
                             .child(
                                 div()
                                     .border()
                                     .border_color(gpui::red())
-                                    .child(IconButton::new("split", Icon::Split)),
+                                    .child(IconButton::new("split", Icon::Split).on_click(
+                                        cx.listener(|this, _, cx| {
+                                            let menu = ContextMenu::build(cx, |menu, cx| {
+                                                menu.action(
+                                                    "Split Right",
+                                                    SplitRight.boxed_clone(),
+                                                    cx,
+                                                )
+                                                .action("Split Left", SplitLeft.boxed_clone(), cx)
+                                                .action("Split Up", SplitUp.boxed_clone(), cx)
+                                                .action("Split Down", SplitDown.boxed_clone(), cx)
+                                            });
+                                            cx.subscribe(
+                                                &menu,
+                                                |this, _, event: &DismissEvent, cx| {
+                                                    this.focus(cx);
+                                                    this.split_item_menu = None;
+                                                },
+                                            )
+                                            .detach();
+                                            this.split_item_menu = Some(menu);
+                                        }),
+                                    ))
+                                    .when_some(
+                                        self.split_item_menu.as_ref(),
+                                        |el, split_item_menu| {
+                                            el.child(Self::render_menu_overlay(split_item_menu))
+                                        },
+                                    ),
                             ),
                     ),
             )
     }
 
+    fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
+        div()
+            .absolute()
+            .z_index(1)
+            .bottom_0()
+            .right_0()
+            .size_0()
+            .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
+    }
+
     //     fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
     //         let theme = theme::current(cx).clone();
 
@@ -1932,40 +2102,78 @@ impl Render for Pane {
     type Element = Focusable<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let this = cx.view().downgrade();
+
         v_stack()
             .key_context("Pane")
             .track_focus(&self.focus_handle)
-            .on_action(cx.listener(|pane: &mut Pane, _: &SplitLeft, cx| {
-                pane.split(SplitDirection::Left, cx)
-            }))
+            .on_focus_in({
+                let this = this.clone();
+                move |event, cx| {
+                    this.update(cx, |this, cx| this.focus_in(cx)).ok();
+                }
+            })
+            .on_focus_out({
+                let this = this.clone();
+                move |event, cx| {
+                    this.update(cx, |this, cx| this.focus_out(cx)).ok();
+                }
+            })
+            .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
+            .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
             .on_action(
-                cx.listener(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)),
+                cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
             )
-            .on_action(cx.listener(|pane: &mut Pane, _: &SplitRight, cx| {
-                pane.split(SplitDirection::Right, cx)
+            .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
+            .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
+            .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
+            .on_action(cx.listener(Pane::toggle_zoom))
+            .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
+                pane.activate_item(action.0, true, true, cx);
+            }))
+            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
+                pane.activate_item(pane.items.len() - 1, true, true, cx);
+            }))
+            .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
+                pane.activate_prev_item(true, cx);
             }))
-            .on_action(cx.listener(|pane: &mut Pane, _: &SplitDown, cx| {
-                pane.split(SplitDirection::Down, cx)
+            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
+                pane.activate_next_item(true, 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));
+                }),
+            )
+            .on_action(
+                cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
+                    pane.close_inactive_items(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                }),
+            )
+            .on_action(
+                cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
+                    pane.close_clean_items(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                }),
+            )
+            .on_action(
+                cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
+                    pane.close_items_to_the_left(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                }),
+            )
+            .on_action(
+                cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
+                    pane.close_items_to_the_right(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                }),
+            )
+            .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
+                pane.close_all_items(action, cx)
+                    .map(|task| task.detach_and_log_err(cx));
             }))
-            //     cx.add_action(Pane::toggle_zoom);
-            //     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
-            //         pane.activate_item(action.0, true, true, cx);
-            //     });
-            //     cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
-            //         pane.activate_item(pane.items.len() - 1, true, true, cx);
-            //     });
-            //     cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
-            //         pane.activate_prev_item(true, cx);
-            //     });
-            //     cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
-            //         pane.activate_next_item(true, cx);
-            //     });
-            //     cx.add_async_action(Pane::close_active_item);
-            //     cx.add_async_action(Pane::close_inactive_items);
-            //     cx.add_async_action(Pane::close_clean_items);
-            //     cx.add_async_action(Pane::close_items_to_the_left);
-            //     cx.add_async_action(Pane::close_items_to_the_right);
-            //     cx.add_async_action(Pane::close_all_items);
             .size_full()
             .on_action(
                 cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {

crates/workspace2/src/pane_group.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, bail, Result};
 use collections::HashMap;
-use db2::sqlez::{
+use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
@@ -9,7 +9,7 @@ use gpui::{
     point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
 };
 use parking_lot::Mutex;
-use project2::Project;
+use project::Project;
 use serde::Deserialize;
 use std::sync::Arc;
 use ui::prelude::*;

crates/workspace2/src/persistence.rs πŸ”—

@@ -5,7 +5,7 @@ pub mod model;
 use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
-use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
 use gpui::WindowBounds;
 
 use util::{unzip_option, ResultExt};
@@ -552,7 +552,7 @@ impl WorkspaceDb {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use db2::open_test_db;
+    use db::open_test_db;
     use gpui;
 
     #[gpui::test]

crates/workspace2/src/persistence/model.rs πŸ”—

@@ -3,12 +3,12 @@ use crate::{
 };
 use anyhow::{Context, Result};
 use async_recursion::async_recursion;
-use db2::sqlez::{
+use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
 use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
-use project2::Project;
+use project::Project;
 use std::{
     path::{Path, PathBuf},
     sync::Arc,

crates/workspace2/src/searchable.rs πŸ”—

@@ -4,7 +4,7 @@ use gpui::{
     AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
     WindowContext,
 };
-use project2::search::SearchQuery;
+use project::search::SearchQuery;
 
 use crate::{
     item::{Item, WeakItemHandle},

crates/workspace2/src/status_bar.rs πŸ”—

@@ -47,39 +47,18 @@ impl Render for StatusBar {
             .w_full()
             .h_8()
             .bg(cx.theme().colors().status_bar_background)
-            // Nate: I know this isn't how we render status bar tools
-            // We can move these to the correct place once we port their tools
-            .child(
-                h_stack().gap_1().child(self.render_left_tools(cx)).child(
-                    h_stack().gap_4().child(
-                        // TODO: Language Server status
-                        div()
-                            .border()
-                            .border_color(gpui::red())
-                            .child("Checking..."),
-                    ),
-                ),
-            )
+            .child(h_stack().gap_1().child(self.render_left_tools(cx)))
             .child(
                 h_stack()
                     .gap_4()
                     .child(
-                        h_stack()
-                            .gap_1()
-                            .child(
-                                // TODO: Line / column numbers
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(Button::new("15:22")),
-                            )
-                            .child(
-                                // TODO: Language picker
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(Button::new("Rust")),
-                            ),
+                        h_stack().gap_1().child(
+                            // TODO: Language picker
+                            div()
+                                .border()
+                                .border_color(gpui::red())
+                                .child(Button::new("status_buffer_language", "Rust")),
+                        ),
                     )
                     .child(
                         h_stack()
@@ -145,7 +124,7 @@ impl StatusBar {
         h_stack()
             .items_center()
             .gap_2()
-            .children(self.right_items.iter().map(|item| item.to_any()))
+            .children(self.right_items.iter().rev().map(|item| item.to_any()))
     }
 }
 

crates/workspace2/src/toolbar.rs πŸ”—

@@ -4,7 +4,7 @@ use gpui::{
     ViewContext, WindowContext,
 };
 use ui::prelude::*;
-use ui::{h_stack, v_stack, Button, Color, Icon, IconButton, Label};
+use ui::{h_stack, v_stack, Icon, IconButton};
 
 pub enum ToolbarItemEvent {
     ChangeLocation(ToolbarItemLocation),
@@ -80,7 +80,6 @@ impl Render for Toolbar {
     type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        //dbg!(&self.items.len());
         v_stack()
             .border_b()
             .border_color(cx.theme().colors().border_variant)
@@ -88,16 +87,8 @@ impl Render for Toolbar {
             .child(
                 h_stack()
                     .justify_between()
-                    .child(
-                        // Toolbar left side
-                        h_stack()
-                            .border()
-                            .border_color(gpui::red())
-                            .p_1()
-                            .child(Button::new("crates"))
-                            .child(Label::new("/").color(Color::Muted))
-                            .child(Button::new("workspace2")),
-                    )
+                    // Toolbar left side
+                    .children(self.items.iter().map(|(child, _)| child.to_any()))
                     // Toolbar right side
                     .child(
                         h_stack()
@@ -116,7 +107,6 @@ impl Render for Toolbar {
                             ),
                     ),
             )
-            .children(self.items.iter().map(|(child, _)| child.to_any()))
     }
 }
 

crates/workspace2/src/workspace2.rs πŸ”—

@@ -16,7 +16,7 @@ mod workspace_settings;
 
 use anyhow::{anyhow, Context as _, Result};
 use async_trait::async_trait;
-use client2::{
+use client::{
     proto::{self, PeerId},
     Client, TypedEnvelope, User, UserStore,
 };
@@ -37,7 +37,7 @@ use gpui::{
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
-use language2::{LanguageRegistry, Rope};
+use language::{LanguageRegistry, Rope};
 use lazy_static::lazy_static;
 pub use modal_layer::*;
 use node_runtime::NodeRuntime;
@@ -49,9 +49,9 @@ pub use persistence::{
     WorkspaceDb, DB,
 };
 use postage::stream::Stream;
-use project2::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
+use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
 use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
 use std::{
@@ -62,7 +62,7 @@ use std::{
     sync::{atomic::AtomicUsize, Arc},
     time::Duration,
 };
-use theme2::{ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, ThemeSettings};
 pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 pub use ui;
 use util::ResultExt;
@@ -247,7 +247,7 @@ type FollowableItemBuilder = fn(
     View<Workspace>,
     ViewId,
     &mut Option<proto::view::Variant>,
-    &mut AppContext,
+    &mut WindowContext,
 ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 type FollowableItemBuilders = HashMap<
     TypeId,
@@ -301,7 +301,7 @@ pub struct AppState {
     pub client: Arc<Client>,
     pub user_store: Model<UserStore>,
     pub workspace_store: Model<WorkspaceStore>,
-    pub fs: Arc<dyn fs2::Fs>,
+    pub fs: Arc<dyn fs::Fs>,
     pub call_factory: CallFactory,
     pub build_window_options:
         fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
@@ -312,7 +312,7 @@ pub struct WorkspaceStore {
     workspaces: HashSet<WindowHandle<Workspace>>,
     followers: Vec<Follower>,
     client: Arc<Client>,
-    _subscriptions: Vec<client2::Subscription>,
+    _subscriptions: Vec<client::Subscription>,
 }
 
 #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -326,7 +326,12 @@ pub struct TestCallHandler;
 
 #[cfg(any(test, feature = "test-support"))]
 impl CallHandler for TestCallHandler {
-    fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)> {
+    fn peer_state(
+        &mut self,
+        id: PeerId,
+        project: &Model<Project>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<(bool, bool)> {
         None
     }
 
@@ -383,22 +388,22 @@ impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
         use node_runtime::FakeNodeRuntime;
-        use settings2::SettingsStore;
+        use settings::SettingsStore;
 
         if !cx.has_global::<SettingsStore>() {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
         }
 
-        let fs = fs2::FakeFs::new(cx.background_executor().clone());
+        let fs = fs::FakeFs::new(cx.background_executor().clone());
         let languages = Arc::new(LanguageRegistry::test());
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone(), cx);
         let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
         let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
-        theme2::init(theme2::LoadThemes::JustBase, cx);
-        client2::init(&client, cx);
+        theme::init(theme::LoadThemes::JustBase, cx);
+        client::init(&client, cx);
         crate::init_settings(cx);
 
         Arc::new(Self {
@@ -409,7 +414,7 @@ impl AppState {
             workspace_store,
             node_runtime: FakeNodeRuntime::new(),
             build_window_options: |_, _, _| Default::default(),
-            call_factory: |_, _| Box::new(TestCallHandler),
+            call_factory: |_| Box::new(TestCallHandler),
         })
     }
 }
@@ -468,7 +473,12 @@ pub enum Event {
 
 #[async_trait(?Send)]
 pub trait CallHandler {
-    fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
+    fn peer_state(
+        &mut self,
+        id: PeerId,
+        project: &Model<Project>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<(bool, bool)>;
     fn shared_screen_for_peer(
         &self,
         peer_id: PeerId,
@@ -546,7 +556,7 @@ struct FollowerState {
 
 enum WorkspaceBounds {}
 
-type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
+type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
 impl Workspace {
     pub fn new(
         workspace_id: WorkspaceId,
@@ -557,29 +567,29 @@ impl Workspace {
         cx.observe(&project, |_, _, cx| cx.notify()).detach();
         cx.subscribe(&project, move |this, _, event, cx| {
             match event {
-                project2::Event::RemoteIdChanged(_) => {
+                project::Event::RemoteIdChanged(_) => {
                     this.update_window_title(cx);
                 }
 
-                project2::Event::CollaboratorLeft(peer_id) => {
+                project::Event::CollaboratorLeft(peer_id) => {
                     this.collaborator_left(*peer_id, cx);
                 }
 
-                project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
                     this.update_window_title(cx);
                     this.serialize_workspace(cx);
                 }
 
-                project2::Event::DisconnectedFromHost => {
+                project::Event::DisconnectedFromHost => {
                     this.update_window_edited(cx);
                     cx.blur();
                 }
 
-                project2::Event::Closed => {
+                project::Event::Closed => {
                     cx.remove_window();
                 }
 
-                project2::Event::DeletedEntry(entry_id) => {
+                project::Event::DeletedEntry(entry_id) => {
                     for pane in this.panes.iter() {
                         pane.update(cx, |pane, cx| {
                             pane.handle_deleted_project_item(*entry_id, cx)
@@ -587,7 +597,7 @@ impl Workspace {
                     }
                 }
 
-                project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
                     cx.build_view(|_| MessageNotification::new(message.clone()))
                 }),
 
@@ -760,7 +770,7 @@ impl Workspace {
             last_leaders_by_pane: Default::default(),
             window_edited: false,
 
-            call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
+            call_handler: (app_state.call_factory)(cx),
             database_id: workspace_id,
             app_state,
             _observe_current_user,
@@ -1440,7 +1450,7 @@ impl Workspace {
                             .map(|entry| entry.id);
                             if let Some(entry_id) = entry_id {
                                 workspace.project.update(cx, |_, cx| {
-                                    cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id)));
+                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
                                 })
                             }
                         })
@@ -1802,8 +1812,7 @@ impl Workspace {
         });
         cx.subscribe(&pane, Self::handle_pane_event).detach();
         self.panes.push(pane.clone());
-        // todo!()
-        // cx.focus(&pane);
+        cx.focus_view(&pane);
         cx.emit(Event::PaneAdded(pane.clone()));
         pane
     }
@@ -1978,7 +1987,7 @@ impl Workspace {
     where
         T: ProjectItem,
     {
-        use project2::Item as _;
+        use project::Item as _;
 
         let entry_id = project_item.read(cx).entry_id(cx);
         if let Some(item) = entry_id
@@ -2003,7 +2012,7 @@ impl Workspace {
     where
         T: ProjectItem,
     {
-        use project2::Item as _;
+        use project::Item as _;
 
         let entry_id = project_item.read(cx).entry_id(cx);
         if let Some(item) = entry_id
@@ -2582,8 +2591,7 @@ impl Workspace {
             title.push_str(" β†—");
         }
 
-        // todo!()
-        // cx.set_window_title(&title);
+        cx.set_window_title(&title);
     }
 
     fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
@@ -2884,7 +2892,7 @@ impl Workspace {
         cx.notify();
 
         let (leader_in_this_project, leader_in_this_app) =
-            self.call_handler.peer_state(leader_id, cx)?;
+            self.call_handler.peer_state(leader_id, &self.project, cx)?;
         let mut items_to_activate = Vec::new();
         for (pane, state) in &self.follower_states {
             if state.leader_id != leader_id {
@@ -3385,7 +3393,7 @@ impl Workspace {
             fs: project.read(cx).fs().clone(),
             build_window_options: |_, _, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
-            call_factory: |_, _| Box::new(TestCallHandler),
+            call_factory: |_| Box::new(TestCallHandler),
         });
         let workspace = Self::new(0, project, app_state, cx);
         workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
@@ -3577,93 +3585,12 @@ fn open_items(
     })
 }
 
-// todo!()
-// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
-//     const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
-//     const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
-//     const MESSAGE_ID: usize = 2;
-
-//     if workspace
-//         .read_with(cx, |workspace, cx| {
-//             workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-//         })
-//         .unwrap_or(false)
-//     {
-//         return;
-//     }
-
-//     if db::kvp::KEY_VALUE_STORE
-//         .read_kvp(NEW_DOCK_HINT_KEY)
-//         .ok()
-//         .flatten()
-//         .is_some()
-//     {
-//         if !workspace
-//             .read_with(cx, |workspace, cx| {
-//                 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
-//             })
-//             .unwrap_or(false)
-//         {
-//             cx.update(|cx| {
-//                 cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
-//                     let entry = tracker
-//                         .entry(TypeId::of::<MessageNotification>())
-//                         .or_default();
-//                     if !entry.contains(&MESSAGE_ID) {
-//                         entry.push(MESSAGE_ID);
-//                     }
-//                 });
-//             });
-//         }
-
-//         return;
-//     }
-
-//     cx.spawn(|_| async move {
-//         db::kvp::KEY_VALUE_STORE
-//             .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
-//             .await
-//             .ok();
-//     })
-//     .detach();
-
-//     workspace
-//         .update(cx, |workspace, cx| {
-//             workspace.show_notification_once(2, cx, |cx| {
-//                 cx.build_view(|_| {
-//                     MessageNotification::new_element(|text, _| {
-//                         Text::new(
-//                             "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
-//                             text,
-//                         )
-//                         .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
-//                             let code_span_background_color = settings::get::<ThemeSettings>(cx)
-//                                 .theme
-//                                 .editor
-//                                 .document_highlight_read_background;
-
-//                             cx.scene().push_quad(gpui::Quad {
-//                                 bounds,
-//                                 background: Some(code_span_background_color),
-//                                 border: Default::default(),
-//                                 corner_radii: (2.0).into(),
-//                             })
-//                         })
-//                         .into_any()
-//                     })
-//                     .with_click_message("Read more about the new panel system")
-//                     .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
-//                 })
-//             })
-//         })
-//         .ok();
-
 fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
     const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
 
     workspace
         .update(cx, |workspace, cx| {
-            if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
+            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
                 workspace.show_notification_once(0, cx, |cx| {
                     cx.build_view(|_| {
                         MessageNotification::new("Failed to load the database file.")
@@ -3711,6 +3638,8 @@ impl Render for Workspace {
             .items_start()
             .text_color(cx.theme().colors().text)
             .bg(cx.theme().colors().background)
+            .border()
+            .border_color(cx.theme().colors().border)
             .children(self.titlebar_item.clone())
             .child(
                 div()
@@ -4481,50 +4410,54 @@ pub fn create_and_open_local_file(
 //     })
 // }
 
-// pub fn restart(_: &Restart, cx: &mut AppContext) {
-//     let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
-//     cx.spawn(|mut cx| async move {
-//         let mut workspace_windows = cx
-//             .windows()
-//             .into_iter()
-//             .filter_map(|window| window.downcast::<Workspace>())
-//             .collect::<Vec<_>>();
-
-//         // If multiple windows have unsaved changes, and need a save prompt,
-//         // prompt in the active window before switching to a different window.
-//         workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
-
-//         if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
-//             let answer = window.prompt(
-//                 PromptLevel::Info,
-//                 "Are you sure you want to restart?",
-//                 &["Restart", "Cancel"],
-//                 &mut cx,
-//             );
+pub fn restart(_: &Restart, cx: &mut AppContext) {
+    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
+    let mut workspace_windows = cx
+        .windows()
+        .into_iter()
+        .filter_map(|window| window.downcast::<Workspace>())
+        .collect::<Vec<_>>();
+
+    // If multiple windows have unsaved changes, and need a save prompt,
+    // prompt in the active window before switching to a different window.
+    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
+
+    let mut prompt = None;
+    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
+        prompt = window
+            .update(cx, |_, cx| {
+                cx.prompt(
+                    PromptLevel::Info,
+                    "Are you sure you want to restart?",
+                    &["Restart", "Cancel"],
+                )
+            })
+            .ok();
+    }
 
-//             if let Some(mut answer) = answer {
-//                 let answer = answer.next().await;
-//                 if answer != Some(0) {
-//                     return Ok(());
-//                 }
-//             }
-//         }
+    cx.spawn(|mut cx| async move {
+        if let Some(mut prompt) = prompt {
+            let answer = prompt.await?;
+            if answer != 0 {
+                return Ok(());
+            }
+        }
 
-//         // If the user cancels any save prompt, then keep the app open.
-//         for window in workspace_windows {
-//             if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
-//                 workspace.prepare_to_close(true, cx)
-//             }) {
-//                 if !should_close.await? {
-//                     return Ok(());
-//                 }
-//             }
-//         }
-//         cx.platform().restart();
-//         anyhow::Ok(())
-//     })
-//     .detach_and_log_err(cx);
-// }
+        // If the user cancels any save prompt, then keep the app open.
+        for window in workspace_windows {
+            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
+                workspace.prepare_to_close(true, cx)
+            }) {
+                if !should_close.await? {
+                    return Ok(());
+                }
+            }
+        }
+
+        cx.update(|cx| cx.restart())
+    })
+    .detach_and_log_err(cx);
+}
 
 fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
     let mut parts = value.split(',');
@@ -4540,960 +4473,950 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
     Some(size((width as f64).into(), (height as f64).into()))
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-//     use crate::{
-//         dock::test::TestPanel,
-//         item::test::{TestItem, TestItemEvent, TestProjectItem},
-//     };
-//     use fs::FakeFs;
-//     use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
-//     use project::{Project, ProjectEntryId};
-//     use serde_json::json;
-//     use settings::SettingsStore;
-//     use std::{cell::RefCell, rc::Rc};
-
-//     #[gpui::test]
-//     async fn test_tab_disambiguation(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-
-//         // Adding an item with no ambiguity renders the tab without detail.
-//         let item1 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item1.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
-
-//         // Adding an item that creates ambiguity increases the level of detail on
-//         // both tabs.
-//         let item2 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item2.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-
-//         // Adding an item that creates ambiguity increases the level of detail only
-//         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
-//         // we stop at the highest detail available.
-//         let item3 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item3.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-//         item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-//     }
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::item::{
+        test::{TestItem, TestProjectItem},
+        ItemEvent,
+    };
+    use fs::FakeFs;
+    use gpui::TestAppContext;
+    use project::{Project, ProjectEntryId};
+    use serde_json::json;
+    use settings::SettingsStore;
+    use std::{cell::RefCell, rc::Rc};
+
+    #[gpui::test]
+    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+        // Adding an item with no ambiguity renders the tab without detail.
+        let item1 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item1.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
+
+        // Adding an item that creates ambiguity increases the level of detail on
+        // both tabs.
+        let item2 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item2.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+
+        // Adding an item that creates ambiguity increases the level of detail only
+        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
+        // we stop at the highest detail available.
+        let item3 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item3.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+    }
 
-//     #[gpui::test]
-//     async fn test_tracking_active_path(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         fs.insert_tree(
-//             "/root1",
-//             json!({
-//                 "one.txt": "",
-//                 "two.txt": "",
-//             }),
-//         )
-//         .await;
-//         fs.insert_tree(
-//             "/root2",
-//             json!({
-//                 "three.txt": "",
-//             }),
-//         )
-//         .await;
+    #[gpui::test]
+    async fn test_tracking_active_path(cx: &mut TestAppContext) {
+        init_test(cx);
 
-//         let project = Project::test(fs, ["root1".as_ref()], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         let worktree_id = project.read_with(cx, |project, cx| {
-//             project.worktrees().next().unwrap().read(cx).id()
-//         });
+        let fs = FakeFs::new(cx.executor());
+        fs.insert_tree(
+            "/root1",
+            json!({
+                "one.txt": "",
+                "two.txt": "",
+            }),
+        )
+        .await;
+        fs.insert_tree(
+            "/root2",
+            json!({
+                "three.txt": "",
+            }),
+        )
+        .await;
 
-//         let item1 = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-//         });
-//         let item2 = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
-//         });
+        let project = Project::test(fs, ["root1".as_ref()], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+        let worktree_id = project.read_with(cx, |project, cx| {
+            project.worktrees().next().unwrap().read(cx).id()
+        });
 
-//         // Add an item to an empty pane
-//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
-
-//         // Add a second item to a non-empty pane
-//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
-//         assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "two.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
+        let item1 = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
+        });
+        let item2 = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
+        });
 
-//         // Close the active item
-//         pane.update(cx, |pane, cx| {
-//             pane.close_active_item(&Default::default(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
+        // Add an item to an empty pane
+        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
+        project.read_with(cx, |project, cx| {
+            assert_eq!(
+                project.active_entry(),
+                project
+                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+                    .map(|e| e.id)
+            );
+        });
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
+
+        // Add a second item to a non-empty pane
+        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
+        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
+        project.read_with(cx, |project, cx| {
+            assert_eq!(
+                project.active_entry(),
+                project
+                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
+                    .map(|e| e.id)
+            );
+        });
 
-//         // Add a project folder
-//         project
-//             .update(cx, |project, cx| {
-//                 project.find_or_create_local_worktree("/root2", true, cx)
-//             })
-//             .await
-//             .unwrap();
-//         assert_eq!(
-//             window.current_title(cx).as_deref(),
-//             Some("one.txt β€” root1, root2")
-//         );
-
-//         // Remove a project folder
-//         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
-//     }
+        // Close the active item
+        pane.update(cx, |pane, cx| {
+            pane.close_active_item(&Default::default(), cx).unwrap()
+        })
+        .await
+        .unwrap();
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
+        project.read_with(cx, |project, cx| {
+            assert_eq!(
+                project.active_entry(),
+                project
+                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+                    .map(|e| e.id)
+            );
+        });
 
-//     #[gpui::test]
-//     async fn test_close_window(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         fs.insert_tree("/root", json!({ "one": "" })).await;
-
-//         let project = Project::test(fs, ["root".as_ref()], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-
-//         // When there are no dirty items, there's nothing to do.
-//         let item1 = window.build_view(cx, |_| TestItem::new());
-//         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
-//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-//         assert!(task.await.unwrap());
-
-//         // When there are dirty untitled items, prompt to save each one. If the user
-//         // cancels any prompt, then abort.
-//         let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
-//         let item3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         workspace.update(cx, |w, cx| {
-//             w.add_item(Box::new(item2.clone()), cx);
-//             w.add_item(Box::new(item3.clone()), cx);
-//         });
-//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-//         cx.foreground().run_until_parked();
-//         window.simulate_prompt_answer(2, cx); // cancel save all
-//         cx.foreground().run_until_parked();
-//         window.simulate_prompt_answer(2, cx); // cancel save all
-//         cx.foreground().run_until_parked();
-//         assert!(!window.has_pending_prompt(cx));
-//         assert!(!task.await.unwrap());
-//     }
+        // Add a project folder
+        project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/root2", true, cx)
+            })
+            .await
+            .unwrap();
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
 
-//     #[gpui::test]
-//     async fn test_close_pane_items(cx: &mut TestAppContext) {
-//         init_test(cx);
+        // Remove a project folder
+        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
+    }
 
-//         let fs = FakeFs::new(cx.background());
+    #[gpui::test]
+    async fn test_close_window(cx: &mut TestAppContext) {
+        init_test(cx);
 
-//         let project = Project::test(fs, None, cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        let fs = FakeFs::new(cx.executor());
+        fs.insert_tree("/root", json!({ "one": "" })).await;
 
-//         let item1 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let item2 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_conflict(true)
-//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-//         });
-//         let item3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_conflict(true)
-//                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
-//         });
-//         let item4 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
-//         });
-//         let pane = workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item1.clone()), cx);
-//             workspace.add_item(Box::new(item2.clone()), cx);
-//             workspace.add_item(Box::new(item3.clone()), cx);
-//             workspace.add_item(Box::new(item4.clone()), cx);
-//             workspace.active_pane().clone()
-//         });
+        let project = Project::test(fs, ["root".as_ref()], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
 
-//         let close_items = pane.update(cx, |pane, cx| {
-//             pane.activate_item(1, true, true, cx);
-//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
-//             let item1_id = item1.id();
-//             let item3_id = item3.id();
-//             let item4_id = item4.id();
-//             pane.close_items(cx, SaveIntent::Close, move |id| {
-//                 [item1_id, item3_id, item4_id].contains(&id)
-//             })
-//         });
-//         cx.foreground().run_until_parked();
-
-//         assert!(window.has_pending_prompt(cx));
-//         // Ignore "Save all" prompt
-//         window.simulate_prompt_answer(2, cx);
-//         cx.foreground().run_until_parked();
-//         // There's a prompt to save item 1.
-//         pane.read_with(cx, |pane, _| {
-//             assert_eq!(pane.items_len(), 4);
-//             assert_eq!(pane.active_item().unwrap().id(), item1.id());
-//         });
-//         // Confirm saving item 1.
-//         window.simulate_prompt_answer(0, cx);
-//         cx.foreground().run_until_parked();
-
-//         // Item 1 is saved. There's a prompt to save item 3.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item1.read(cx).save_count, 1);
-//             assert_eq!(item1.read(cx).save_as_count, 0);
-//             assert_eq!(item1.read(cx).reload_count, 0);
-//             assert_eq!(pane.items_len(), 3);
-//             assert_eq!(pane.active_item().unwrap().id(), item3.id());
-//         });
-//         assert!(window.has_pending_prompt(cx));
-
-//         // Cancel saving item 3.
-//         window.simulate_prompt_answer(1, cx);
-//         cx.foreground().run_until_parked();
-
-//         // Item 3 is reloaded. There's a prompt to save item 4.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item3.read(cx).save_count, 0);
-//             assert_eq!(item3.read(cx).save_as_count, 0);
-//             assert_eq!(item3.read(cx).reload_count, 1);
-//             assert_eq!(pane.items_len(), 2);
-//             assert_eq!(pane.active_item().unwrap().id(), item4.id());
-//         });
-//         assert!(window.has_pending_prompt(cx));
-
-//         // Confirm saving item 4.
-//         window.simulate_prompt_answer(0, cx);
-//         cx.foreground().run_until_parked();
-
-//         // There's a prompt for a path for item 4.
-//         cx.simulate_new_path_selection(|_| Some(Default::default()));
-//         close_items.await.unwrap();
-
-//         // The requested items are closed.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item4.read(cx).save_count, 0);
-//             assert_eq!(item4.read(cx).save_as_count, 1);
-//             assert_eq!(item4.read(cx).reload_count, 0);
-//             assert_eq!(pane.items_len(), 1);
-//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
-//         });
-//     }
+        // When there are no dirty items, there's nothing to do.
+        let item1 = cx.build_view(|cx| TestItem::new(cx));
+        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
+        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+        assert!(task.await.unwrap());
 
-//     #[gpui::test]
-//     async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-
-//         // Create several workspace items with single project entries, and two
-//         // workspace items with multiple project entries.
-//         let single_entry_items = (0..=4)
-//             .map(|project_entry_id| {
-//                 window.build_view(cx, |cx| {
-//                     TestItem::new()
-//                         .with_dirty(true)
-//                         .with_project_items(&[TestProjectItem::new(
-//                             project_entry_id,
-//                             &format!("{project_entry_id}.txt"),
-//                             cx,
-//                         )])
-//                 })
-//             })
-//             .collect::<Vec<_>>();
-//         let item_2_3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_singleton(false)
-//                 .with_project_items(&[
-//                     single_entry_items[2].read(cx).project_items[0].clone(),
-//                     single_entry_items[3].read(cx).project_items[0].clone(),
-//                 ])
-//         });
-//         let item_3_4 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_singleton(false)
-//                 .with_project_items(&[
-//                     single_entry_items[3].read(cx).project_items[0].clone(),
-//                     single_entry_items[4].read(cx).project_items[0].clone(),
-//                 ])
-//         });
+        // When there are dirty untitled items, prompt to save each one. If the user
+        // cancels any prompt, then abort.
+        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
+        let item3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        workspace.update(cx, |w, cx| {
+            w.add_item(Box::new(item2.clone()), cx);
+            w.add_item(Box::new(item3.clone()), cx);
+        });
+        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+        cx.executor().run_until_parked();
+        cx.simulate_prompt_answer(2); // cancel save all
+        cx.executor().run_until_parked();
+        cx.simulate_prompt_answer(2); // cancel save all
+        cx.executor().run_until_parked();
+        assert!(!cx.has_pending_prompt());
+        assert!(!task.await.unwrap());
+    }
 
-//         // Create two panes that contain the following project entries:
-//         //   left pane:
-//         //     multi-entry items:   (2, 3)
-//         //     single-entry items:  0, 1, 2, 3, 4
-//         //   right pane:
-//         //     single-entry items:  1
-//         //     multi-entry items:   (3, 4)
-//         let left_pane = workspace.update(cx, |workspace, cx| {
-//             let left_pane = workspace.active_pane().clone();
-//             workspace.add_item(Box::new(item_2_3.clone()), cx);
-//             for item in single_entry_items {
-//                 workspace.add_item(Box::new(item), cx);
-//             }
-//             left_pane.update(cx, |pane, cx| {
-//                 pane.activate_item(2, true, true, cx);
-//             });
+    #[gpui::test]
+    async fn test_close_pane_items(cx: &mut TestAppContext) {
+        init_test(cx);
 
-//             workspace
-//                 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
-//                 .unwrap();
+        let fs = FakeFs::new(cx.executor());
 
-//             left_pane
-//         });
+        let project = Project::test(fs, None, cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
 
-//         //Need to cause an effect flush in order to respect new focus
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item_3_4.clone()), cx);
-//             cx.focus(&left_pane);
-//         });
+        let item1 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let item2 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_conflict(true)
+                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
+        });
+        let item3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_conflict(true)
+                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
+        });
+        let item4 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_project_items(&[TestProjectItem::new_untitled(cx)])
+        });
+        let pane = workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item1.clone()), cx);
+            workspace.add_item(Box::new(item2.clone()), cx);
+            workspace.add_item(Box::new(item3.clone()), cx);
+            workspace.add_item(Box::new(item4.clone()), cx);
+            workspace.active_pane().clone()
+        });
 
-//         // When closing all of the items in the left pane, we should be prompted twice:
-//         // once for project entry 0, and once for project entry 2. After those two
-//         // prompts, the task should complete.
+        let close_items = pane.update(cx, |pane, cx| {
+            pane.activate_item(1, true, true, cx);
+            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+            let item1_id = item1.item_id();
+            let item3_id = item3.item_id();
+            let item4_id = item4.item_id();
+            pane.close_items(cx, SaveIntent::Close, move |id| {
+                [item1_id, item3_id, item4_id].contains(&id)
+            })
+        });
+        cx.executor().run_until_parked();
+
+        assert!(cx.has_pending_prompt());
+        // Ignore "Save all" prompt
+        cx.simulate_prompt_answer(2);
+        cx.executor().run_until_parked();
+        // There's a prompt to save item 1.
+        pane.update(cx, |pane, _| {
+            assert_eq!(pane.items_len(), 4);
+            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
+        });
+        // Confirm saving item 1.
+        cx.simulate_prompt_answer(0);
+        cx.executor().run_until_parked();
+
+        // Item 1 is saved. There's a prompt to save item 3.
+        pane.update(cx, |pane, cx| {
+            assert_eq!(item1.read(cx).save_count, 1);
+            assert_eq!(item1.read(cx).save_as_count, 0);
+            assert_eq!(item1.read(cx).reload_count, 0);
+            assert_eq!(pane.items_len(), 3);
+            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
+        });
+        assert!(cx.has_pending_prompt());
+
+        // Cancel saving item 3.
+        cx.simulate_prompt_answer(1);
+        cx.executor().run_until_parked();
+
+        // Item 3 is reloaded. There's a prompt to save item 4.
+        pane.update(cx, |pane, cx| {
+            assert_eq!(item3.read(cx).save_count, 0);
+            assert_eq!(item3.read(cx).save_as_count, 0);
+            assert_eq!(item3.read(cx).reload_count, 1);
+            assert_eq!(pane.items_len(), 2);
+            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
+        });
+        assert!(cx.has_pending_prompt());
+
+        // Confirm saving item 4.
+        cx.simulate_prompt_answer(0);
+        cx.executor().run_until_parked();
+
+        // There's a prompt for a path for item 4.
+        cx.simulate_new_path_selection(|_| Some(Default::default()));
+        close_items.await.unwrap();
+
+        // The requested items are closed.
+        pane.update(cx, |pane, cx| {
+            assert_eq!(item4.read(cx).save_count, 0);
+            assert_eq!(item4.read(cx).save_as_count, 1);
+            assert_eq!(item4.read(cx).reload_count, 0);
+            assert_eq!(pane.items_len(), 1);
+            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+        });
+    }
 
-//         let close = left_pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |_| true)
-//         });
-//         cx.foreground().run_until_parked();
-//         // Discard "Save all" prompt
-//         window.simulate_prompt_answer(2, cx);
-
-//         cx.foreground().run_until_parked();
-//         left_pane.read_with(cx, |pane, cx| {
-//             assert_eq!(
-//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-//                 &[ProjectEntryId::from_proto(0)]
-//             );
-//         });
-//         window.simulate_prompt_answer(0, cx);
+    #[gpui::test]
+    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+        // Create several workspace items with single project entries, and two
+        // workspace items with multiple project entries.
+        let single_entry_items = (0..=4)
+            .map(|project_entry_id| {
+                cx.build_view(|cx| {
+                    TestItem::new(cx)
+                        .with_dirty(true)
+                        .with_project_items(&[TestProjectItem::new(
+                            project_entry_id,
+                            &format!("{project_entry_id}.txt"),
+                            cx,
+                        )])
+                })
+            })
+            .collect::<Vec<_>>();
+        let item_2_3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_singleton(false)
+                .with_project_items(&[
+                    single_entry_items[2].read(cx).project_items[0].clone(),
+                    single_entry_items[3].read(cx).project_items[0].clone(),
+                ])
+        });
+        let item_3_4 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_singleton(false)
+                .with_project_items(&[
+                    single_entry_items[3].read(cx).project_items[0].clone(),
+                    single_entry_items[4].read(cx).project_items[0].clone(),
+                ])
+        });
 
-//         cx.foreground().run_until_parked();
-//         left_pane.read_with(cx, |pane, cx| {
-//             assert_eq!(
-//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-//                 &[ProjectEntryId::from_proto(2)]
-//             );
-//         });
-//         window.simulate_prompt_answer(0, cx);
+        // Create two panes that contain the following project entries:
+        //   left pane:
+        //     multi-entry items:   (2, 3)
+        //     single-entry items:  0, 1, 2, 3, 4
+        //   right pane:
+        //     single-entry items:  1
+        //     multi-entry items:   (3, 4)
+        let left_pane = workspace.update(cx, |workspace, cx| {
+            let left_pane = workspace.active_pane().clone();
+            workspace.add_item(Box::new(item_2_3.clone()), cx);
+            for item in single_entry_items {
+                workspace.add_item(Box::new(item), cx);
+            }
+            left_pane.update(cx, |pane, cx| {
+                pane.activate_item(2, true, true, cx);
+            });
 
-//         cx.foreground().run_until_parked();
-//         close.await.unwrap();
-//         left_pane.read_with(cx, |pane, _| {
-//             assert_eq!(pane.items_len(), 0);
-//         });
-//     }
+            let right_pane = workspace
+                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
+                .unwrap();
 
-//     #[gpui::test]
-//     async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
+            right_pane.update(cx, |pane, cx| {
+                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
+            });
 
-//         let fs = FakeFs::new(cx.background());
+            left_pane
+        });
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+        cx.focus_view(&left_pane);
 
-//         let item = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let item_id = item.id();
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//         });
+        // When closing all of the items in the left pane, we should be prompted twice:
+        // once for project entry 0, and once for project entry 2. Project entries 1,
+        // 3, and 4 are all still open in the other paten. After those two
+        // prompts, the task should complete.
 
-//         // Autosave on window change.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnWindowChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        let close = left_pane.update(cx, |pane, cx| {
+            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
+        });
+        cx.executor().run_until_parked();
 
-//         // Deactivating the window saves the file.
-//         window.simulate_deactivation(cx);
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
-
-//         // Autosave on focus change.
-//         item.update(cx, |item, cx| {
-//             cx.focus_self();
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        // Discard "Save all" prompt
+        cx.simulate_prompt_answer(2);
 
-//         // Blurring the item saves the file.
-//         item.update(cx, |_, cx| cx.blur());
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
+        cx.executor().run_until_parked();
+        left_pane.update(cx, |pane, cx| {
+            assert_eq!(
+                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+                &[ProjectEntryId::from_proto(0)]
+            );
+        });
+        cx.simulate_prompt_answer(0);
 
-//         // Deactivating the window still saves the file.
-//         window.simulate_activation(cx);
-//         item.update(cx, |item, cx| {
-//             cx.focus_self();
-//             item.is_dirty = true;
-//         });
-//         window.simulate_deactivation(cx);
+        cx.executor().run_until_parked();
+        left_pane.update(cx, |pane, cx| {
+            assert_eq!(
+                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+                &[ProjectEntryId::from_proto(2)]
+            );
+        });
+        cx.simulate_prompt_answer(0);
 
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+        cx.executor().run_until_parked();
+        close.await.unwrap();
+        left_pane.update(cx, |pane, _| {
+            assert_eq!(pane.items_len(), 0);
+        });
+    }
 
-//         // Autosave after delay.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
-//                 })
-//             });
-//             item.is_dirty = true;
-//             cx.emit(TestItemEvent::Edit);
-//         });
+    #[gpui::test]
+    async fn test_autosave(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
 
-//         // Delay hasn't fully expired, so the file is still dirty and unsaved.
-//         deterministic.advance_clock(Duration::from_millis(250));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
 
-//         // After delay expires, the file is saved.
-//         deterministic.advance_clock(Duration::from_millis(250));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
+        let item = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let item_id = item.entity_id();
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+        });
 
-//         // Autosave on focus change, ensuring closing the tab counts as such.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        // Autosave on window change.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-//         })
-//         .await
-//         .unwrap();
-//         assert!(!window.has_pending_prompt(cx));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-
-//         // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//         });
-//         item.update(cx, |item, cx| {
-//             item.project_items[0].update(cx, |item, _| {
-//                 item.entry_id = None;
-//             });
-//             item.is_dirty = true;
-//             cx.blur();
-//         });
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+        // Deactivating the window saves the file.
+        cx.simulate_deactivation();
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
+
+        // Autosave on focus change.
+        item.update(cx, |item, cx| {
+            cx.focus_self();
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         // Ensure autosave is prevented for deleted files also when closing the buffer.
-//         let _close_items = pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-//         });
-//         deterministic.run_until_parked();
-//         assert!(window.has_pending_prompt(cx));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-//     }
+        // Blurring the item saves the file.
+        item.update(cx, |_, cx| cx.blur());
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
 
-//     #[gpui::test]
-//     async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
+        // Deactivating the window still saves the file.
+        cx.simulate_activation();
+        item.update(cx, |item, cx| {
+            cx.focus_self();
+            item.is_dirty = true;
+        });
+        cx.simulate_deactivation();
 
-//         let fs = FakeFs::new(cx.background());
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        // Autosave after delay.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+                })
+            });
+            item.is_dirty = true;
+            cx.emit(ItemEvent::Edit);
+        });
 
-//         let item = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
-//         let toolbar_notify_count = Rc::new(RefCell::new(0));
-
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//             let toolbar_notification_count = toolbar_notify_count.clone();
-//             cx.observe(&toolbar, move |_, _, _| {
-//                 *toolbar_notification_count.borrow_mut() += 1
-//             })
-//             .detach();
-//         });
+        // Delay hasn't fully expired, so the file is still dirty and unsaved.
+        cx.executor().advance_clock(Duration::from_millis(250));
+        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 
-//         pane.read_with(cx, |pane, _| {
-//             assert!(!pane.can_navigate_backward());
-//             assert!(!pane.can_navigate_forward());
-//         });
+        // After delay expires, the file is saved.
+        cx.executor().advance_clock(Duration::from_millis(250));
+        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
 
-//         item.update(cx, |item, cx| {
-//             item.set_state("one".to_string(), cx);
-//         });
+        // Autosave on focus change, ensuring closing the tab counts as such.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         // Toolbar must be notified to re-render the navigation buttons
-//         assert_eq!(*toolbar_notify_count.borrow(), 1);
+        pane.update(cx, |pane, cx| {
+            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+        })
+        .await
+        .unwrap();
+        assert!(!cx.has_pending_prompt());
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+
+        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+        });
+        item.update(cx, |item, cx| {
+            item.project_items[0].update(cx, |item, _| {
+                item.entry_id = None;
+            });
+            item.is_dirty = true;
+            cx.blur();
+        });
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
 
-//         pane.read_with(cx, |pane, _| {
-//             assert!(pane.can_navigate_backward());
-//             assert!(!pane.can_navigate_forward());
-//         });
+        // Ensure autosave is prevented for deleted files also when closing the buffer.
+        let _close_items = pane.update(cx, |pane, cx| {
+            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+        });
+        cx.executor().run_until_parked();
+        assert!(cx.has_pending_prompt());
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+    }
 
-//         workspace
-//             .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
-//             .await
-//             .unwrap();
+    #[gpui::test]
+    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
 
-//         assert_eq!(*toolbar_notify_count.borrow(), 3);
-//         pane.read_with(cx, |pane, _| {
-//             assert!(!pane.can_navigate_backward());
-//             assert!(pane.can_navigate_forward());
-//         });
-//     }
+        let fs = FakeFs::new(cx.executor());
 
-//     #[gpui::test]
-//     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
-//         let fs = FakeFs::new(cx.background());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        let item = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
+        let toolbar_notify_count = Rc::new(RefCell::new(0));
+
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+            let toolbar_notification_count = toolbar_notify_count.clone();
+            cx.observe(&toolbar, move |_, _, _| {
+                *toolbar_notification_count.borrow_mut() += 1
+            })
+            .detach();
+        });
 
-//         let panel = workspace.update(cx, |workspace, cx| {
-//             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-//             workspace.add_panel(panel.clone(), cx);
+        pane.update(cx, |pane, _| {
+            assert!(!pane.can_navigate_backward());
+            assert!(!pane.can_navigate_forward());
+        });
 
-//             workspace
-//                 .right_dock()
-//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+        item.update(cx, |item, cx| {
+            item.set_state("one".to_string(), cx);
+        });
 
-//             panel
-//         });
+        // Toolbar must be notified to re-render the navigation buttons
+        assert_eq!(*toolbar_notify_count.borrow(), 1);
 
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         pane.update(cx, |pane, cx| {
-//             let item = cx.build_view(|_| TestItem::new());
-//             pane.add_item(Box::new(item), true, true, None, cx);
-//         });
+        pane.update(cx, |pane, _| {
+            assert!(pane.can_navigate_backward());
+            assert!(!pane.can_navigate_forward());
+        });
 
-//         // Transfer focus from center to panel
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+        workspace
+            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
+            .await
+            .unwrap();
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+        assert_eq!(*toolbar_notify_count.borrow(), 2);
+        pane.update(cx, |pane, _| {
+            assert!(!pane.can_navigate_backward());
+            assert!(pane.can_navigate_forward());
+        });
+    }
 
-//         // Transfer focus from panel to center
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //     #[gpui::test]
+    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+    //         init_test(cx);
+    //         let fs = FakeFs::new(cx.executor());
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         let project = Project::test(fs, [], cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    //         let workspace = window.root(cx);
 
-//         // Close the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx);
-//         });
+    //         let panel = workspace.update(cx, |workspace, cx| {
+    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+    //             workspace.add_panel(panel.clone(), cx);
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //             workspace
+    //                 .right_dock()
+    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
 
-//         // Open the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx);
-//         });
+    //             panel
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+    //         pane.update(cx, |pane, cx| {
+    //             let item = cx.build_view(|_| TestItem::new(cx));
+    //             pane.add_item(Box::new(item), true, true, None, cx);
+    //         });
 
-//         // Focus and zoom panel
-//         panel.update(cx, |panel, cx| {
-//             cx.focus_self();
-//             panel.set_zoomed(true, cx)
-//         });
+    //         // Transfer focus from center to panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Transfer focus to the center closes the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         // Transfer focus from panel to center
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Transferring focus back to the panel keeps it zoomed
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         // Close the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Close the dock while it is zoomed
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
+    //         // Open the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(workspace.zoomed.is_none());
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Opening the dock, when it's zoomed, retains focus
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
+    //         // Focus and zoom panel
+    //         panel.update(cx, |panel, cx| {
+    //             cx.focus_self();
+    //             panel.set_zoomed(true, cx)
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(workspace.zoomed.is_some());
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Unzoom and close the panel, zoom the active pane.
-//         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
-//         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+    //         // Transfer focus to the center closes the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         // Opening a dock unzooms the pane.
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
-//         workspace.read_with(cx, |workspace, cx| {
-//             let pane = pane.read(cx);
-//             assert!(!pane.is_zoomed());
-//             assert!(!pane.has_focus());
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(workspace.zoomed.is_none());
-//         });
-//     }
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//     #[gpui::test]
-//     async fn test_panels(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
-//         let fs = FakeFs::new(cx.background());
-
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-
-//         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
-//             // Add panel_1 on the left, panel_2 on the right.
-//             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
-//             workspace.add_panel(panel_1.clone(), cx);
-//             workspace
-//                 .left_dock()
-//                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
-//             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-//             workspace.add_panel(panel_2.clone(), cx);
-//             workspace
-//                 .right_dock()
-//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-//             let left_dock = workspace.left_dock();
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(
-//                 left_dock.read(cx).active_panel_size(cx).unwrap(),
-//                 panel_1.size(cx)
-//             );
+    //         // Transferring focus back to the panel keeps it zoomed
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//             left_dock.update(cx, |left_dock, cx| {
-//                 left_dock.resize_active_panel(Some(1337.), cx)
-//             });
-//             assert_eq!(
-//                 workspace
-//                     .right_dock()
-//                     .read(cx)
-//                     .visible_panel()
-//                     .unwrap()
-//                     .id(),
-//                 panel_2.id()
-//             );
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//             (panel_1, panel_2)
-//         });
+    //         // Close the dock while it is zoomed
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
 
-//         // Move panel_1 to the right
-//         panel_1.update(cx, |panel_1, cx| {
-//             panel_1.set_position(DockPosition::Right, cx)
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(workspace.zoomed.is_none());
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
-//             // Since it was the only panel on the left, the left dock should now be closed.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
-//             let right_dock = workspace.right_dock();
-//             assert_eq!(
-//                 right_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //         // Opening the dock, when it's zoomed, retains focus
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
 
-//             // Now we move panel_2Β to the left
-//             panel_2.set_position(DockPosition::Left, cx);
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(workspace.zoomed.is_some());
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_2 was not visible on the right, we don't open the left dock.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             // And the right dock is unaffected in it's displaying of panel_1
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert_eq!(
-//                 workspace
-//                     .right_dock()
-//                     .read(cx)
-//                     .visible_panel()
-//                     .unwrap()
-//                     .id(),
-//                 panel_1.id()
-//             );
-//         });
+    //         // Unzoom and close the panel, zoom the active pane.
+    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
+    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
 
-//         // Move panel_1 back to the left
-//         panel_1.update(cx, |panel_1, cx| {
-//             panel_1.set_position(DockPosition::Left, cx)
-//         });
+    //         // Opening a dock unzooms the pane.
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             let pane = pane.read(cx);
+    //             assert!(!pane.is_zoomed());
+    //             assert!(!pane.has_focus());
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(workspace.zoomed.is_none());
+    //         });
+    //     }
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-//             // And right the dock should be closed as it no longer has any panels.
-//             assert!(!workspace.right_dock().read(cx).is_open());
+    //     #[gpui::test]
+    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
+    //         init_test(cx);
+    //         let fs = FakeFs::new(cx.executor());
+
+    //         let project = Project::test(fs, [], cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    //         let workspace = window.root(cx);
+
+    //         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+    //             // Add panel_1 on the left, panel_2 on the right.
+    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
+    //             workspace.add_panel(panel_1.clone(), cx);
+    //             workspace
+    //                 .left_dock()
+    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+    //             workspace.add_panel(panel_2.clone(), cx);
+    //             workspace
+    //                 .right_dock()
+    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+    //             let left_dock = workspace.left_dock();
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(
+    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
+    //                 panel_1.size(cx)
+    //             );
+
+    //             left_dock.update(cx, |left_dock, cx| {
+    //                 left_dock.resize_active_panel(Some(1337.), cx)
+    //             });
+    //             assert_eq!(
+    //                 workspace
+    //                     .right_dock()
+    //                     .read(cx)
+    //                     .visible_panel()
+    //                     .unwrap()
+    //                     .id(),
+    //                 panel_2.id()
+    //             );
+
+    //             (panel_1, panel_2)
+    //         });
 
-//             // Now we move panel_1 to the bottom
-//             panel_1.set_position(DockPosition::Bottom, cx);
-//         });
+    //         // Move panel_1 to the right
+    //         panel_1.update(cx, |panel_1, cx| {
+    //             panel_1.set_position(DockPosition::Right, cx)
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the left, we close the left dock.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             // The bottom dock is sized based on the panel's default size,
-//             // since the panel orientation changed from vertical to horizontal.
-//             let bottom_dock = workspace.bottom_dock();
-//             assert_eq!(
-//                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
-//                 panel_1.size(cx),
-//             );
-//             // Close bottom dock and move panel_1 back to the left.
-//             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
-//             panel_1.set_position(DockPosition::Left, cx);
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+    //             // Since it was the only panel on the left, the left dock should now be closed.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
+    //             let right_dock = workspace.right_dock();
+    //             assert_eq!(
+    //                 right_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+
+    //             // Now we move panel_2Β to the left
+    //             panel_2.set_position(DockPosition::Left, cx);
+    //         });
 
-//         // Emit activated event on panel 1
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_2 was not visible on the right, we don't open the left dock.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             // And the right dock is unaffected in it's displaying of panel_1
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert_eq!(
+    //                 workspace
+    //                     .right_dock()
+    //                     .read(cx)
+    //                     .visible_panel()
+    //                     .unwrap()
+    //                     .id(),
+    //                 panel_1.id()
+    //             );
+    //         });
 
-//         // Now the left dock is open and panel_1 is active and focused.
-//         workspace.read_with(cx, |workspace, cx| {
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert!(panel_1.is_focused(cx));
-//         });
+    //         // Move panel_1 back to the left
+    //         panel_1.update(cx, |panel_1, cx| {
+    //             panel_1.set_position(DockPosition::Left, cx)
+    //         });
 
-//         // Emit closed event on panel 2, which is not active
-//         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //             // And right the dock should be closed as it no longer has any panels.
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+
+    //             // Now we move panel_1 to the bottom
+    //             panel_1.set_position(DockPosition::Bottom, cx);
+    //         });
 
-//         // Wo don't close the left dock, because panel_2 wasn't the active panel
-//         workspace.read_with(cx, |workspace, cx| {
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the left, we close the left dock.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             // The bottom dock is sized based on the panel's default size,
+    //             // since the panel orientation changed from vertical to horizontal.
+    //             let bottom_dock = workspace.bottom_dock();
+    //             assert_eq!(
+    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
+    //                 panel_1.size(cx),
+    //             );
+    //             // Close bottom dock and move panel_1 back to the left.
+    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
+    //             panel_1.set_position(DockPosition::Left, cx);
+    //         });
 
-//         // Emitting a ZoomIn event shows the panel as zoomed.
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-//         });
+    //         // Emit activated event on panel 1
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+
+    //         // Now the left dock is open and panel_1 is active and focused.
+    //         workspace.update(cx, |workspace, cx| {
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert!(panel_1.is_focused(cx));
+    //         });
 
-//         // Move panel to another dock while it is zoomed
-//         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // Emit closed event on panel 2, which is not active
+    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //         });
 
-//         // If focus is transferred to another view that's not a panel or another pane, we still show
-//         // the panel as zoomed.
-//         let focus_receiver = window.build_view(cx, |_| EmptyView);
-//         focus_receiver.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // Emitting a ZoomIn event shows the panel as zoomed.
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
+    //         });
 
-//         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
-//         workspace.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // Move panel to another dock while it is zoomed
+    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // If focus is transferred again to another view that's not a panel or a pane, we won't
-//         // show the panel as zoomed because it wasn't zoomed before.
-//         focus_receiver.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // If focus is transferred to another view that's not a panel or another pane, we still show
+    //         // the panel as zoomed.
+    //         let focus_receiver = cx.build_view(|_| EmptyView);
+    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // When focus is transferred back to the panel, it is zoomed again.
-//         panel_1.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
+    //         workspace.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // Emitting a ZoomOut event unzooms the panel.
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
+    //         // show the panel as zoomed because it wasn't zoomed before.
+    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // Emit closed event on panel 1, which is active
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+    //         // When focus is transferred back to the panel, it is zoomed again.
+    //         panel_1.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // Now the left dock is closed, because panel_1 was the active panel
-//         workspace.read_with(cx, |workspace, cx| {
-//             let right_dock = workspace.right_dock();
-//             assert!(!right_dock.read(cx).is_open());
-//         });
-//     }
+    //         // Emitting a ZoomOut event unzooms the panel.
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//     pub fn init_test(cx: &mut TestAppContext) {
-//         cx.foreground().forbid_parking();
-//         cx.update(|cx| {
-//             cx.set_global(SettingsStore::test(cx));
-//             theme::init((), cx);
-//             language::init(cx);
-//             crate::init_settings(cx);
-//             Project::init_settings(cx);
-//         });
-//     }
-// }
+    //         // Emit closed event on panel 1, which is active
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+    //         // Now the left dock is closed, because panel_1 was the active panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             let right_dock = workspace.right_dock();
+    //             assert!(!right_dock.read(cx).is_open());
+    //         });
+    //     }
+
+    pub fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
+            theme::init(theme::LoadThemes::JustBase, cx);
+            language::init(cx);
+            crate::init_settings(cx);
+            Project::init_settings(cx);
+        });
+    }
+}

crates/zed/src/languages/json.rs πŸ”—

@@ -105,6 +105,7 @@ impl LspAdapter for JsonLspAdapter {
 
     fn workspace_configuration(
         &self,
+        _workspace_root: &Path,
         cx: &mut AppContext,
     ) -> BoxFuture<'static, serde_json::Value> {
         let action_names = cx.all_action_names().collect::<Vec<_>>();

crates/zed/src/languages/php.rs πŸ”—

@@ -29,7 +29,6 @@ pub struct IntelephenseLspAdapter {
 impl IntelephenseLspAdapter {
     const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
 
-    #[allow(unused)]
     pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         Self { node }
     }

crates/zed/src/languages/tailwind.rs πŸ”—

@@ -107,7 +107,11 @@ impl LspAdapter for TailwindLspAdapter {
         }))
     }
 
-    fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(
+        &self,
+        _workspace_root: &Path,
+        _: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
         future::ready(json!({
             "tailwindCSS": {
                 "emmetCompletions": true,

crates/zed/src/languages/typescript.rs πŸ”—

@@ -205,7 +205,6 @@ pub struct EsLintLspAdapter {
 impl EsLintLspAdapter {
     const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
 
-    #[allow(unused)]
     pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         EsLintLspAdapter { node }
     }
@@ -213,13 +212,23 @@ impl EsLintLspAdapter {
 
 #[async_trait]
 impl LspAdapter for EsLintLspAdapter {
-    fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(
+        &self,
+        workspace_root: &Path,
+        _: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
         future::ready(json!({
             "": {
                 "validate": "on",
                 "rulesCustomizations": [],
                 "run": "onType",
                 "nodePath": null,
+                "workingDirectory": {"mode": "auto"},
+                "workspaceFolder": {
+                    "uri": workspace_root,
+                    "name": workspace_root.file_name()
+                        .unwrap_or_else(|| workspace_root.as_os_str()),
+                },
             }
         }))
         .boxed()

crates/zed/src/languages/yaml.rs πŸ”—

@@ -93,7 +93,11 @@ impl LspAdapter for YamlLspAdapter {
     ) -> Option<LanguageServerBinary> {
         get_cached_server_binary(container_dir, &*self.node).await
     }
-    fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(
+        &self,
+        _workspace_root: &Path,
+        cx: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
         let tab_size = all_language_settings(None, cx)
             .language(Some("YAML"))
             .tab_size;

crates/zed2/Cargo.toml πŸ”—

@@ -17,9 +17,9 @@ path = "src/main.rs"
 [dependencies]
 ai = { package = "ai2", path = "../ai2"}
 audio = { package = "audio2", path = "../audio2" }
-# activity_indicator = { path = "../activity_indicator" }
+activity_indicator = { package = "activity_indicator2", path = "../activity_indicator2"}
 auto_update = { package = "auto_update2", path = "../auto_update2" }
-# breadcrumbs = { path = "../breadcrumbs" }
+breadcrumbs = { package = "breadcrumbs2", path = "../breadcrumbs2" }
 call = { package = "call2", path = "../call2" }
 channel = { package = "channel2", path = "../channel2" }
 cli = { path = "../cli" }

crates/zed2/src/languages/json.rs πŸ”—

@@ -105,6 +105,7 @@ impl LspAdapter for JsonLspAdapter {
 
     fn workspace_configuration(
         &self,
+        _workspace_root: &Path,
         cx: &mut AppContext,
     ) -> BoxFuture<'static, serde_json::Value> {
         let action_names = cx.all_action_names();

crates/zed2/src/languages/php.rs πŸ”—

@@ -29,7 +29,6 @@ pub struct IntelephenseLspAdapter {
 impl IntelephenseLspAdapter {
     const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
 
-    #[allow(unused)]
     pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         Self { node }
     }

crates/zed2/src/languages/tailwind.rs πŸ”—

@@ -107,7 +107,11 @@ impl LspAdapter for TailwindLspAdapter {
         }))
     }
 
-    fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(
+        &self,
+        _workspace_root: &Path,
+        _: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
         future::ready(json!({
             "tailwindCSS": {
                 "emmetCompletions": true,

crates/zed2/src/languages/typescript.rs πŸ”—

@@ -205,7 +205,6 @@ pub struct EsLintLspAdapter {
 impl EsLintLspAdapter {
     const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
 
-    #[allow(unused)]
     pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         EsLintLspAdapter { node }
     }
@@ -213,13 +212,23 @@ impl EsLintLspAdapter {
 
 #[async_trait]
 impl LspAdapter for EsLintLspAdapter {
-    fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(
+        &self,
+        workspace_root: &Path,
+        _: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
         future::ready(json!({
             "": {
                 "validate": "on",
                 "rulesCustomizations": [],
                 "run": "onType",
                 "nodePath": null,
+                "workingDirectory": {"mode": "auto"},
+                "workspaceFolder": {
+                    "uri": workspace_root,
+                    "name": workspace_root.file_name()
+                        .unwrap_or_else(|| workspace_root.as_os_str()),
+                },
             }
         }))
         .boxed()

crates/zed2/src/languages/yaml.rs πŸ”—

@@ -93,7 +93,11 @@ impl LspAdapter for YamlLspAdapter {
     ) -> Option<LanguageServerBinary> {
         get_cached_server_binary(container_dir, &*self.node).await
     }
-    fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+    fn workspace_configuration(
+        &self,
+        _workspace_root: &Path,
+        cx: &mut AppContext,
+    ) -> BoxFuture<'static, Value> {
         let tab_size = all_language_settings(None, cx)
             .language(Some("YAML"))
             .tab_size;

crates/zed2/src/main.rs πŸ”—

@@ -746,9 +746,9 @@ fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
 }
 
 #[cfg(not(debug_assertions))]
-async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
+async fn watch_languages(_: Arc<dyn fs::Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
     None
 }
 
 #[cfg(not(debug_assertions))]
-fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
+fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}

crates/zed2/src/zed2.rs πŸ”—

@@ -7,6 +7,7 @@ mod only_instance;
 mod open_listener;
 
 pub use assets::*;
+use breadcrumbs::Breadcrumbs;
 use collections::VecDeque;
 use editor::{Editor, MultiBuffer};
 use gpui::{
@@ -95,11 +96,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                 if let workspace::Event::PaneAdded(pane) = event {
                     pane.update(cx, |pane, cx| {
                         pane.toolbar().update(cx, |toolbar, cx| {
-                            // todo!()
-                            //     let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
-                            //     toolbar.add_item(breadcrumbs, cx);
+                            let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
+                            toolbar.add_item(breadcrumbs, cx);
                             let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
                             toolbar.add_item(buffer_search_bar.clone(), cx);
+                            // todo!()
                             //     let quick_action_bar = cx.add_view(|_| {
                             //         QuickActionBar::new(buffer_search_bar, workspace)
                             //     });
@@ -139,27 +140,24 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
         //         cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
         let diagnostic_summary =
             cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
-        //     let activity_indicator = activity_indicator::ActivityIndicator::new(
-        //         workspace,
-        //         app_state.languages.clone(),
-        //         cx,
-        //     );
+        let activity_indicator =
+            activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
         //     let active_buffer_language =
         //         cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
         //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
         //     let feedback_button = cx.add_view(|_| {
         //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
         //     });
-        //     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+        let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
         workspace.status_bar().update(cx, |status_bar, cx| {
             status_bar.add_left_item(diagnostic_summary, cx);
-            // status_bar.add_left_item(activity_indicator, cx);
+            status_bar.add_left_item(activity_indicator, cx);
 
             // status_bar.add_right_item(feedback_button, cx);
             // status_bar.add_right_item(copilot, cx);
             // status_bar.add_right_item(active_buffer_language, cx);
             // status_bar.add_right_item(vim_mode_indicator, cx);
-            // status_bar.add_right_item(cursor_position, cx);
+            status_bar.add_right_item(cursor_position, cx);
         });
 
         auto_update::notify_of_any_new_update(cx);

test.rs πŸ”—

@@ -1,5618 +0,0 @@
-#![feature(prelude_import)]
-#![allow(dead_code, unused_variables)]
-#[prelude_import]
-use std::prelude::rust_2021::*;
-#[macro_use]
-extern crate std;
-use color::black;
-use components::button;
-use element::Element;
-use frame::frame;
-use gpui::{
-    geometry::{rect::RectF, vector::vec2f},
-    platform::WindowOptions,aa
-};
-use log::LevelFilter;a
-use simplelog::SimpleLogger;
-use themes::{rose_pine, ThemeColors};
-use view::view;a
-mod adapter {
-    use crate::element::AnyElement;
-    use crate::element::{LayoutContext, PaintContext};
-    use gpui::{geometry::rect::RectF, LayoutEngine};aaaa
-    use util::ResultExt;
-    pub struct Adapter<V>(pub(crate) AnyElement<V>);
-    impl<V: 'static> gpui::Element<V> for Adapter<V> {aa
-        type LayoutState = Option<LayaoutEngine>;
-        type PaintState = ();
-        fn layout(
-            &mut self,
-            constraint: gpui::SizeConstraint,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,aa
-        ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
-            cx.push_layout_engine(LayoutEngine::new());
-            let node = self.0.layout(view, cx).log_err();a
-            if let Some(node) = node {
-                let layout_engine = cx.layout_engine().unwrap();
-                layout_engine.compute_layout(node, constraint.max).log_err();
-            }
-            let layout_engine = cx.pop_layout_engine();
-            if true {a
-                if !layout_engine.is_some() {
-                    ::core::panicking::panic("assertion failed: layout_engine.is_some()")
-                }
-            }
-            (constraint.max, layout_engine)a
-        }
-        fn paint(a
-            &mut self,
-            scene: &mut gpui::SceneBuilder,
-            bounds: RectF,
-            visible_bounds: RectF,
-            layout_engine: &mut Option<LayoutEngine>,
-            view: &mut V,
-            legacy_cx: &mut gpui::PaintContext<V>,aaa
-        ) -> Self::PaintState {
-            legacy_cx.push_layout_engine(layout_engine.take().unwrap());
-            let mut cx = PaintContext::new(legacy_cx, scene);
-            self.0.paint(view, &mut cx).log_err();
-            *layout_engine = legacy_cx.pop_layout_engine();
-            if true {
-                if !layout_engine.is_some() {
-                    ::core::panicking::panic("assertion failed: layout_engine.is_some()")
-                }
-            }
-        }
-        fn rect_for_text_range(
-            &self,
-            range_utf16: std::ops::Range<usize>,
-            bounds: RectF,
-            visible_bounds: RectF,
-            layout: &Self::LayoutState,
-            paint: &Self::PaintState,
-            view: &V,
-            cx: &gpui::ViewContext<V>,
-        ) -> Option<RectF> {
-            ::core::panicking::panic("not yet implemented")
-        }
-        fn debug(
-            &self,
-            bounds: RectF,
-            layout: &Self::LayoutState,
-            paint: &Self::PaintState,
-            view: &V,
-            cx: &gpui::ViewContext<V>,
-        ) -> gpui::serde_json::Value {
-            ::core::panicking::panic("not yet implemented")
-        }
-    }
-}
-mod color {
-    #![allow(dead_code)]
-    use smallvec::SmallVec;
-    use std::{num::ParseIntError, ops::Range};
-    pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
-        let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
-        let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
-        let b = (hex & 0xFF) as f32 / 255.0;
-        Rgba { r, g, b, a: 1.0 }.into()
-    }
-    pub struct Rgba {
-        pub r: f32,
-        pub g: f32,
-        pub b: f32,
-        pub a: f32,
-    }
-    #[automatically_derived]
-    impl ::core::clone::Clone for Rgba {
-        #[inline]
-        fn clone(&self) -> Rgba {
-            let _: ::core::clone::AssertParamIsClone<f32>;
-            *self
-        }
-    }
-    #[automatically_derived]
-    impl ::core::marker::Copy for Rgba {}
-    #[automatically_derived]
-    impl ::core::default::Default for Rgba {
-        #[inline]
-        fn default() -> Rgba {
-            Rgba {
-                r: ::core::default::Default::default(),
-                g: ::core::default::Default::default(),
-                b: ::core::default::Default::default(),
-                a: ::core::default::Default::default(),
-            }
-        }
-    }
-    #[automatically_derived]
-    impl ::core::fmt::Debug for Rgba {
-        fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-            ::core::fmt::Formatter::debug_struct_field4_finish(
-                f, "Rgba", "r", &self.r, "g", &self.g, "b", &self.b, "a", &&self.a,
-            )
-        }
-    }
-    pub trait Lerp {
-        fn lerp(&self, level: f32) -> Hsla;
-    }
-    impl Lerp for Range<Hsla> {
-        fn lerp(&self, level: f32) -> Hsla {
-            let level = level.clamp(0., 1.);
-            Hsla {
-                h: self.start.h + (level * (self.end.h - self.start.h)),
-                s: self.start.s + (level * (self.end.s - self.start.s)),
-                l: self.start.l + (level * (self.end.l - self.start.l)),
-                a: self.start.a + (level * (self.end.a - self.start.a)),
-            }
-        }
-    }
-    impl From<gpui::color::Color> for Rgba {
-        fn from(value: gpui::color::Color) -> Self {
-            Self {
-                r: value.0.r as f32 / 255.0,
-                g: value.0.g as f32 / 255.0,
-                b: value.0.b as f32 / 255.0,
-                a: value.0.a as f32 / 255.0,
-            }
-        }
-    }
-    impl From<Hsla> for Rgba {
-        fn from(color: Hsla) -> Self {
-            let h = color.h;
-            let s = color.s;
-            let l = color.l;
-            let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
-            let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
-            let m = l - c / 2.0;
-            let cm = c + m;
-            let xm = x + m;
-            let (r, g, b) = match (h * 6.0).floor() as i32 {
-                0 | 6 => (cm, xm, m),
-                1 => (xm, cm, m),
-                2 => (m, cm, xm),
-                3 => (m, xm, cm),
-                4 => (xm, m, cm),
-                _ => (cm, m, xm),
-            };
-            Rgba {
-                r,
-                g,
-                b,
-                a: color.a,
-            }
-        }
-    }
-    impl TryFrom<&'_ str> for Rgba {
-        type Error = ParseIntError;
-        fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
-            let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
-            let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
-            let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
-            let a = if value.len() > 7 {
-                u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
-            } else {
-                1.0
-            };
-            Ok(Rgba { r, g, b, a })
-        }
-    }
-    impl Into<gpui::color::Color> for Rgba {
-        fn into(self) -> gpui::color::Color {
-            gpui::color::rgba(self.r, self.g, self.b, self.a)
-        }
-    }
-    pub struct Hsla {
-        pub h: f32,
-        pub s: f32,
-        pub l: f32,
-        pub a: f32,
-    }
-    #[automatically_derived]
-    impl ::core::default::Default for Hsla {
-        #[inline]
-        fn default() -> Hsla {
-            Hsla {
-                h: ::core::default::Default::default(),
-                s: ::core::default::Default::default(),
-                l: ::core::default::Default::default(),
-                a: ::core::default::Default::default(),
-            }
-        }
-    }
-    #[automatically_derived]
-    impl ::core::marker::Copy for Hsla {}
-    #[automatically_derived]
-    impl ::core::clone::Clone for Hsla {
-        #[inline]
-        fn clone(&self) -> Hsla {
-            let _: ::core::clone::AssertParamIsClone<f32>;
-            *self
-        }
-    }
-    #[automatically_derived]
-    impl ::core::fmt::Debug for Hsla {
-        fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-            ::core::fmt::Formatter::debug_struct_field4_finish(
-                f, "Hsla", "h", &self.h, "s", &self.s, "l", &self.l, "a", &&self.a,
-            )
-        }
-    }
-    #[automatically_derived]
-    impl ::core::marker::StructuralPartialEq for Hsla {}
-    #[automatically_derived]
-    impl ::core::cmp::PartialEq for Hsla {
-        #[inline]
-        fn eq(&self, other: &Hsla) -> bool {
-            self.h == other.h && self.s == other.s && self.l == other.l && self.a == other.a
-        }
-    }
-    pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
-        Hsla {
-            h: h.clamp(0., 1.),
-            s: s.clamp(0., 1.),
-            l: l.clamp(0., 1.),
-            a: a.clamp(0., 1.),
-        }
-    }
-    pub fn black() -> Hsla {
-        Hsla {
-            h: 0.,
-            s: 0.,
-            l: 0.,
-            a: 1.,
-        }
-    }
-    impl From<Rgba> for Hsla {
-        fn from(color: Rgba) -> Self {
-            let r = color.r;
-            let g = color.g;
-            let b = color.b;
-            let max = r.max(g.max(b));
-            let min = r.min(g.min(b));
-            let delta = max - min;
-            let l = (max + min) / 2.0;
-            let s = if l == 0.0 || l == 1.0 {
-                0.0
-            } else if l < 0.5 {
-                delta / (2.0 * l)
-            } else {
-                delta / (2.0 - 2.0 * l)
-            };
-            let h = if delta == 0.0 {
-                0.0
-            } else if max == r {
-                ((g - b) / delta).rem_euclid(6.0) / 6.0
-            } else if max == g {
-                ((b - r) / delta + 2.0) / 6.0
-            } else {
-                ((r - g) / delta + 4.0) / 6.0
-            };
-            Hsla {
-                h,
-                s,
-                l,
-                a: color.a,
-            }
-        }
-    }
-    impl Hsla {
-        /// Scales the saturation and lightness by the given values, clamping at 1.0.
-        pub fn scale_sl(mut self, s: f32, l: f32) -> Self {
-            self.s = (self.s * s).clamp(0., 1.);
-            self.l = (self.l * l).clamp(0., 1.);
-            self
-        }
-        /// Increases the saturation of the color by a certain amount, with a max
-        /// value of 1.0.
-        pub fn saturate(mut self, amount: f32) -> Self {
-            self.s += amount;
-            self.s = self.s.clamp(0.0, 1.0);
-            self
-        }
-        /// Decreases the saturation of the color by a certain amount, with a min
-        /// value of 0.0.
-        pub fn desaturate(mut self, amount: f32) -> Self {
-            self.s -= amount;
-            self.s = self.s.max(0.0);
-            if self.s < 0.0 {
-                self.s = 0.0;
-            }
-            self
-        }
-        /// Brightens the color by increasing the lightness by a certain amount,
-        /// with a max value of 1.0.
-        pub fn brighten(mut self, amount: f32) -> Self {
-            self.l += amount;
-            self.l = self.l.clamp(0.0, 1.0);
-            self
-        }
-        /// Darkens the color by decreasing the lightness by a certain amount,
-        /// with a max value of 0.0.
-        pub fn darken(mut self, amount: f32) -> Self {
-            self.l -= amount;
-            self.l = self.l.clamp(0.0, 1.0);
-            self
-        }
-    }
-    impl From<gpui::color::Color> for Hsla {
-        fn from(value: gpui::color::Color) -> Self {
-            Rgba::from(value).into()
-        }
-    }
-    impl Into<gpui::color::Color> for Hsla {
-        fn into(self) -> gpui::color::Color {
-            Rgba::from(self).into()
-        }
-    }
-    pub struct ColorScale {
-        colors: SmallVec<[Hsla; 2]>,
-        positions: SmallVec<[f32; 2]>,
-    }
-    pub fn scale<I, C>(colors: I) -> ColorScale
-    where
-        I: IntoIterator<Item = C>,
-        C: Into<Hsla>,
-    {
-        let mut scale = ColorScale {
-            colors: colors.into_iter().map(Into::into).collect(),
-            positions: SmallVec::new(),
-        };
-        let num_colors: f32 = scale.colors.len() as f32 - 1.0;
-        scale.positions = (0..scale.colors.len())
-            .map(|i| i as f32 / num_colors)
-            .collect();
-        scale
-    }
-    impl ColorScale {
-        fn at(&self, t: f32) -> Hsla {
-            if true {
-                if !(0.0 <= t && t <= 1.0) {
-                    {
-                        ::core::panicking::panic_fmt(format_args!(
-                            "t value {0} is out of range. Expected value in range 0.0 to 1.0",
-                            t,
-                        ));
-                    }
-                }
-            }
-            let position = match self
-                .positions
-                .binary_search_by(|a| a.partial_cmp(&t).unwrap())
-            {
-                Ok(index) | Err(index) => index,
-            };
-            let lower_bound = position.saturating_sub(1);
-            let upper_bound = position.min(self.colors.len() - 1);
-            let lower_color = &self.colors[lower_bound];
-            let upper_color = &self.colors[upper_bound];
-            match upper_bound.checked_sub(lower_bound) {
-                Some(0) | None => *lower_color,
-                Some(_) => {
-                    let interval_t = (t - self.positions[lower_bound])
-                        / (self.positions[upper_bound] - self.positions[lower_bound]);
-                    let h = lower_color.h + interval_t * (upper_color.h - lower_color.h);
-                    let s = lower_color.s + interval_t * (upper_color.s - lower_color.s);
-                    let l = lower_color.l + interval_t * (upper_color.l - lower_color.l);
-                    let a = lower_color.a + interval_t * (upper_color.a - lower_color.a);
-                    Hsla { h, s, l, a }
-                }
-            }
-        }
-    }
-}
-mod components {
-    use crate::{
-        element::{Element, ElementMetadata},
-        frame,
-        text::ArcCow,
-        themes::rose_pine,
-    };
-    use gpui::{platform::MouseButton, ViewContext};
-    use gpui_macros::Element;
-    use std::{marker::PhantomData, rc::Rc};
-    struct ButtonHandlers<V, D> {
-        click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
-    }
-    impl<V, D> Default for ButtonHandlers<V, D> {
-        fn default() -> Self {
-            Self { click: None }
-        }
-    }
-    #[element_crate = "crate"]
-    pub struct Button<V: 'static, D: 'static> {
-        metadata: ElementMetadata<V>,
-        handlers: ButtonHandlers<V, D>,
-        label: Option<ArcCow<'static, str>>,
-        icon: Option<ArcCow<'static, str>>,
-        data: Rc<D>,
-        view_type: PhantomData<V>,
-    }
-    impl<V: 'static, D: 'static> crate::element::Element<V> for Button<V, D> {
-        type Layout = crate::element::AnyElement<V>;
-        fn declared_style(&mut self) -> &mut crate::style::OptionalStyle {
-            &mut self.metadata.style
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<crate::element::EventHandler<V>> {
-            &mut self.metadata.handlers
-        }
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut crate::element::LayoutContext<V>,
-        ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
-            let mut element = self.render(view, cx).into_any();
-            let node_id = element.layout(view, cx)?;
-            Ok((node_id, element))
-        }
-        fn paint<'a>(
-            &mut self,
-            layout: crate::element::Layout<'a, Self::Layout>,
-            view: &mut V,
-            cx: &mut crate::element::PaintContext<V>,
-        ) -> anyhow::Result<()> {
-            layout.from_element.paint(view, cx)?;
-            Ok(())
-        }
-    }
-    impl<V: 'static, D: 'static> crate::element::IntoElement<V> for Button<V, D> {
-        type Element = Self;
-        fn into_element(self) -> Self {
-            self
-        }
-    }
-    impl<V: 'static> Button<V, ()> {
-        fn new() -> Self {
-            Self {
-                metadata: Default::default(),
-                handlers: ButtonHandlers::default(),
-                label: None,
-                icon: None,
-                data: Rc::new(()),
-                view_type: PhantomData,
-            }
-        }
-        pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
-            Button {
-                metadata: Default::default(),
-                handlers: ButtonHandlers::default(),
-                label: self.label,
-                icon: self.icon,
-                data: Rc::new(data),
-                view_type: PhantomData,
-            }
-        }
-    }
-    impl<V: 'static, D: 'static> Button<V, D> {
-        pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
-            self.label = Some(label.into());
-            self
-        }
-        pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
-            self.icon = Some(icon.into());
-            self
-        }
-        pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
-            let data = self.data.clone();
-            Element::click(self, MouseButton::Left, move |view, _, cx| {
-                handler(view, data.as_ref(), cx);
-            })
-        }
-    }
-    pub fn button<V>() -> Button<V, ()> {
-        Button::new()
-    }
-    impl<V: 'static, D: 'static> Button<V, D> {
-        fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-            let button = frame()
-                .fill(rose_pine::dawn().error(0.5))
-                .h_4()
-                .children(self.label.clone());
-            if let Some(handler) = self.handlers.click.clone() {
-                let data = self.data.clone();
-                button.mouse_down(MouseButton::Left, move |view, event, cx| {
-                    handler(view, data.as_ref(), cx)
-                })
-            } else {
-                button
-            }
-        }
-    }
-}
-mod element {
-    pub use crate::paint_context::PaintContext;
-    use crate::{
-        adapter::Adapter,
-        color::Hsla,
-        hoverable::Hoverable,
-        style::{Display, Fill, OptionalStyle, Overflow, Position},
-    };
-    use anyhow::Result;
-    pub use gpui::LayoutContext;
-    use gpui::{
-        geometry::{DefinedLength, Length, OptionalPoint},
-        platform::{MouseButton, MouseButtonEvent},
-        EngineLayout, EventContext, RenderContext, ViewContext,
-    };
-    use gpui_macros::tailwind_lengths;
-    use std::{
-        any::{Any, TypeId},
-        cell::Cell,
-        rc::Rc,
-    };
-    pub use taffy::tree::NodeId;
-    pub struct Layout<'a, E: ?Sized> {
-        pub from_engine: EngineLayout,
-        pub from_element: &'a mut E,
-    }
-    pub struct ElementMetadata<V> {
-        pub style: OptionalStyle,
-        pub handlers: Vec<EventHandler<V>>,
-    }
-    pub struct EventHandler<V> {
-        handler: Rc<dyn Fn(&mut V, &dyn Any, &mut EventContext<V>)>,
-        event_type: TypeId,
-        outside_bounds: bool,
-    }
-    impl<V> Clone for EventHandler<V> {
-        fn clone(&self) -> Self {
-            Self {
-                handler: self.handler.clone(),
-                event_type: self.event_type,
-                outside_bounds: self.outside_bounds,
-            }
-        }
-    }
-    impl<V> Default for ElementMetadata<V> {
-        fn default() -> Self {
-            Self {
-                style: OptionalStyle::default(),
-                handlers: Vec::new(),
-            }
-        }
-    }
-    pub trait Element<V: 'static>: 'static {
-        type Layout: 'static;
-        fn declared_style(&mut self) -> &mut OptionalStyle;
-        fn computed_style(&mut self) -> &OptionalStyle {
-            self.declared_style()
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>>;
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
-        ) -> Result<(NodeId, Self::Layout)>;
-        fn paint<'a>(
-            &mut self,
-            layout: Layout<Self::Layout>,
-            view: &mut V,
-            cx: &mut PaintContext<V>,
-        ) -> Result<()>;
-        /// Convert to a dynamically-typed element suitable for layout and paint.
-        fn into_any(self) -> AnyElement<V>
-        where
-            Self: 'static + Sized,
-        {
-            AnyElement {
-                element: Box::new(self) as Box<dyn ElementObject<V>>,
-                layout: None,
-            }
-        }
-        fn adapt(self) -> Adapter<V>
-        where
-            Self: Sized,
-            Self: Element<V>,
-        {
-            Adapter(self.into_any())
-        }
-        fn click(
-            self,
-            button: MouseButton,
-            handler: impl Fn(&mut V, &MouseButtonEvent, &mut ViewContext<V>) + 'static,
-        ) -> Self
-        where
-            Self: Sized,
-        {
-            let pressed: Rc<Cell<bool>> = Default::default();
-            self.mouse_down(button, {
-                let pressed = pressed.clone();
-                move |_, _, _| {
-                    pressed.set(true);
-                }
-            })
-            .mouse_up_outside(button, {
-                let pressed = pressed.clone();
-                move |_, _, _| {
-                    pressed.set(false);
-                }
-            })
-            .mouse_up(button, move |view, event, event_cx| {
-                if pressed.get() {
-                    pressed.set(false);
-                    handler(view, event, event_cx);
-                }
-            })
-        }
-        fn mouse_down(
-            mut self,
-            button: MouseButton,
-            handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
-        ) -> Self
-        where
-            Self: Sized,
-        {
-            self.handlers_mut().push(EventHandler {
-                handler: Rc::new(move |view, event, event_cx| {
-                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                    if event.button == button && event.is_down {
-                        handler(view, event, event_cx);
-                    }
-                }),
-                event_type: TypeId::of::<MouseButtonEvent>(),
-                outside_bounds: false,
-            });
-            self
-        }
-        fn mouse_down_outside(
-            mut self,
-            button: MouseButton,
-            handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
-        ) -> Self
-        where
-            Self: Sized,
-        {
-            self.handlers_mut().push(EventHandler {
-                handler: Rc::new(move |view, event, event_cx| {
-                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                    if event.button == button && event.is_down {
-                        handler(view, event, event_cx);
-                    }
-                }),
-                event_type: TypeId::of::<MouseButtonEvent>(),
-                outside_bounds: true,
-            });
-            self
-        }
-        fn mouse_up(
-            mut self,
-            button: MouseButton,
-            handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
-        ) -> Self
-        where
-            Self: Sized,
-        {
-            self.handlers_mut().push(EventHandler {
-                handler: Rc::new(move |view, event, event_cx| {
-                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                    if event.button == button && !event.is_down {
-                        handler(view, event, event_cx);
-                    }
-                }),
-                event_type: TypeId::of::<MouseButtonEvent>(),
-                outside_bounds: false,
-            });
-            self
-        }
-        fn mouse_up_outside(
-            mut self,
-            button: MouseButton,
-            handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
-        ) -> Self
-        where
-            Self: Sized,
-        {
-            self.handlers_mut().push(EventHandler {
-                handler: Rc::new(move |view, event, event_cx| {
-                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                    if event.button == button && !event.is_down {
-                        handler(view, event, event_cx);
-                    }
-                }),
-                event_type: TypeId::of::<MouseButtonEvent>(),
-                outside_bounds: true,
-            });
-            self
-        }
-        fn block(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().display = Some(Display::Block);
-            self
-        }
-        fn flex(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().display = Some(Display::Flex);
-            self
-        }
-        fn grid(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().display = Some(Display::Grid);
-            self
-        }
-        fn overflow_visible(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow = OptionalPoint {
-                x: Some(Overflow::Visible),
-                y: Some(Overflow::Visible),
-            };
-            self
-        }
-        fn overflow_hidden(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow = OptionalPoint {
-                x: Some(Overflow::Hidden),
-                y: Some(Overflow::Hidden),
-            };
-            self
-        }
-        fn overflow_scroll(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow = OptionalPoint {
-                x: Some(Overflow::Scroll),
-                y: Some(Overflow::Scroll),
-            };
-            self
-        }
-        fn overflow_x_visible(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow.x = Some(Overflow::Visible);
-            self
-        }
-        fn overflow_x_hidden(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow.x = Some(Overflow::Hidden);
-            self
-        }
-        fn overflow_x_scroll(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow.x = Some(Overflow::Scroll);
-            self
-        }
-        fn overflow_y_visible(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow.y = Some(Overflow::Visible);
-            self
-        }
-        fn overflow_y_hidden(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow.y = Some(Overflow::Hidden);
-            self
-        }
-        fn overflow_y_scroll(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().overflow.y = Some(Overflow::Scroll);
-            self
-        }
-        fn relative(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().position = Some(Position::Relative);
-            self
-        }
-        fn absolute(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().position = Some(Position::Absolute);
-            self
-        }
-        fn inset_0(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(0.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_px(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(1.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_0_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.125).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.25).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.375).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.5).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.625).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_3(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.75).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_3_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.875).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_4(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.25).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_6(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.5).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_7(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.75).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_8(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_9(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.25).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_10(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.5).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_11(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.75).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_12(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_14(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.5).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_16(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(4.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_20(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(5.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_24(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(6.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_28(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(7.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_32(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(8.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_36(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(9.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_40(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(10.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_44(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(11.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_48(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(12.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_52(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(13.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_56(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(14.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_60(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(15.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_64(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(16.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_72(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(18.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_80(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(20.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_96(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(24.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_half(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_3_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(20.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(40.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_3_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(60.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_4_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(80.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_3_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_4_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_5_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_1_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(8.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_2_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_3_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_4_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_5_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(41.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_6_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_7_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(58.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_8_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_9_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_10_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_11_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(91.666667).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn inset_full(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(100.).into();
-            {
-                let inset = self
-                    .computed_style()
-                    .inset
-                    .get_or_insert_with(Default::default);
-                inset.top = length;
-                inset.right = length;
-                inset.bottom = length;
-                inset.left = length;
-                self
-            }
-        }
-        fn w(mut self, width: impl Into<Length>) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().size.width = Some(width.into());
-            self
-        }
-        fn w_auto(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().size.width = Some(Length::Auto);
-            self
-        }
-        fn w_0(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(0.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_px(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(1.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_0_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.125).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.25).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.375).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.5).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.625).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_3(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.75).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_3_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.875).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_4(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.25).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_6(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.5).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_7(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.75).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_8(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_9(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.25).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_10(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.5).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_11(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.75).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_12(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_14(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.5).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_16(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(4.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_20(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(5.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_24(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(6.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_28(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(7.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_32(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(8.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_36(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(9.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_40(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(10.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_44(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(11.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_48(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(12.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_52(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(13.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_56(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(14.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_60(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(15.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_64(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(16.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_72(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(18.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_80(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(20.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_96(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(24.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_half(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_3_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(20.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(40.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_3_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(60.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_4_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(80.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_3_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_4_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_5_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_1_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(8.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_2_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_3_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_4_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_5_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(41.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_6_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_7_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(58.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_8_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_9_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_10_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_11_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(91.666667).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn w_full(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(100.).into();
-            {
-                self.declared_style().size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_0(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(0.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_px(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(1.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_0_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.125).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.25).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.375).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.5).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.625).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_3(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.75).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_3_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.875).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_4(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.25).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_6(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.5).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_7(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.75).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_8(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_9(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.25).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_10(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.5).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_11(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.75).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_12(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_14(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.5).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_16(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(4.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_20(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(5.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_24(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(6.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_28(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(7.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_32(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(8.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_36(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(9.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_40(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(10.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_44(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(11.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_48(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(12.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_52(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(13.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_56(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(14.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_60(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(15.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_64(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(16.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_72(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(18.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_80(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(20.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_96(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(24.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_half(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_3_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(20.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(40.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_3_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(60.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_4_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(80.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_3_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_4_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_5_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_1_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(8.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_2_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_3_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_4_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_5_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(41.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_6_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_7_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(58.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_8_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_9_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_10_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_11_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(91.666667).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn min_w_full(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(100.).into();
-            {
-                self.declared_style().min_size.width = Some(length);
-                self
-            }
-        }
-        fn h(mut self, height: impl Into<Length>) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().size.height = Some(height.into());
-            self
-        }
-        fn h_auto(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().size.height = Some(Length::Auto);
-            self
-        }
-        fn h_0(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Pixels(0.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_px(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Pixels(1.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_0_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.125).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.25).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.375).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.5).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.625).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_3(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.75).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_3_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(0.875).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_4(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(1.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(1.25).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_6(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(1.5).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_7(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(1.75).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_8(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(2.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_9(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(2.25).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_10(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(2.5).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_11(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(2.75).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_12(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(3.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_14(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(3.5).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_16(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(4.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_20(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(5.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_24(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(6.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_28(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(7.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_32(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(8.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_36(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(9.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_40(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(10.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_44(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(11.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_48(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(12.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_52(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(13.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_56(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(14.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_60(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(15.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_64(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(16.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_72(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(18.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_80(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(20.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_96(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Rems(24.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_half(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_3_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(20.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(40.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_3_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(60.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_4_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(80.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_3_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_4_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_5_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_1_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(8.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_2_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_3_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_4_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_5_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(41.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_6_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_7_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(58.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_8_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_9_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_10_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_11_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(91.666667).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn h_full(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let height = DefinedLength::Percent(100.).into();
-            {
-                self.declared_style().size.height = Some(height);
-                self
-            }
-        }
-        fn min_h_0(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(0.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_px(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Pixels(1.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_0_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.125).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.25).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.375).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.5).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.625).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_3(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.75).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_3_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(0.875).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_4(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_5(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.25).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_6(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.5).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_7(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(1.75).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_8(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_9(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.25).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_10(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.5).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_11(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(2.75).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_12(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_14(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(3.5).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_16(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(4.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_20(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(5.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_24(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(6.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_28(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(7.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_32(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(8.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_36(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(9.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_40(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(10.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_44(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(11.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_48(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(12.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_52(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(13.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_56(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(14.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_60(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(15.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_64(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(16.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_72(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(18.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_80(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(20.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_96(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Rems(24.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_half(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2_3rd(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_3_4th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(20.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(40.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_3_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(60.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_4_5th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(80.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_3_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_4_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_5_6th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_1_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(8.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_2_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(16.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_3_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(25.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_4_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(33.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_5_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(41.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_6_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(50.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_7_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(58.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_8_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(66.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_9_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(75.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_10_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(83.333333).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_11_12th(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(91.666667).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn min_h_full(mut self) -> Self
-        where
-            Self: Sized,
-        {
-            let length = DefinedLength::Percent(100.).into();
-            {
-                self.declared_style().min_size.height = Some(length);
-                self
-            }
-        }
-        fn hoverable(self) -> Hoverable<V, Self>
-        where
-            Self: Sized,
-        {
-            Hoverable::new(self)
-        }
-        fn fill(mut self, fill: impl Into<Fill>) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().fill = Some(Some(fill.into()));
-            self
-        }
-        fn text_color(mut self, color: impl Into<Hsla>) -> Self
-        where
-            Self: Sized,
-        {
-            self.declared_style().text_color = Some(Some(color.into()));
-            self
-        }
-    }
-    trait ElementObject<V> {
-        fn style(&mut self) -> &mut OptionalStyle;
-        fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>>;
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
-        ) -> Result<(NodeId, Box<dyn Any>)>;
-        fn paint(
-            &mut self,
-            layout: Layout<dyn Any>,
-            view: &mut V,
-            cx: &mut PaintContext<V>,
-        ) -> Result<()>;
-    }
-    impl<V: 'static, E: Element<V>> ElementObject<V> for E {
-        fn style(&mut self) -> &mut OptionalStyle {
-            Element::declared_style(self)
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
-            Element::handlers_mut(self)
-        }
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
-        ) -> Result<(NodeId, Box<dyn Any>)> {
-            let (node_id, layout) = self.layout(view, cx)?;
-            let layout = Box::new(layout) as Box<dyn Any>;
-            Ok((node_id, layout))
-        }
-        fn paint(
-            &mut self,
-            layout: Layout<dyn Any>,
-            view: &mut V,
-            cx: &mut PaintContext<V>,
-        ) -> Result<()> {
-            let layout = Layout {
-                from_engine: layout.from_engine,
-                from_element: layout.from_element.downcast_mut::<E::Layout>().unwrap(),
-            };
-            self.paint(layout, view, cx)
-        }
-    }
-    /// A dynamically typed element.
-    pub struct AnyElement<V> {
-        element: Box<dyn ElementObject<V>>,
-        layout: Option<(NodeId, Box<dyn Any>)>,
-    }
-    impl<V: 'static> AnyElement<V> {
-        pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
-            let pushed_text_style = self.push_text_style(cx);
-            let (node_id, layout) = self.element.layout(view, cx)?;
-            self.layout = Some((node_id, layout));
-            if pushed_text_style {
-                cx.pop_text_style();
-            }
-            Ok(node_id)
-        }
-        pub fn push_text_style(&mut self, cx: &mut impl RenderContext) -> bool {
-            let text_style = self.element.style().text_style();
-            if let Some(text_style) = text_style {
-                let mut current_text_style = cx.text_style();
-                text_style.apply(&mut current_text_style);
-                cx.push_text_style(current_text_style);
-                true
-            } else {
-                false
-            }
-        }
-        pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
-            let pushed_text_style = self.push_text_style(cx);
-            let (layout_node_id, element_layout) =
-                self.layout.as_mut().expect("paint called before layout");
-            let layout = Layout {
-                from_engine: cx
-                    .layout_engine()
-                    .unwrap()
-                    .computed_layout(*layout_node_id)
-                    .expect("make sure you're using this within a gpui2 adapter element"),
-                from_element: element_layout.as_mut(),
-            };
-            let style = self.element.style();
-            let fill_color = style.fill.flatten().and_then(|fill| fill.color());
-            if let Some(fill_color) = fill_color {
-                cx.scene.push_quad(gpui::scene::Quad {
-                    bounds: layout.from_engine.bounds,
-                    background: Some(fill_color.into()),
-                    border: Default::default(),
-                    corner_radii: Default::default(),
-                });
-            }
-            for event_handler in self.element.handlers_mut().iter().cloned() {
-                let EngineLayout { order, bounds } = layout.from_engine;
-                let view_id = cx.view_id();
-                let view_event_handler = event_handler.handler.clone();
-                cx.scene
-                    .interactive_regions
-                    .push(gpui::scene::InteractiveRegion {
-                        order,
-                        bounds,
-                        outside_bounds: event_handler.outside_bounds,
-                        event_handler: Rc::new(move |view, event, window_cx, view_id| {
-                            let mut view_context = ViewContext::mutable(window_cx, view_id);
-                            let mut event_context = EventContext::new(&mut view_context);
-                            view_event_handler(
-                                view.downcast_mut().unwrap(),
-                                event,
-                                &mut event_context,
-                            );
-                        }),
-                        event_type: event_handler.event_type,
-                        view_id,
-                    });
-            }
-            self.element.paint(layout, view, cx)?;
-            if pushed_text_style {
-                cx.pop_text_style();
-            }
-            Ok(())
-        }
-    }
-    impl<V: 'static> Element<V> for AnyElement<V> {
-        type Layout = ();
-        fn declared_style(&mut self) -> &mut OptionalStyle {
-            self.element.style()
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
-            self.element.handlers_mut()
-        }
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
-        ) -> Result<(NodeId, Self::Layout)> {
-            Ok((self.layout(view, cx)?, ()))
-        }
-        fn paint(
-            &mut self,
-            layout: Layout<()>,
-            view: &mut V,
-            cx: &mut PaintContext<V>,
-        ) -> Result<()> {
-            self.paint(view, cx)
-        }
-    }
-    pub trait IntoElement<V: 'static> {
-        type Element: Element<V>;
-        fn into_element(self) -> Self::Element;
-        fn into_any_element(self) -> AnyElement<V>
-        where
-            Self: Sized,
-        {
-            self.into_element().into_any()
-        }
-    }
-}
-mod frame {
-    use crate::{
-        element::{
-            AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId,
-            PaintContext,
-        },
-        style::{OptionalStyle, Style},
-    };
-    use anyhow::{anyhow, Result};
-    use gpui::LayoutNodeId;
-    use gpui_macros::IntoElement;
-    #[element_crate = "crate"]
-    pub struct Frame<V: 'static> {
-        style: OptionalStyle,
-        handlers: Vec<EventHandler<V>>,
-        children: Vec<AnyElement<V>>,
-    }
-    impl<V: 'static> crate::element::IntoElement<V> for Frame<V> {
-        type Element = Self;
-        fn into_element(self) -> Self {
-            self
-        }
-    }
-    pub fn frame<V>() -> Frame<V> {
-        Frame {
-            style: OptionalStyle::default(),
-            handlers: Vec::new(),
-            children: Vec::new(),
-        }
-    }
-    impl<V: 'static> Element<V> for Frame<V> {
-        type Layout = ();
-        fn declared_style(&mut self) -> &mut OptionalStyle {
-            &mut self.style
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
-            &mut self.handlers
-        }
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
-        ) -> Result<(NodeId, Self::Layout)> {
-            let child_layout_node_ids = self
-                .children
-                .iter_mut()
-                .map(|child| child.layout(view, cx))
-                .collect::<Result<Vec<LayoutNodeId>>>()?;
-            let rem_size = cx.rem_pixels();
-            let style: Style = self.style.into();
-            let node_id = cx
-                .layout_engine()
-                .ok_or_else(|| {
-                    ::anyhow::__private::must_use({
-                        let error =
-                            ::anyhow::__private::format_err(format_args!("no layout engine"));
-                        error
-                    })
-                })?
-                .add_node(style.to_taffy(rem_size), child_layout_node_ids)?;
-            Ok((node_id, ()))
-        }
-        fn paint(
-            &mut self,
-            layout: Layout<()>,
-            view: &mut V,
-            cx: &mut PaintContext<V>,
-        ) -> Result<()> {
-            for child in &mut self.children {
-                child.paint(view, cx)?;
-            }
-            Ok(())
-        }
-    }
-    impl<V: 'static> Frame<V> {
-        pub fn child(mut self, child: impl IntoElement<V>) -> Self {
-            self.children.push(child.into_any_element());
-            self
-        }
-        pub fn children<I, E>(mut self, children: I) -> Self
-        where
-            I: IntoIterator<Item = E>,
-            E: IntoElement<V>,
-        {
-            self.children
-                .extend(children.into_iter().map(|e| e.into_any_element()));
-            self
-        }
-    }
-}
-mod hoverable {
-    use crate::{
-        element::Element,
-        style::{OptionalStyle, Style},
-    };
-    use gpui::{
-        geometry::{rect::RectF, vector::Vector2F},
-        scene::MouseMove,
-        EngineLayout,
-    };
-    use std::{cell::Cell, marker::PhantomData, rc::Rc};
-    pub struct Hoverable<V, E> {
-        hover_style: OptionalStyle,
-        computed_style: Option<Style>,
-        view_type: PhantomData<V>,
-        child: E,
-    }
-    impl<V, E> Hoverable<V, E> {
-        pub fn new(child: E) -> Self {
-            Self {
-                hover_style: OptionalStyle::default(),
-                computed_style: None,
-                view_type: PhantomData,
-                child,
-            }
-        }
-    }
-    impl<V: 'static, E: Element<V>> Element<V> for Hoverable<V, E> {
-        type Layout = E::Layout;
-        fn declared_style(&mut self) -> &mut OptionalStyle {
-            &mut self.hover_style
-        }
-        fn computed_style(&mut self) -> &OptionalStyle {
-            ::core::panicking::panic("not yet implemented")
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<crate::element::EventHandler<V>> {
-            self.child.handlers_mut()
-        }
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut gpui::LayoutContext<V>,
-        ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
-            self.child.layout(view, cx)
-        }
-        fn paint<'a>(
-            &mut self,
-            layout: crate::element::Layout<Self::Layout>,
-            view: &mut V,
-            cx: &mut crate::element::PaintContext<V>,
-        ) -> anyhow::Result<()> {
-            let EngineLayout { bounds, order } = layout.from_engine;
-            let window_bounds = RectF::new(Vector2F::zero(), cx.window_size());
-            let was_hovered = Rc::new(Cell::new(false));
-            self.child.paint(layout, view, cx)?;
-            cx.draw_interactive_region(
-                order,
-                window_bounds,
-                false,
-                move |view, event: &MouseMove, cx| {
-                    let is_hovered = bounds.contains_point(cx.mouse_position());
-                    if is_hovered != was_hovered.get() {
-                        was_hovered.set(is_hovered);
-                        cx.repaint();
-                    }
-                },
-            );
-            Ok(())
-        }
-    }
-}
-mod paint_context {
-    use derive_more::{Deref, DerefMut};
-    use gpui::{geometry::rect::RectF, EventContext, RenderContext, ViewContext};
-    pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
-    use std::{any::TypeId, rc::Rc};
-    pub use taffy::tree::NodeId;
-    pub struct PaintContext<'a, 'b, 'c, 'd, V> {
-        #[deref]
-        #[deref_mut]
-        pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
-        pub(crate) scene: &'d mut gpui::SceneBuilder,
-    }
-    impl<'a, 'b, 'c, 'd, V> ::core::ops::Deref for PaintContext<'a, 'b, 'c, 'd, V> {
-        type Target = &'d mut LegacyPaintContext<'a, 'b, 'c, V>;
-        #[inline]
-        fn deref(&self) -> &Self::Target {
-            &self.legacy_cx
-        }
-    }
-    impl<'a, 'b, 'c, 'd, V> ::core::ops::DerefMut for PaintContext<'a, 'b, 'c, 'd, V> {
-        #[inline]
-        fn deref_mut(&mut self) -> &mut Self::Target {
-            &mut self.legacy_cx
-        }
-    }
-    impl<V> RenderContext for PaintContext<'_, '_, '_, '_, V> {
-        fn text_style(&self) -> gpui::fonts::TextStyle {
-            self.legacy_cx.text_style()
-        }
-        fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
-            self.legacy_cx.push_text_style(style)
-        }
-        fn pop_text_style(&mut self) {
-            self.legacy_cx.pop_text_style()
-        }
-    }
-    impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
-        pub fn new(
-            legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
-            scene: &'d mut gpui::SceneBuilder,
-        ) -> Self {
-            Self { legacy_cx, scene }
-        }
-        pub fn draw_interactive_region<E: 'static>(
-            &mut self,
-            order: u32,
-            bounds: RectF,
-            outside_bounds: bool,
-            event_handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
-        ) {
-            self.scene
-                .interactive_regions
-                .push(gpui::scene::InteractiveRegion {
-                    order,
-                    bounds,
-                    outside_bounds,
-                    event_handler: Rc::new(move |view, event, window_cx, view_id| {
-                        let mut view_context = ViewContext::mutable(window_cx, view_id);
-                        let mut event_context = EventContext::new(&mut view_context);
-                        event_handler(
-                            view.downcast_mut().unwrap(),
-                            event.downcast_ref().unwrap(),
-                            &mut event_context,
-                        );
-                    }),
-                    event_type: TypeId::of::<E>(),
-                    view_id: self.view_id(),
-                });
-        }
-    }
-}
-mod style {
-    use crate::color::Hsla;
-    use gpui::geometry::{
-        DefinedLength, Edges, Length, OptionalEdges, OptionalPoint, OptionalSize, Point, Size,
-    };
-    use optional::Optional;
-    pub use taffy::style::{
-        AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
-        Overflow, Position,
-    };
-    pub struct Style {
-        /// What layout strategy should be used?
-        pub display: Display,
-        /// How children overflowing their container should affect layout
-        #[optional]
-        pub overflow: Point<Overflow>,
-        /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
-        pub scrollbar_width: f32,
-        /// What should the `position` value of this struct use as a base offset?
-        pub position: Position,
-        /// How should the position of this element be tweaked relative to the layout defined?
-        pub inset: Edges<Length>,
-        /// Sets the initial size of the item
-        #[optional]
-        pub size: Size<Length>,
-        /// Controls the minimum size of the item
-        #[optional]
-        pub min_size: Size<Length>,
-        /// Controls the maximum size of the item
-        #[optional]
-        pub max_size: Size<Length>,
-        /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
-        pub aspect_ratio: Option<f32>,
-        /// How large should the margin be on each side?
-        #[optional]
-        pub margin: Edges<Length>,
-        /// How large should the padding be on each side?
-        pub padding: Edges<DefinedLength>,
-        /// How large should the border be on each side?
-        pub border: Edges<DefinedLength>,
-        /// How this node's children aligned in the cross/block axis?
-        pub align_items: Option<AlignItems>,
-        /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
-        pub align_self: Option<AlignSelf>,
-        /// How should content contained within this item be aligned in the cross/block axis
-        pub align_content: Option<AlignContent>,
-        /// How should contained within this item be aligned in the main/inline axis
-        pub justify_content: Option<JustifyContent>,
-        /// How large should the gaps between items in a flex container be?
-        pub gap: Size<DefinedLength>,
-        /// Which direction does the main axis flow in?
-        pub flex_direction: FlexDirection,
-        /// Should elements wrap, or stay in a single line?
-        pub flex_wrap: FlexWrap,
-        /// Sets the initial main axis size of the item
-        pub flex_basis: Length,
-        /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
-        pub flex_grow: f32,
-        /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
-        pub flex_shrink: f32,
-        /// The fill color of this element
-        pub fill: Option<Fill>,
-        /// The color of text within this element. Cascades to children unless overridden.
-        pub text_color: Option<Hsla>,
-    }
-    #[automatically_derived]
-    impl ::core::clone::Clone for Style {
-        #[inline]
-        fn clone(&self) -> Style {
-            Style {
-                display: ::core::clone::Clone::clone(&self.display),
-                overflow: ::core::clone::Clone::clone(&self.overflow),
-                scrollbar_width: ::core::clone::Clone::clone(&self.scrollbar_width),
-                position: ::core::clone::Clone::clone(&self.position),
-                inset: ::core::clone::Clone::clone(&self.inset),
-                size: ::core::clone::Clone::clone(&self.size),
-                min_size: ::core::clone::Clone::clone(&self.min_size),
-                max_size: ::core::clone::Clone::clone(&self.max_size),
-                aspect_ratio: ::core::clone::Clone::clone(&self.aspect_ratio),
-                margin: ::core::clone::Clone::clone(&self.margin),
-                padding: ::core::clone::Clone::clone(&self.padding),
-                border: ::core::clone::Clone::clone(&self.border),
-                align_items: ::core::clone::Clone::clone(&self.align_items),
-                align_self: ::core::clone::Clone::clone(&self.align_self),
-                align_content: ::core::clone::Clone::clone(&self.align_content),
-                justify_content: ::core::clone::Clone::clone(&self.justify_content),
-                gap: ::core::clone::Clone::clone(&self.gap),
-                flex_direction: ::core::clone::Clone::clone(&self.flex_direction),
-                flex_wrap: ::core::clone::Clone::clone(&self.flex_wrap),
-                flex_basis: ::core::clone::Clone::clone(&self.flex_basis),
-                flex_grow: ::core::clone::Clone::clone(&self.flex_grow),
-                flex_shrink: ::core::clone::Clone::clone(&self.flex_shrink),
-                fill: ::core::clone::Clone::clone(&self.fill),
-                text_color: ::core::clone::Clone::clone(&self.text_color),
-            }
-        }
-    }
-    pub struct OptionalStyle {
-        pub display: Option<Display>,
-        pub overflow: OptionalPoint<Overflow>,
-        pub scrollbar_width: Option<f32>,
-        pub position: Option<Position>,
-        pub inset: Option<Edges<Length>>,
-        pub size: OptionalSize<Length>,
-        pub min_size: OptionalSize<Length>,
-        pub max_size: OptionalSize<Length>,
-        pub aspect_ratio: Option<Option<f32>>,
-        pub margin: OptionalEdges<Length>,
-        pub padding: Option<Edges<DefinedLength>>,
-        pub border: Option<Edges<DefinedLength>>,
-        pub align_items: Option<Option<AlignItems>>,
-        pub align_self: Option<Option<AlignSelf>>,
-        pub align_content: Option<Option<AlignContent>>,
-        pub justify_content: Option<Option<JustifyContent>>,
-        pub gap: Option<Size<DefinedLength>>,
-        pub flex_direction: Option<FlexDirection>,
-        pub flex_wrap: Option<FlexWrap>,
-        pub flex_basis: Option<Length>,
-        pub flex_grow: Option<f32>,
-        pub flex_shrink: Option<f32>,
-        pub fill: Option<Option<Fill>>,
-        pub text_color: Option<Option<Hsla>>,
-    }
-    #[automatically_derived]
-    impl ::core::default::Default for OptionalStyle {
-        #[inline]
-        fn default() -> OptionalStyle {
-            OptionalStyle {
-                display: ::core::default::Default::default(),
-                overflow: ::core::default::Default::default(),
-                scrollbar_width: ::core::default::Default::default(),
-                position: ::core::default::Default::default(),
-                inset: ::core::default::Default::default(),
-                size: ::core::default::Default::default(),
-                min_size: ::core::default::Default::default(),
-                max_size: ::core::default::Default::default(),
-                aspect_ratio: ::core::default::Default::default(),
-                margin: ::core::default::Default::default(),
-                padding: ::core::default::Default::default(),
-                border: ::core::default::Default::default(),
-                align_items: ::core::default::Default::default(),
-                align_self: ::core::default::Default::default(),
-                align_content: ::core::default::Default::default(),
-                justify_content: ::core::default::Default::default(),
-                gap: ::core::default::Default::default(),
-                flex_direction: ::core::default::Default::default(),
-                flex_wrap: ::core::default::Default::default(),
-                flex_basis: ::core::default::Default::default(),
-                flex_grow: ::core::default::Default::default(),
-                flex_shrink: ::core::default::Default::default(),
-                fill: ::core::default::Default::default(),
-                text_color: ::core::default::Default::default(),
-            }
-        }
-    }
-    #[automatically_derived]
-    impl ::core::clone::Clone for OptionalStyle {
-        #[inline]
-        fn clone(&self) -> OptionalStyle {
-            OptionalStyle {
-                display: ::core::clone::Clone::clone(&self.display),
-                overflow: ::core::clone::Clone::clone(&self.overflow),
-                scrollbar_width: ::core::clone::Clone::clone(&self.scrollbar_width),
-                position: ::core::clone::Clone::clone(&self.position),
-                inset: ::core::clone::Clone::clone(&self.inset),
-                size: ::core::clone::Clone::clone(&self.size),
-                min_size: ::core::clone::Clone::clone(&self.min_size),
-                max_size: ::core::clone::Clone::clone(&self.max_size),
-                aspect_ratio: ::core::clone::Clone::clone(&self.aspect_ratio),
-                margin: ::core::clone::Clone::clone(&self.margin),
-                padding: ::core::clone::Clone::clone(&self.padding),
-                border: ::core::clone::Clone::clone(&self.border),
-                align_items: ::core::clone::Clone::clone(&self.align_items),
-                align_self: ::core::clone::Clone::clone(&self.align_self),
-                align_content: ::core::clone::Clone::clone(&self.align_content),
-                justify_content: ::core::clone::Clone::clone(&self.justify_content),
-                gap: ::core::clone::Clone::clone(&self.gap),
-                flex_direction: ::core::clone::Clone::clone(&self.flex_direction),
-                flex_wrap: ::core::clone::Clone::clone(&self.flex_wrap),
-                flex_basis: ::core::clone::Clone::clone(&self.flex_basis),
-                flex_grow: ::core::clone::Clone::clone(&self.flex_grow),
-                flex_shrink: ::core::clone::Clone::clone(&self.flex_shrink),
-                fill: ::core::clone::Clone::clone(&self.fill),
-                text_color: ::core::clone::Clone::clone(&self.text_color),
-            }
-        }
-    }
-    impl Optional for OptionalStyle {
-        type Base = Style;
-        fn assign(&self, base: &mut Self::Base) {
-            if let Some(value) = self.display.clone() {
-                base.display = value;
-            }
-            if let Some(value) = self.overflow.clone() {
-                base.overflow = value;
-            }
-            if let Some(value) = self.scrollbar_width.clone() {
-                base.scrollbar_width = value;
-            }
-            if let Some(value) = self.position.clone() {
-                base.position = value;
-            }
-            if let Some(value) = self.inset.clone() {
-                base.inset = value;
-            }
-            if let Some(value) = self.size.clone() {
-                base.size = value;
-            }
-            if let Some(value) = self.min_size.clone() {
-                base.min_size = value;
-            }
-            if let Some(value) = self.max_size.clone() {
-                base.max_size = value;
-            }
-            if let Some(value) = self.aspect_ratio.clone() {
-                base.aspect_ratio = value;
-            }
-            if let Some(value) = self.margin.clone() {
-                base.margin = value;
-            }
-            if let Some(value) = self.padding.clone() {
-                base.padding = value;
-            }
-            if let Some(value) = self.border.clone() {
-                base.border = value;
-            }
-            if let Some(value) = self.align_items.clone() {
-                base.align_items = value;
-            }
-            if let Some(value) = self.align_self.clone() {
-                base.align_self = value;
-            }
-            if let Some(value) = self.align_content.clone() {
-                base.align_content = value;
-            }
-            if let Some(value) = self.justify_content.clone() {
-                base.justify_content = value;
-            }
-            if let Some(value) = self.gap.clone() {
-                base.gap = value;
-            }
-            if let Some(value) = self.flex_direction.clone() {
-                base.flex_direction = value;
-            }
-            if let Some(value) = self.flex_wrap.clone() {
-                base.flex_wrap = value;
-            }
-            if let Some(value) = self.flex_basis.clone() {
-                base.flex_basis = value;
-            }
-            if let Some(value) = self.flex_grow.clone() {
-                base.flex_grow = value;
-            }
-            if let Some(value) = self.flex_shrink.clone() {
-                base.flex_shrink = value;
-            }
-            if let Some(value) = self.fill.clone() {
-                base.fill = value;
-            }
-            if let Some(value) = self.text_color.clone() {
-                base.text_color = value;
-            }
-        }
-    }
-    impl From<OptionalStyle> for Style
-    where
-        Style: Default,
-    {
-        fn from(wrapper: OptionalStyle) -> Self {
-            let mut base = Self::default();
-            wrapper.assign(&mut base);
-            base
-        }
-    }
-    impl Style {
-        pub const DEFAULT: Style = Style {
-            display: Display::DEFAULT,
-            overflow: Point {
-                x: Overflow::Visible,
-                y: Overflow::Visible,
-            },
-            scrollbar_width: 0.0,
-            position: Position::Relative,
-            inset: Edges::auto(),
-            margin: Edges::<Length>::zero(),
-            padding: Edges::<DefinedLength>::zero(),
-            border: Edges::<DefinedLength>::zero(),
-            size: Size::auto(),
-            min_size: Size::auto(),
-            max_size: Size::auto(),
-            aspect_ratio: None,
-            gap: Size::zero(),
-            align_items: None,
-            align_self: None,
-            align_content: None,
-            justify_content: None,
-            flex_direction: FlexDirection::Row,
-            flex_wrap: FlexWrap::NoWrap,
-            flex_grow: 0.0,
-            flex_shrink: 1.0,
-            flex_basis: Length::Auto,
-            fill: None,
-            text_color: None,
-        };
-        pub fn new() -> Self {
-            Self::DEFAULT.clone()
-        }
-        pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
-            taffy::style::Style {
-                display: self.display,
-                overflow: self.overflow.clone().into(),
-                scrollbar_width: self.scrollbar_width,
-                position: self.position,
-                inset: self.inset.to_taffy(rem_size),
-                size: self.size.to_taffy(rem_size),
-                min_size: self.min_size.to_taffy(rem_size),
-                max_size: self.max_size.to_taffy(rem_size),
-                aspect_ratio: self.aspect_ratio,
-                margin: self.margin.to_taffy(rem_size),
-                padding: self.padding.to_taffy(rem_size),
-                border: self.border.to_taffy(rem_size),
-                align_items: self.align_items,
-                align_self: self.align_self,
-                align_content: self.align_content,
-                justify_content: self.justify_content,
-                gap: self.gap.to_taffy(rem_size),
-                flex_direction: self.flex_direction,
-                flex_wrap: self.flex_wrap,
-                flex_basis: self.flex_basis.to_taffy(rem_size).into(),
-                flex_grow: self.flex_grow,
-                flex_shrink: self.flex_shrink,
-                ..Default::default()
-            }
-        }
-    }
-    impl Default for Style {
-        fn default() -> Self {
-            Self::DEFAULT.clone()
-        }
-    }
-    impl OptionalStyle {
-        pub fn text_style(&self) -> Option<OptionalTextStyle> {
-            self.text_color.map(|color| OptionalTextStyle { color })
-        }
-    }
-    pub struct OptionalTextStyle {
-        color: Option<Hsla>,
-    }
-    impl OptionalTextStyle {
-        pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
-            if let Some(color) = self.color {
-                style.color = color.into();
-            }
-        }
-    }
-    pub enum Fill {
-        Color(Hsla),
-    }
-    #[automatically_derived]
-    impl ::core::clone::Clone for Fill {
-        #[inline]
-        fn clone(&self) -> Fill {
-            match self {
-                Fill::Color(__self_0) => Fill::Color(::core::clone::Clone::clone(__self_0)),
-            }
-        }
-    }
-    impl Fill {
-        pub fn color(&self) -> Option<Hsla> {
-            match self {
-                Fill::Color(color) => Some(*color),
-            }
-        }
-    }
-    impl Default for Fill {
-        fn default() -> Self {
-            Self::Color(Hsla::default())
-        }
-    }
-    impl From<Hsla> for Fill {
-        fn from(color: Hsla) -> Self {
-            Self::Color(color)
-        }
-    }
-}
-mod text {
-    use crate::{
-        element::{Element, ElementMetadata, EventHandler, IntoElement},
-        style::Style,
-    };
-    use gpui::{geometry::Size, text_layout::LineLayout, RenderContext};
-    use parking_lot::Mutex;
-    use std::sync::Arc;
-    impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
-        type Element = Text<V>;
-        fn into_element(self) -> Self::Element {
-            Text {
-                text: self.into(),
-                metadata: Default::default(),
-            }
-        }
-    }
-    pub struct Text<V> {
-        text: ArcCow<'static, str>,
-        metadata: ElementMetadata<V>,
-    }
-    impl<V: 'static> Element<V> for Text<V> {
-        type Layout = Arc<Mutex<Option<TextLayout>>>;
-        fn declared_style(&mut self) -> &mut crate::style::OptionalStyle {
-            &mut self.metadata.style
-        }
-        fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut gpui::LayoutContext<V>,
-        ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
-            let rem_size = cx.rem_pixels();
-            let fonts = cx.platform().fonts();
-            let text_style = cx.text_style();
-            let line_height = cx.font_cache().line_height(text_style.font_size);
-            let layout_engine = cx.layout_engine().expect("no layout engine present");
-            let text = self.text.clone();
-            let layout = Arc::new(Mutex::new(None));
-            let style: Style = self.metadata.style.into();
-            let node_id = layout_engine.add_measured_node(style.to_taffy(rem_size), {
-                let layout = layout.clone();
-                move |params| {
-                    let line_layout = fonts.layout_line(
-                        text.as_ref(),
-                        text_style.font_size,
-                        &[(text.len(), text_style.to_run())],
-                    );
-                    let size = Size {
-                        width: line_layout.width,
-                        height: line_height,
-                    };
-                    layout.lock().replace(TextLayout {
-                        line_layout: Arc::new(line_layout),
-                        line_height,
-                    });
-                    size
-                }
-            })?;
-            Ok((node_id, layout))
-        }
-        fn paint<'a>(
-            &mut self,
-            layout: crate::element::Layout<Arc<Mutex<Option<TextLayout>>>>,
-            view: &mut V,
-            cx: &mut crate::element::PaintContext<V>,
-        ) -> anyhow::Result<()> {
-            let element_layout_lock = layout.from_element.lock();
-            let element_layout = element_layout_lock
-                .as_ref()
-                .expect("layout has not been performed");
-            let line_layout = element_layout.line_layout.clone();
-            let line_height = element_layout.line_height;
-            drop(element_layout_lock);
-            let text_style = cx.text_style();
-            let line = gpui::text_layout::Line::new(
-                line_layout,
-                &[(self.text.len(), text_style.to_run())],
-            );
-            line.paint(
-                cx.scene,
-                layout.from_engine.bounds.origin(),
-                layout.from_engine.bounds,
-                line_height,
-                cx.legacy_cx,
-            );
-            Ok(())
-        }
-        fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
-            &mut self.metadata.handlers
-        }
-    }
-    pub struct TextLayout {
-        line_layout: Arc<LineLayout>,
-        line_height: f32,
-    }
-    pub enum ArcCow<'a, T: ?Sized> {
-        Borrowed(&'a T),
-        Owned(Arc<T>),
-    }
-    impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
-        fn clone(&self) -> Self {
-            match self {
-                Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
-                Self::Owned(owned) => Self::Owned(owned.clone()),
-            }
-        }
-    }
-    impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
-        fn from(s: &'a T) -> Self {
-            Self::Borrowed(s)
-        }
-    }
-    impl<T> From<Arc<T>> for ArcCow<'_, T> {
-        fn from(s: Arc<T>) -> Self {
-            Self::Owned(s)
-        }
-    }
-    impl From<String> for ArcCow<'_, str> {
-        fn from(value: String) -> Self {
-            Self::Owned(value.into())
-        }
-    }
-    impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
-        type Target = T;
-        fn deref(&self) -> &Self::Target {
-            match self {
-                ArcCow::Borrowed(s) => s,
-                ArcCow::Owned(s) => s.as_ref(),
-            }
-        }
-    }
-    impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
-        fn as_ref(&self) -> &T {
-            match self {
-                ArcCow::Borrowed(borrowed) => borrowed,
-                ArcCow::Owned(owned) => owned.as_ref(),
-            }
-        }
-    }
-}
-mod themes {
-    use crate::color::{Hsla, Lerp};
-    use std::ops::Range;
-    pub mod rose_pine {
-        use crate::{
-            color::{hsla, rgb, Hsla},
-            ThemeColors,
-        };
-        use std::ops::Range;
-        pub struct RosePineThemes {
-            pub default: RosePinePalette,
-            pub dawn: RosePinePalette,
-            pub moon: RosePinePalette,
-        }
-        pub struct RosePinePalette {
-            pub base: Hsla,
-            pub surface: Hsla,
-            pub overlay: Hsla,
-            pub muted: Hsla,
-            pub subtle: Hsla,
-            pub text: Hsla,
-            pub love: Hsla,
-            pub gold: Hsla,
-            pub rose: Hsla,
-            pub pine: Hsla,
-            pub foam: Hsla,
-            pub iris: Hsla,
-            pub highlight_low: Hsla,
-            pub highlight_med: Hsla,
-            pub highlight_high: Hsla,
-        }
-        #[automatically_derived]
-        impl ::core::clone::Clone for RosePinePalette {
-            #[inline]
-            fn clone(&self) -> RosePinePalette {
-                let _: ::core::clone::AssertParamIsClone<Hsla>;
-                *self
-            }
-        }
-        #[automatically_derived]
-        impl ::core::marker::Copy for RosePinePalette {}
-        #[automatically_derived]
-        impl ::core::fmt::Debug for RosePinePalette {
-            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-                let names: &'static _ = &[
-                    "base",
-                    "surface",
-                    "overlay",
-                    "muted",
-                    "subtle",
-                    "text",
-                    "love",
-                    "gold",
-                    "rose",
-                    "pine",
-                    "foam",
-                    "iris",
-                    "highlight_low",
-                    "highlight_med",
-                    "highlight_high",
-                ];
-                let values: &[&dyn::core::fmt::Debug] = &[
-                    &self.base,
-                    &self.surface,
-                    &self.overlay,
-                    &self.muted,
-                    &self.subtle,
-                    &self.text,
-                    &self.love,
-                    &self.gold,
-                    &self.rose,
-                    &self.pine,
-                    &self.foam,
-                    &self.iris,
-                    &self.highlight_low,
-                    &self.highlight_med,
-                    &&self.highlight_high,
-                ];
-                ::core::fmt::Formatter::debug_struct_fields_finish(
-                    f,
-                    "RosePinePalette",
-                    names,
-                    values,
-                )
-            }
-        }
-        impl RosePinePalette {
-            pub fn default() -> RosePinePalette {
-                RosePinePalette {
-                    base: rgb(0x191724),
-                    surface: rgb(0x1f1d2e),
-                    overlay: rgb(0x26233a),
-                    muted: rgb(0x6e6a86),
-                    subtle: rgb(0x908caa),
-                    text: rgb(0xe0def4),
-                    love: rgb(0xeb6f92),
-                    gold: rgb(0xf6c177),
-                    rose: rgb(0xebbcba),
-                    pine: rgb(0x31748f),
-                    foam: rgb(0x9ccfd8),
-                    iris: rgb(0xc4a7e7),
-                    highlight_low: rgb(0x21202e),
-                    highlight_med: rgb(0x403d52),
-                    highlight_high: rgb(0x524f67),
-                }
-            }
-            pub fn moon() -> RosePinePalette {
-                RosePinePalette {
-                    base: rgb(0x232136),
-                    surface: rgb(0x2a273f),
-                    overlay: rgb(0x393552),
-                    muted: rgb(0x6e6a86),
-                    subtle: rgb(0x908caa),
-                    text: rgb(0xe0def4),
-                    love: rgb(0xeb6f92),
-                    gold: rgb(0xf6c177),
-                    rose: rgb(0xea9a97),
-                    pine: rgb(0x3e8fb0),
-                    foam: rgb(0x9ccfd8),
-                    iris: rgb(0xc4a7e7),
-                    highlight_low: rgb(0x2a283e),
-                    highlight_med: rgb(0x44415a),
-                    highlight_high: rgb(0x56526e),
-                }
-            }
-            pub fn dawn() -> RosePinePalette {
-                RosePinePalette {
-                    base: rgb(0xfaf4ed),
-                    surface: rgb(0xfffaf3),
-                    overlay: rgb(0xf2e9e1),
-                    muted: rgb(0x9893a5),
-                    subtle: rgb(0x797593),
-                    text: rgb(0x575279),
-                    love: rgb(0xb4637a),
-                    gold: rgb(0xea9d34),
-                    rose: rgb(0xd7827e),
-                    pine: rgb(0x286983),
-                    foam: rgb(0x56949f),
-                    iris: rgb(0x907aa9),
-                    highlight_low: rgb(0xf4ede8),
-                    highlight_med: rgb(0xdfdad9),
-                    highlight_high: rgb(0xcecacd),
-                }
-            }
-        }
-        pub fn default() -> ThemeColors {
-            theme_colors(&RosePinePalette::default())
-        }
-        pub fn moon() -> ThemeColors {
-            theme_colors(&RosePinePalette::moon())
-        }
-        pub fn dawn() -> ThemeColors {
-            theme_colors(&RosePinePalette::dawn())
-        }
-        fn theme_colors(p: &RosePinePalette) -> ThemeColors {
-            ThemeColors {
-                base: scale_sl(p.base, (0.8, 0.8), (1.2, 1.2)),
-                surface: scale_sl(p.surface, (0.8, 0.8), (1.2, 1.2)),
-                overlay: scale_sl(p.overlay, (0.8, 0.8), (1.2, 1.2)),
-                muted: scale_sl(p.muted, (0.8, 0.8), (1.2, 1.2)),
-                subtle: scale_sl(p.subtle, (0.8, 0.8), (1.2, 1.2)),
-                text: scale_sl(p.text, (0.8, 0.8), (1.2, 1.2)),
-                highlight_low: scale_sl(p.highlight_low, (0.8, 0.8), (1.2, 1.2)),
-                highlight_med: scale_sl(p.highlight_med, (0.8, 0.8), (1.2, 1.2)),
-                highlight_high: scale_sl(p.highlight_high, (0.8, 0.8), (1.2, 1.2)),
-                success: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
-                warning: scale_sl(p.gold, (0.8, 0.8), (1.2, 1.2)),
-                error: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
-                inserted: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
-                deleted: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
-                modified: scale_sl(p.rose, (0.8, 0.8), (1.2, 1.2)),
-            }
-        }
-        /// Produces a range by multiplying the saturation and lightness of the base color by the given
-        /// start and end factors.
-        fn scale_sl(
-            base: Hsla,
-            (start_s, start_l): (f32, f32),
-            (end_s, end_l): (f32, f32),
-        ) -> Range<Hsla> {
-            let start = hsla(base.h, base.s * start_s, base.l * start_l, base.a);
-            let end = hsla(base.h, base.s * end_s, base.l * end_l, base.a);
-            Range { start, end }
-        }
-    }
-    pub struct ThemeColors {
-        pub base: Range<Hsla>,
-        pub surface: Range<Hsla>,
-        pub overlay: Range<Hsla>,
-        pub muted: Range<Hsla>,
-        pub subtle: Range<Hsla>,
-        pub text: Range<Hsla>,
-        pub highlight_low: Range<Hsla>,
-        pub highlight_med: Range<Hsla>,
-        pub highlight_high: Range<Hsla>,
-        pub success: Range<Hsla>,
-        pub warning: Range<Hsla>,
-        pub error: Range<Hsla>,
-        pub inserted: Range<Hsla>,
-        pub deleted: Range<Hsla>,
-        pub modified: Range<Hsla>,
-    }
-    impl ThemeColors {
-        pub fn base(&self, level: f32) -> Hsla {
-            self.base.lerp(level)
-        }
-        pub fn surface(&self, level: f32) -> Hsla {
-            self.surface.lerp(level)
-        }
-        pub fn overlay(&self, level: f32) -> Hsla {
-            self.overlay.lerp(level)
-        }
-        pub fn muted(&self, level: f32) -> Hsla {
-            self.muted.lerp(level)
-        }
-        pub fn subtle(&self, level: f32) -> Hsla {
-            self.subtle.lerp(level)
-        }
-        pub fn text(&self, level: f32) -> Hsla {
-            self.text.lerp(level)
-        }
-        pub fn highlight_low(&self, level: f32) -> Hsla {
-            self.highlight_low.lerp(level)
-        }
-        pub fn highlight_med(&self, level: f32) -> Hsla {
-            self.highlight_med.lerp(level)
-        }
-        pub fn highlight_high(&self, level: f32) -> Hsla {
-            self.highlight_high.lerp(level)
-        }
-        pub fn success(&self, level: f32) -> Hsla {
-            self.success.lerp(level)
-        }
-        pub fn warning(&self, level: f32) -> Hsla {
-            self.warning.lerp(level)
-        }
-        pub fn error(&self, level: f32) -> Hsla {
-            self.error.lerp(level)
-        }
-        pub fn inserted(&self, level: f32) -> Hsla {
-            self.inserted.lerp(level)
-        }
-        pub fn deleted(&self, level: f32) -> Hsla {
-            self.deleted.lerp(level)
-        }
-        pub fn modified(&self, level: f32) -> Hsla {
-            self.modified.lerp(level)
-        }
-    }
-}
-mod view {
-    use crate::element::{AnyElement, Element};
-    use gpui::{Element as _, ViewContext};
-    pub fn view<F, E>(mut render: F) -> ViewFn
-    where
-        F: 'static + FnMut(&mut ViewContext<ViewFn>) -> E,
-        E: Element<ViewFn>,
-    {
-        ViewFn(Box::new(move |cx| (render)(cx).into_any()))
-    }
-    pub struct ViewFn(Box<dyn FnMut(&mut ViewContext<ViewFn>) -> AnyElement<ViewFn>>);
-    impl gpui::Entity for ViewFn {
-        type Event = ();
-    }
-    impl gpui::View for ViewFn {
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
-            (self.0)(cx).adapt().into_any()
-        }
-    }
-}
-fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-    gpui::App::new(()).unwrap().run(|cx| {
-        cx.add_window(
-            WindowOptions {
-                bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
-                    vec2f(0., 0.),
-                    vec2f(400., 300.),
-                )),
-                center: true,
-                ..Default::default()
-            },
-            |_| view(|_| storybook(&rose_pine::moon())),
-        );
-        cx.platform().activate(true);
-    });
-}
-fn storybook<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
-    frame()
-        .text_color(black())
-        .h_full()
-        .w_half()
-        .fill(theme.success(0.5))
-        .child(button().label("Hello").click(|_, _, _| {
-            ::std::io::_print(format_args!("click!\n"));
-        }))
-}