Merge branch 'main' into add-collab-tests

Mikayla created

Change summary

Cargo.lock                                       |   3 
Cargo.toml                                       |   2 
assets/icons/dash.svg                            |   1 
crates/diagnostics/Cargo.toml                    |   1 
crates/diagnostics/src/diagnostics.rs            | 189 ++++++---
crates/editor2/src/editor.rs                     |  92 ++--
crates/editor2/src/element.rs                    | 181 ++++++++-
crates/editor2/src/items.rs                      |  15 
crates/editor2/src/scroll.rs                     |  28 
crates/editor2/src/scroll/autoscroll.rs          |   6 
crates/gpui2/build.rs                            |   1 
crates/gpui2/src/app.rs                          |   8 
crates/gpui2/src/element.rs                      |  13 
crates/gpui2/src/elements/div.rs                 |  15 
crates/gpui2/src/geometry.rs                     |   8 
crates/gpui2/src/interactive.rs                  | 131 ++++++
crates/gpui2/src/platform/mac/dispatcher.rs      |  14 
crates/gpui2/src/style.rs                        |  21 
crates/gpui2/src/styled.rs                       |  53 ++
crates/gpui2/src/subscription.rs                 |  63 ++-
crates/gpui2/src/window.rs                       |  39 +
crates/gpui2_macros/src/derive_component.rs      |   6 
crates/gpui2_macros/src/style_helpers.rs         |   4 
crates/gpui_macros/src/gpui_macros.rs            |   4 
crates/live_kit_client2/Cargo.toml               |   6 
crates/live_kit_client2/examples/test_app.rs     |   6 
crates/live_kit_client2/src/test.rs              |   2 
crates/storybook2/Cargo.toml                     |   4 
crates/storybook2/src/assets.rs                  |   2 
crates/storybook2/src/stories/colors.rs          |   4 
crates/storybook2/src/stories/focus.rs           |   4 
crates/storybook2/src/stories/kitchen_sink.rs    |   2 
crates/storybook2/src/stories/scroll.rs          |   4 
crates/storybook2/src/stories/text.rs            |   4 
crates/storybook2/src/stories/z_index.rs         |   2 
crates/storybook2/src/story_selector.rs          |   4 
crates/storybook2/src/storybook2.rs              |   8 
crates/terminal2/src/mappings/colors.rs          |   2 
crates/theme2/src/colors.rs                      |  43 +
crates/theme2/src/default_colors.rs              |  86 +++-
crates/theme2/src/theme2.rs                      |   2 
crates/theme2/src/themes/.gitkeep                |   0 
crates/ui2/Cargo.toml                            |   2 
crates/ui2/docs/building-ui.md                   |  10 
crates/ui2/docs/hello-world.md                   |  10 
crates/ui2/src/components.rs                     |   8 
crates/ui2/src/components/avatar.rs              |   6 
crates/ui2/src/components/button.rs              |  31 +
crates/ui2/src/components/checkbox.rs            | 229 ++++++++++++
crates/ui2/src/components/context_menu.rs        |  21 
crates/ui2/src/components/details.rs             |   2 
crates/ui2/src/components/facepile.rs            |   2 
crates/ui2/src/components/icon.rs                |  11 
crates/ui2/src/components/icon_button.rs         |   6 
crates/ui2/src/components/indicator.rs           |   4 
crates/ui2/src/components/input.rs               |   6 
crates/ui2/src/components/keybinding.rs          |   4 
crates/ui2/src/components/label.rs               |   4 
crates/ui2/src/components/list.rs                | 328 +++--------------
crates/ui2/src/components/modal.rs               |   2 
crates/ui2/src/components/notification_toast.rs  |   4 
crates/ui2/src/components/palette.rs             |  13 
crates/ui2/src/components/panel.rs               |   6 
crates/ui2/src/components/player.rs              |  20 +
crates/ui2/src/components/slot.rs                |  14 
crates/ui2/src/components/stack.rs               |   2 
crates/ui2/src/components/tab.rs                 |   6 
crates/ui2/src/components/toast.rs               |   6 
crates/ui2/src/components/toggle.rs              |  61 +++
crates/ui2/src/components/tooltip.rs             |  31 +
crates/ui2/src/prelude.rs                        |  85 ----
crates/ui2/src/settings.rs                       |   2 
crates/ui2/src/static_data.rs                    | 127 +-----
crates/ui2/src/story.rs                          |   2 
crates/ui2/src/to_extract/assistant_panel.rs     |   4 
crates/ui2/src/to_extract/breadcrumb.rs          |   4 
crates/ui2/src/to_extract/buffer.rs              |   6 
crates/ui2/src/to_extract/buffer_search.rs       |  23 
crates/ui2/src/to_extract/chat_panel.rs          |   2 
crates/ui2/src/to_extract/collab_panel.rs        |  25 
crates/ui2/src/to_extract/command_palette.rs     |   2 
crates/ui2/src/to_extract/copilot.rs             |   2 
crates/ui2/src/to_extract/editor_pane.rs         |   2 
crates/ui2/src/to_extract/language_selector.rs   |   2 
crates/ui2/src/to_extract/multi_buffer.rs        |   4 
crates/ui2/src/to_extract/notifications_panel.rs |  37 -
crates/ui2/src/to_extract/panes.rs               |   4 
crates/ui2/src/to_extract/project_panel.rs       |  19 
crates/ui2/src/to_extract/recent_projects.rs     |   2 
crates/ui2/src/to_extract/status_bar.rs          |   2 
crates/ui2/src/to_extract/tab_bar.rs             |   6 
crates/ui2/src/to_extract/terminal.rs            |   6 
crates/ui2/src/to_extract/theme_selector.rs      |   2 
crates/ui2/src/to_extract/title_bar.rs           |   2 
crates/ui2/src/to_extract/toolbar.rs             |   6 
crates/ui2/src/to_extract/traffic_lights.rs      |   4 
crates/ui2/src/to_extract/workspace.rs           |  27 +
crates/workspace2/src/pane.rs                    |  42 +
crates/workspace2/src/pane_group.rs              |   2 
crates/workspace2/src/status_bar.rs              |   3 
crates/workspace2/src/workspace2.rs              |   2 
crates/zed/src/languages/elixir.rs               |  32 
crates/zed2/src/languages/elixir.rs              |  32 
crates/zed2/src/languages/language_plugin.rs     |   2 
crates/zed2/src/main.rs                          |   1 
test.rs                                          |   6 
106 files changed, 1,508 insertions(+), 955 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2531,6 +2531,7 @@ dependencies = [
  "client",
  "collections",
  "editor",
+ "futures 0.3.28",
  "gpui",
  "language",
  "log",
@@ -9802,7 +9803,7 @@ dependencies = [
 [[package]]
 name = "tree-sitter-vue"
 version = "0.0.1"
-source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=95b2890#95b28908d90e928c308866f7631e73ef6e1d4b5f"
+source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58#9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"
 dependencies = [
  "cc",
  "tree-sitter",

Cargo.toml 🔗

@@ -177,7 +177,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
 tree-sitter-lua = "0.0.14"
 tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
 tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
-tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "95b2890"}
+tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
 [patch.crates-io]
 tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }
 async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }

assets/icons/dash.svg 🔗

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-minus"><path d="M5 12h14"/></svg>

crates/diagnostics/Cargo.toml 🔗

@@ -22,6 +22,7 @@ workspace = { path = "../workspace" }
 
 log.workspace = true
 anyhow.workspace = true
+futures.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_derive.workspace = true

crates/diagnostics/src/diagnostics.rs 🔗

@@ -2,8 +2,8 @@ pub mod items;
 mod project_diagnostics_settings;
 mod toolbar_controls;
 
-use anyhow::Result;
-use collections::{BTreeSet, HashSet};
+use anyhow::{Context, Result};
+use collections::{HashMap, HashSet};
 use editor::{
     diagnostic_block_renderer,
     display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
@@ -11,9 +11,10 @@ use editor::{
     scroll::autoscroll::Autoscroll,
     Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
 };
+use futures::future::try_join_all;
 use gpui::{
     actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
-    ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -28,6 +29,7 @@ use std::{
     any::{Any, TypeId},
     borrow::Cow,
     cmp::Ordering,
+    mem,
     ops::Range,
     path::PathBuf,
     sync::Arc,
@@ -60,8 +62,10 @@ struct ProjectDiagnosticsEditor {
     summary: DiagnosticSummary,
     excerpts: ModelHandle<MultiBuffer>,
     path_states: Vec<PathState>,
-    paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
+    paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
+    current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
     include_warnings: bool,
+    _subscriptions: Vec<Subscription>,
 }
 
 struct PathState {
@@ -125,9 +129,12 @@ impl View for ProjectDiagnosticsEditor {
                 "summary": project.diagnostic_summary(cx),
             }),
             "summary": self.summary,
-            "paths_to_update": self.paths_to_update.iter().map(|(path, server_id)|
-                (path.path.to_string_lossy(), server_id.0)
-            ).collect::<Vec<_>>(),
+            "paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
+                (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
+            ).collect::<HashMap<_, _>>(),
+            "current_diagnostics": self.current_diagnostics.iter().map(|(server_id, paths)|
+                (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
+            ).collect::<HashMap<_, _>>(),
             "paths_states": self.path_states.iter().map(|state|
                 json!({
                     "path": state.path.path.to_string_lossy(),
@@ -149,25 +156,30 @@ impl ProjectDiagnosticsEditor {
         workspace: WeakViewHandle<Workspace>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        cx.subscribe(&project_handle, |this, _, event, cx| match event {
-            project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
-                log::debug!("Disk based diagnostics finished for server {language_server_id}");
-                this.update_excerpts(Some(*language_server_id), cx);
-                this.update_title(cx);
-            }
-            project::Event::DiagnosticsUpdated {
-                language_server_id,
-                path,
-            } => {
-                log::debug!("Adding path {path:?} to update for server {language_server_id}");
-                this.paths_to_update
-                    .insert((path.clone(), *language_server_id));
-                this.update_excerpts(Some(*language_server_id), cx);
-                this.update_title(cx);
-            }
-            _ => {}
-        })
-        .detach();
+        let project_event_subscription =
+            cx.subscribe(&project_handle, |this, _, event, cx| match event {
+                project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
+                    log::debug!("Disk based diagnostics finished for server {language_server_id}");
+                    this.update_excerpts(Some(*language_server_id), cx);
+                }
+                project::Event::DiagnosticsUpdated {
+                    language_server_id,
+                    path,
+                } => {
+                    log::debug!("Adding path {path:?} to update for server {language_server_id}");
+                    this.paths_to_update
+                        .entry(*language_server_id)
+                        .or_default()
+                        .insert(path.clone());
+                    let no_multiselections = this.editor.update(cx, |editor, cx| {
+                        editor.selections.all::<usize>(cx).len() <= 1
+                    });
+                    if no_multiselections && !this.is_dirty(cx) {
+                        this.update_excerpts(Some(*language_server_id), cx);
+                    }
+                }
+                _ => {}
+            });
 
         let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
         let editor = cx.add_view(|cx| {
@@ -176,19 +188,14 @@ impl ProjectDiagnosticsEditor {
             editor.set_vertical_scroll_margin(5, cx);
             editor
         });
-        cx.subscribe(&editor, |this, _, event, cx| {
+        let editor_event_subscription = cx.subscribe(&editor, |this, _, event, cx| {
             cx.emit(event.clone());
             if event == &editor::Event::Focused && this.path_states.is_empty() {
                 cx.focus_self()
             }
-        })
-        .detach();
+        });
 
         let project = project_handle.read(cx);
-        let paths_to_update = project
-            .diagnostic_summaries(cx)
-            .map(|(path, server_id, _)| (path, server_id))
-            .collect();
         let summary = project.diagnostic_summary(cx);
         let mut this = Self {
             project: project_handle,
@@ -197,8 +204,10 @@ impl ProjectDiagnosticsEditor {
             excerpts,
             editor,
             path_states: Default::default(),
-            paths_to_update,
+            paths_to_update: HashMap::default(),
             include_warnings: settings::get::<ProjectDiagnosticsSettings>(cx).include_warnings,
+            current_diagnostics: HashMap::default(),
+            _subscriptions: vec![project_event_subscription, editor_event_subscription],
         };
         this.update_excerpts(None, cx);
         this
@@ -218,12 +227,7 @@ impl ProjectDiagnosticsEditor {
 
     fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
         self.include_warnings = !self.include_warnings;
-        self.paths_to_update = self
-            .project
-            .read(cx)
-            .diagnostic_summaries(cx)
-            .map(|(path, server_id, _)| (path, server_id))
-            .collect();
+        self.paths_to_update = self.current_diagnostics.clone();
         self.update_excerpts(None, cx);
         cx.notify();
     }
@@ -234,29 +238,93 @@ impl ProjectDiagnosticsEditor {
         cx: &mut ViewContext<Self>,
     ) {
         log::debug!("Updating excerpts for server {language_server_id:?}");
-        let mut paths = Vec::new();
-        self.paths_to_update.retain(|(path, server_id)| {
-            if language_server_id
-                .map_or(true, |language_server_id| language_server_id == *server_id)
-            {
-                paths.push(path.clone());
-                false
+        let mut paths_to_recheck = HashSet::default();
+        let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
+            .project
+            .read(cx)
+            .diagnostic_summaries(cx)
+            .fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
+                summaries.entry(server_id).or_default().insert(path);
+                summaries
+            });
+        let mut old_diagnostics = if let Some(language_server_id) = language_server_id {
+            new_summaries.retain(|server_id, _| server_id == &language_server_id);
+            self.paths_to_update.retain(|server_id, paths| {
+                if server_id == &language_server_id {
+                    paths_to_recheck.extend(paths.drain());
+                    false
+                } else {
+                    true
+                }
+            });
+            let mut old_diagnostics = HashMap::default();
+            if let Some(new_paths) = new_summaries.get(&language_server_id) {
+                if let Some(old_paths) = self
+                    .current_diagnostics
+                    .insert(language_server_id, new_paths.clone())
+                {
+                    old_diagnostics.insert(language_server_id, old_paths);
+                }
             } else {
-                true
+                if let Some(old_paths) = self.current_diagnostics.remove(&language_server_id) {
+                    old_diagnostics.insert(language_server_id, old_paths);
+                }
             }
-        });
+            old_diagnostics
+        } else {
+            paths_to_recheck.extend(self.paths_to_update.drain().flat_map(|(_, paths)| paths));
+            mem::replace(&mut self.current_diagnostics, new_summaries.clone())
+        };
+        for (server_id, new_paths) in new_summaries {
+            match old_diagnostics.remove(&server_id) {
+                Some(mut old_paths) => {
+                    paths_to_recheck.extend(
+                        new_paths
+                            .into_iter()
+                            .filter(|new_path| !old_paths.remove(new_path)),
+                    );
+                    paths_to_recheck.extend(old_paths);
+                }
+                None => paths_to_recheck.extend(new_paths),
+            }
+        }
+        paths_to_recheck.extend(old_diagnostics.into_iter().flat_map(|(_, paths)| paths));
+
+        if paths_to_recheck.is_empty() {
+            log::debug!("No paths to recheck for language server {language_server_id:?}");
+            return;
+        }
+        log::debug!(
+            "Rechecking {} paths for language server {:?}",
+            paths_to_recheck.len(),
+            language_server_id
+        );
         let project = self.project.clone();
         cx.spawn(|this, mut cx| {
             async move {
-                for path in paths {
-                    let buffer = project
-                        .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
-                        .await?;
-                    this.update(&mut cx, |this, cx| {
-                        this.populate_excerpts(path, language_server_id, buffer, cx)
-                    })?;
-                }
-                Result::<_, anyhow::Error>::Ok(())
+                let _: Vec<()> = try_join_all(paths_to_recheck.into_iter().map(|path| {
+                    let mut cx = cx.clone();
+                    let project = project.clone();
+                    async move {
+                        let buffer = project
+                            .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
+                            .await
+                            .with_context(|| format!("opening buffer for path {path:?}"))?;
+                        this.update(&mut cx, |this, cx| {
+                            this.populate_excerpts(path, language_server_id, buffer, cx);
+                        })
+                        .context("missing project")?;
+                        anyhow::Ok(())
+                    }
+                }))
+                .await
+                .context("rechecking diagnostics for paths")?;
+
+                this.update(&mut cx, |this, cx| {
+                    this.summary = this.project.read(cx).diagnostic_summary(cx);
+                    cx.emit(Event::TitleChanged);
+                })?;
+                anyhow::Ok(())
             }
             .log_err()
         })
@@ -559,11 +627,6 @@ impl ProjectDiagnosticsEditor {
         }
         cx.notify();
     }
-
-    fn update_title(&mut self, cx: &mut ViewContext<Self>) {
-        self.summary = self.project.read(cx).diagnostic_summary(cx);
-        cx.emit(Event::TitleChanged);
-    }
 }
 
 impl Item for ProjectDiagnosticsEditor {

crates/editor2/src/editor.rs 🔗

@@ -37,8 +37,8 @@ use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
-    FocusHandle, Hsla, Model, Pixels, Render, Subscription, Task, TextStyle, View, ViewContext,
-    VisualContext, WeakView, WindowContext,
+    FocusHandle, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -68,6 +68,7 @@ use scroll::{
 use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
+use smallvec::SmallVec;
 use std::{
     any::TypeId,
     borrow::Cow,
@@ -8347,51 +8348,51 @@ impl Editor {
     //             .text()
     //     }
 
-    //     pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
-    //         let mut wrap_guides = smallvec::smallvec![];
+    pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
+        let mut wrap_guides = smallvec::smallvec![];
 
-    //         if self.show_wrap_guides == Some(false) {
-    //             return wrap_guides;
-    //         }
+        if self.show_wrap_guides == Some(false) {
+            return wrap_guides;
+        }
 
-    //         let settings = self.buffer.read(cx).settings_at(0, cx);
-    //         if settings.show_wrap_guides {
-    //             if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
-    //                 wrap_guides.push((soft_wrap as usize, true));
-    //             }
-    //             wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
-    //         }
+        let settings = self.buffer.read(cx).settings_at(0, cx);
+        if settings.show_wrap_guides {
+            if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
+                wrap_guides.push((soft_wrap as usize, true));
+            }
+            wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
+        }
 
-    //         wrap_guides
-    //     }
+        wrap_guides
+    }
 
-    //     pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
-    //         let settings = self.buffer.read(cx).settings_at(0, cx);
-    //         let mode = self
-    //             .soft_wrap_mode_override
-    //             .unwrap_or_else(|| settings.soft_wrap);
-    //         match mode {
-    //             language_settings::SoftWrap::None => SoftWrap::None,
-    //             language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
-    //             language_settings::SoftWrap::PreferredLineLength => {
-    //                 SoftWrap::Column(settings.preferred_line_length)
-    //             }
-    //         }
-    //     }
+    pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
+        let settings = self.buffer.read(cx).settings_at(0, cx);
+        let mode = self
+            .soft_wrap_mode_override
+            .unwrap_or_else(|| settings.soft_wrap);
+        match mode {
+            language_settings::SoftWrap::None => SoftWrap::None,
+            language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
+            language_settings::SoftWrap::PreferredLineLength => {
+                SoftWrap::Column(settings.preferred_line_length)
+            }
+        }
+    }
 
-    //     pub fn set_soft_wrap_mode(
-    //         &mut self,
-    //         mode: language_settings::SoftWrap,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.soft_wrap_mode_override = Some(mode);
-    //         cx.notify();
-    //     }
+    pub fn set_soft_wrap_mode(
+        &mut self,
+        mode: language_settings::SoftWrap,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.soft_wrap_mode_override = Some(mode);
+        cx.notify();
+    }
 
-    //     pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut AppContext) -> bool {
-    //         self.display_map
-    //             .update(cx, |map, cx| map.set_wrap_width(width, cx))
-    //     }
+    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))
+    }
 
     //     pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
     //         if self.soft_wrap_mode_override.is_some() {
@@ -9321,11 +9322,14 @@ impl EventEmitter for Editor {
 }
 
 impl Render for Editor {
-    type Element = Div<Self>;
+    type Element = EditorElement;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        // todo!()
-        div()
+        EditorElement::new(EditorStyle {
+            text: cx.text_style(),
+            line_height_scalar: 1.,
+            theme_id: 0,
+        })
     }
 }
 

crates/editor2/src/element.rs 🔗

@@ -3,17 +3,18 @@ use super::{
 };
 use crate::{
     display_map::{BlockStyle, DisplaySnapshot},
-    EditorStyle,
+    EditorMode, EditorStyle, SoftWrap,
 };
 use anyhow::Result;
 use gpui::{
-    black, px, relative, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, TextRun,
-    TextSystem,
+    black, point, px, relative, size, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style,
+    TextRun, TextSystem, ViewContext,
 };
 use language::{CursorShape, Selection};
 use smallvec::SmallVec;
-use std::{ops::Range, sync::Arc};
+use std::{cmp, ops::Range, sync::Arc};
 use sum_tree::Bias;
+use theme::ActiveTheme;
 
 enum FoldMarkers {}
 
@@ -1321,29 +1322,31 @@ impl EditorElement {
     //     }
     // }
 
-    // fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> f32 {
-    //     let style = &self.style;
-
-    //     cx.text_layout_cache()
-    //         .layout_str(
-    //             " ".repeat(column).as_str(),
-    //             style.text.font_size,
-    //             &[(
-    //                 column,
-    //                 RunStyle {
-    //                     font_id: style.text.font_id,
-    //                     color: Color::black(),
-    //                     underline: Default::default(),
-    //                 },
-    //             )],
-    //         )
-    //         .width()
-    // }
+    fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
+        let style = &self.style;
+        let font_size = style.text.font_size * cx.rem_size();
+        let layout = cx
+            .text_system()
+            .layout_text(
+                " ".repeat(column).as_str(),
+                font_size,
+                &[TextRun {
+                    len: column,
+                    font: style.text.font(),
+                    color: Hsla::default(),
+                    underline: None,
+                }],
+                None,
+            )
+            .unwrap();
+
+        layout[0].width
+    }
 
-    // fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
-    //     let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
-    //     self.column_pixels(digit_count, cx)
-    // }
+    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {
+        let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
+        self.column_pixels(digit_count, cx)
+    }
 
     //Folds contained in a hunk are ignored apart from shrinking visual size
     //If a fold contains any hunks then that fold line is marked as modified
@@ -2002,6 +2005,7 @@ impl Element<Editor> for EditorElement {
         element_state: &mut Self::ElementState,
         cx: &mut gpui::ViewContext<Editor>,
     ) -> gpui::LayoutId {
+        let rem_size = cx.rem_size();
         let mut style = Style::default();
         style.size.width = relative(1.).into();
         style.size.height = relative(1.).into();
@@ -2011,18 +2015,125 @@ impl Element<Editor> for EditorElement {
     fn paint(
         &mut self,
         bounds: Bounds<gpui::Pixels>,
-        view_state: &mut Editor,
+        editor: &mut Editor,
         element_state: &mut Self::ElementState,
         cx: &mut gpui::ViewContext<Editor>,
     ) {
-        let text_style = cx.text_style();
-
-        let layout_text = cx.text_system().layout_text(
-            "hello world",
-            text_style.font_size * cx.rem_size(),
-            &[text_style.to_run("hello world".len())],
-            None,
-        );
+        // let mut size = constraint.max;
+        // if size.x().is_infinite() {
+        //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
+        // }
+
+        let snapshot = editor.snapshot(cx);
+        let style = self.style.clone();
+        let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+        let font_size = style.text.font_size * cx.rem_size();
+        let line_height = (font_size * style.line_height_scalar).round();
+        let em_width = cx
+            .text_system()
+            .typographic_bounds(font_id, font_size, 'm')
+            .unwrap()
+            .size
+            .width;
+        let em_advance = cx
+            .text_system()
+            .advance(font_id, font_size, 'm')
+            .unwrap()
+            .width;
+
+        let gutter_padding;
+        let gutter_width;
+        let gutter_margin;
+        if snapshot.show_gutter {
+            let descent = cx.text_system().descent(font_id, font_size).unwrap();
+
+            let gutter_padding_factor = 3.5;
+            gutter_padding = (em_width * gutter_padding_factor).round();
+            gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
+            gutter_margin = -descent;
+        } else {
+            gutter_padding = px(0.0);
+            gutter_width = px(0.0);
+            gutter_margin = px(0.0);
+        };
+
+        let text_width = bounds.size.width - gutter_width;
+        let overscroll = point(em_width, px(0.));
+        let snapshot = {
+            editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
+
+            let editor_width = text_width - gutter_margin - overscroll.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::<SmallVec<[_; 2]>>();
+
+        let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
+        // todo!("this should happen during layout")
+        if let EditorMode::AutoHeight { max_lines } = snapshot.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 = snapshot.mode {
+            todo!()
+            //     size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
+        }
+        // todo!()
+        // else if size.y().is_infinite() {
+        //     //     size.set_y(scroll_height);
+        // }
+        //
+        let gutter_size = size(gutter_width, bounds.size.height);
+        let text_size = size(text_width, bounds.size.height);
+
+        let autoscroll_horizontally =
+            editor.autoscroll_vertically(bounds.size.height, line_height, cx);
+        let mut snapshot = editor.snapshot(cx);
+
+        let scroll_position = snapshot.scroll_position();
+        // The scroll position is a fractional point, the whole number of which represents
+        // the top of the window in terms of display rows.
+        let start_row = scroll_position.y as u32;
+        let height_in_lines = f32::from(bounds.size.height / line_height);
+        let max_row = snapshot.max_point().row();
+
+        // Add 1 to ensure selections bleed off screen
+        let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
+
+        dbg!(start_row..end_row);
+        // let text_style = cx.text_style();
+        // let layout_text = cx.text_system().layout_text(
+        //     "hello world",
+        //     text_style.font_size * cx.rem_size(),
+        //     &[text_style.to_run("hello world".len())],
+        //     None,
+        // );
+        // let line_height = text_style
+        //     .line_height
+        //     .to_pixels(text_style.font_size.into(), cx.rem_size());
+
+        // layout_text.unwrap()[0]
+        //     .paint(bounds.origin, line_height, cx)
+        //     .unwrap();
     }
 }
 

crates/editor2/src/items.rs 🔗

@@ -578,18 +578,24 @@ impl Item for Editor {
 
     fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
         let theme = cx.theme();
+
         AnyElement::new(
             div()
                 .flex()
                 .flex_row()
                 .items_center()
-                .bg(gpui::white())
-                .text_color(gpui::white())
+                .gap_2()
                 .child(self.title(cx).to_string())
                 .children(detail.and_then(|detail| {
                     let path = path_for_buffer(&self.buffer, detail, false, cx)?;
                     let description = path.to_string_lossy();
-                    Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN))
+
+                    Some(
+                        div()
+                            .text_color(theme.colors().text_muted)
+                            .text_xs()
+                            .child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)),
+                    )
                 })),
         )
     }
@@ -625,8 +631,7 @@ impl Item for Editor {
 
     fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
         let selection = self.selections.newest_anchor();
-        todo!()
-        // self.push_to_nav_history(selection.head(), None, cx);
+        self.push_to_nav_history(selection.head(), None, cx);
     }
 
     fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {

crates/editor2/src/scroll.rs 🔗

@@ -303,20 +303,20 @@ impl Editor {
         self.scroll_manager.visible_line_count
     }
 
-    //     pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
-    //         let opened_first_time = self.scroll_manager.visible_line_count.is_none();
-    //         self.scroll_manager.visible_line_count = Some(lines);
-    //         if opened_first_time {
-    //             cx.spawn(|editor, mut cx| async move {
-    //                 editor
-    //                     .update(&mut cx, |editor, cx| {
-    //                         editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
-    //                     })
-    //                     .ok()
-    //             })
-    //             .detach()
-    //         }
-    //     }
+    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
+        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
+        self.scroll_manager.visible_line_count = Some(lines);
+        if opened_first_time {
+            cx.spawn(|editor, mut cx| async move {
+                editor
+                    .update(&mut cx, |editor, cx| {
+                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
+                    })
+                    .ok()
+            })
+            .detach()
+        }
+    }
 
     pub fn set_scroll_position(
         &mut self,

crates/editor2/src/scroll/autoscroll.rs 🔗

@@ -48,11 +48,11 @@ impl AutoscrollStrategy {
 impl Editor {
     pub fn autoscroll_vertically(
         &mut self,
-        viewport_height: f32,
-        line_height: f32,
+        viewport_height: Pixels,
+        line_height: Pixels,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
-        let visible_lines = viewport_height / line_height;
+        let visible_lines = f32::from(viewport_height / line_height);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
         let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {

crates/gpui2/build.rs 🔗

@@ -20,6 +20,7 @@ fn generate_dispatch_bindings() {
         .header("src/platform/mac/dispatch.h")
         .allowlist_var("_dispatch_main_q")
         .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
+        .allowlist_var("DISPATCH_TIME_NOW")
         .allowlist_function("dispatch_get_global_queue")
         .allowlist_function("dispatch_async_f")
         .allowlist_function("dispatch_after_f")

crates/gpui2/src/app.rs 🔗

@@ -165,6 +165,7 @@ pub struct AppContext {
     flushing_effects: bool,
     pending_updates: usize,
     pub(crate) active_drag: Option<AnyDrag>,
+    pub(crate) active_tooltip: Option<AnyTooltip>,
     pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
     pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>,
     pub(crate) background_executor: BackgroundExecutor,
@@ -223,6 +224,7 @@ impl AppContext {
                 flushing_effects: false,
                 pending_updates: 0,
                 active_drag: None,
+                active_tooltip: None,
                 next_frame_callbacks: HashMap::default(),
                 frame_consumers: HashMap::default(),
                 background_executor: executor,
@@ -1012,3 +1014,9 @@ pub(crate) struct AnyDrag {
     pub view: AnyView,
     pub cursor_offset: Point<Pixels>,
 }
+
+#[derive(Clone)]
+pub(crate) struct AnyTooltip {
+    pub view: AnyView,
+    pub cursor_offset: Point<Pixels>,
+}

crates/gpui2/src/element.rs 🔗

@@ -212,6 +212,19 @@ pub trait Component<V> {
     {
         self.map(|this| if condition { then(this) } else { this })
     }
+
+    fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
+    where
+        Self: Sized,
+    {
+        self.map(|this| {
+            if let Some(value) = option {
+                then(this, value)
+            } else {
+                this
+            }
+        })
+    }
 }
 
 impl<V> Component<V> for AnyElement<V> {

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

@@ -3,7 +3,7 @@ use crate::{
     ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
     GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
     Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
-    StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
+    StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility,
 };
 use refineable::Refineable;
 use smallvec::SmallVec;
@@ -249,11 +249,22 @@ where
         cx: &mut ViewContext<V>,
     ) {
         self.with_element_id(cx, |this, _global_id, cx| {
+            let style = this.compute_style(bounds, element_state, cx);
+            if style.visibility == Visibility::Hidden {
+                return;
+            }
+
+            if let Some(mouse_cursor) = style.mouse_cursor {
+                let hovered = bounds.contains_point(&cx.mouse_position());
+                if hovered {
+                    cx.set_cursor_style(mouse_cursor);
+                }
+            }
+
             if let Some(group) = this.group.clone() {
                 GroupBounds::push(group, bounds, cx);
             }
 
-            let style = this.compute_style(bounds, element_state, cx);
             let z_index = style.z_index.unwrap_or(0);
 
             let mut child_min = point(Pixels::MAX, Pixels::MAX);

crates/gpui2/src/geometry.rs 🔗

@@ -21,7 +21,7 @@ pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
 }
 
 impl<T: Clone + Debug + Default> Point<T> {
-    pub fn new(x: T, y: T) -> Self {
+    pub const fn new(x: T, y: T) -> Self {
         Self { x, y }
     }
 
@@ -825,6 +825,12 @@ impl From<Pixels> for u32 {
     }
 }
 
+impl From<u32> for Pixels {
+    fn from(pixels: u32) -> Self {
+        Pixels(pixels as f32)
+    }
+}
+
 impl From<Pixels> for usize {
     fn from(pixels: Pixels) -> Self {
         pixels.0 as usize

crates/gpui2/src/interactive.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
-    div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component,
-    DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
-    Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View,
-    ViewContext,
+    div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
+    Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
+    Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
+    StyleRefinement, Task, View, ViewContext,
 };
 use collections::HashMap;
 use derive_more::{Deref, DerefMut};
@@ -17,9 +17,12 @@ use std::{
     ops::Deref,
     path::PathBuf,
     sync::Arc,
+    time::Duration,
 };
 
 const DRAG_THRESHOLD: f64 = 2.;
+const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
+const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
 
 pub trait StatelessInteractive<V: 'static>: Element<V> {
     fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
@@ -333,6 +336,37 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
             }));
         self
     }
+
+    fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
+    where
+        Self: Sized,
+    {
+        debug_assert!(
+            self.stateful_interaction().hover_listener.is_none(),
+            "calling on_hover more than once on the same element is not supported"
+        );
+        self.stateful_interaction().hover_listener = Some(Box::new(listener));
+        self
+    }
+
+    fn tooltip<W>(
+        mut self,
+        build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+        W: 'static + Render,
+    {
+        debug_assert!(
+            self.stateful_interaction().tooltip_builder.is_none(),
+            "calling tooltip more than once on the same element is not supported"
+        );
+        self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| {
+            build_tooltip(view_state, cx).into()
+        }));
+
+        self
+    }
 }
 
 pub trait ElementInteraction<V: 'static>: 'static {
@@ -568,6 +602,77 @@ pub trait ElementInteraction<V: 'static>: 'static {
                 }
             }
 
+            if let Some(hover_listener) = stateful.hover_listener.take() {
+                let was_hovered = element_state.hover_state.clone();
+                let has_mouse_down = element_state.pending_mouse_down.clone();
+
+                cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
+                    if phase != DispatchPhase::Bubble {
+                        return;
+                    }
+                    let is_hovered =
+                        bounds.contains_point(&event.position) && has_mouse_down.lock().is_none();
+                    let mut was_hovered = was_hovered.lock();
+
+                    if is_hovered != was_hovered.clone() {
+                        *was_hovered = is_hovered;
+                        drop(was_hovered);
+
+                        hover_listener(view_state, is_hovered, cx);
+                    }
+                });
+            }
+
+            if let Some(tooltip_builder) = stateful.tooltip_builder.take() {
+                let active_tooltip = element_state.active_tooltip.clone();
+                let pending_mouse_down = element_state.pending_mouse_down.clone();
+
+                cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+                    if phase != DispatchPhase::Bubble {
+                        return;
+                    }
+
+                    let is_hovered = bounds.contains_point(&event.position)
+                        && pending_mouse_down.lock().is_none();
+                    if !is_hovered {
+                        active_tooltip.lock().take();
+                        return;
+                    }
+
+                    if active_tooltip.lock().is_none() {
+                        let task = cx.spawn({
+                            let active_tooltip = active_tooltip.clone();
+                            let tooltip_builder = tooltip_builder.clone();
+
+                            move |view, mut cx| async move {
+                                cx.background_executor().timer(TOOLTIP_DELAY).await;
+                                view.update(&mut cx, move |view_state, cx| {
+                                    active_tooltip.lock().replace(ActiveTooltip {
+                                        waiting: None,
+                                        tooltip: Some(AnyTooltip {
+                                            view: tooltip_builder(view_state, cx),
+                                            cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET,
+                                        }),
+                                    });
+                                    cx.notify();
+                                })
+                                .ok();
+                            }
+                        });
+                        active_tooltip.lock().replace(ActiveTooltip {
+                            waiting: Some(task),
+                            tooltip: None,
+                        });
+                    }
+                });
+
+                if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() {
+                    if active_tooltip.tooltip.is_some() {
+                        cx.active_tooltip = active_tooltip.tooltip.clone()
+                    }
+                }
+            }
+
             let active_state = element_state.active_state.clone();
             if active_state.lock().is_none() {
                 let active_group_bounds = stateful
@@ -639,6 +744,8 @@ pub struct StatefulInteraction<V> {
     active_style: StyleRefinement,
     group_active_style: Option<GroupStyle>,
     drag_listener: Option<DragListener<V>>,
+    hover_listener: Option<HoverListener<V>>,
+    tooltip_builder: Option<TooltipBuilder<V>>,
 }
 
 impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
@@ -666,6 +773,8 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
             stateless: StatelessInteraction::default(),
             click_listeners: SmallVec::new(),
             drag_listener: None,
+            hover_listener: None,
+            tooltip_builder: None,
             active_style: StyleRefinement::default(),
             group_active_style: None,
         }
@@ -695,6 +804,8 @@ impl<V> StatelessInteraction<V> {
             stateless: self,
             click_listeners: SmallVec::new(),
             drag_listener: None,
+            hover_listener: None,
+            tooltip_builder: None,
             active_style: StyleRefinement::default(),
             group_active_style: None,
         }
@@ -746,8 +857,16 @@ impl ActiveState {
 #[derive(Default)]
 pub struct InteractiveElementState {
     active_state: Arc<Mutex<ActiveState>>,
+    hover_state: Arc<Mutex<bool>>,
     pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
     scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
+    active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
+}
+
+struct ActiveTooltip {
+    #[allow(unused)] // used to drop the task
+    waiting: Option<Task<()>>,
+    tooltip: Option<AnyTooltip>,
 }
 
 impl InteractiveElementState {
@@ -1097,6 +1216,10 @@ pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>)
 pub(crate) type DragListener<V> =
     Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
 
+pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
+
+pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
+
 pub type KeyListener<V> = Box<
     dyn Fn(
             &mut V,

crates/gpui2/src/platform/mac/dispatcher.rs 🔗

@@ -11,11 +11,7 @@ use objc::{
 };
 use parking::{Parker, Unparker};
 use parking_lot::Mutex;
-use std::{
-    ffi::c_void,
-    sync::Arc,
-    time::{Duration, SystemTime},
-};
+use std::{ffi::c_void, sync::Arc, time::Duration};
 
 include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
 
@@ -62,16 +58,10 @@ impl PlatformDispatcher for MacDispatcher {
     }
 
     fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
-        let now = SystemTime::now();
-        let after_duration = now
-            .duration_since(SystemTime::UNIX_EPOCH)
-            .unwrap()
-            .as_nanos() as u64
-            + duration.as_nanos() as u64;
         unsafe {
             let queue =
                 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
-            let when = dispatch_time(0, after_duration as i64);
+            let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
             dispatch_after_f(
                 when,
                 queue,

crates/gpui2/src/style.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
     black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
-    Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
-    FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, Rgba,
-    SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
+    Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
+    FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems,
+    Result, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
 };
 use refineable::{Cascade, Refineable};
 use smallvec::SmallVec;
@@ -19,6 +19,9 @@ pub struct Style {
     /// What layout strategy should be used?
     pub display: Display,
 
+    /// Should the element be painted on screen?
+    pub visibility: Visibility,
+
     // Overflow properties
     /// How children overflowing their container should affect layout
     #[refineable]
@@ -98,6 +101,9 @@ pub struct Style {
     /// TEXT
     pub text: TextStyleRefinement,
 
+    /// The mouse cursor style shown when the mouse pointer is over an element.
+    pub mouse_cursor: Option<CursorStyle>,
+
     pub z_index: Option<u32>,
 }
 
@@ -107,6 +113,13 @@ impl Styled for StyleRefinement {
     }
 }
 
+#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Visibility {
+    #[default]
+    Visible,
+    Hidden,
+}
+
 #[derive(Clone, Debug)]
 pub struct BoxShadow {
     pub color: Hsla,
@@ -297,6 +310,7 @@ impl Default for Style {
     fn default() -> Self {
         Style {
             display: Display::Block,
+            visibility: Visibility::Visible,
             overflow: Point {
                 x: Overflow::Visible,
                 y: Overflow::Visible,
@@ -328,6 +342,7 @@ impl Default for Style {
             corner_radii: Corners::default(),
             box_shadow: Default::default(),
             text: TextStyleRefinement::default(),
+            mouse_cursor: None,
             z_index: None,
         }
     }

crates/gpui2/src/styled.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
-    self as gpui2, hsla, point, px, relative, rems, AlignItems, DefiniteLength, Display, Fill,
-    FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString, StyleRefinement,
+    self as gpui, hsla, point, px, relative, rems, AlignItems, CursorStyle, DefiniteLength,
+    Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString,
+    StyleRefinement, Visibility,
 };
 use crate::{BoxShadow, TextStyleRefinement};
 use smallvec::smallvec;
@@ -60,6 +61,54 @@ pub trait Styled {
         self
     }
 
+    /// Sets the visibility of the element to `visible`.
+    /// [Docs](https://tailwindcss.com/docs/visibility)
+    fn visible(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().visibility = Some(Visibility::Visible);
+        self
+    }
+
+    /// Sets the visibility of the element to `hidden`.
+    /// [Docs](https://tailwindcss.com/docs/visibility)
+    fn invisible(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().visibility = Some(Visibility::Hidden);
+        self
+    }
+
+    fn cursor(mut self, cursor: CursorStyle) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().mouse_cursor = Some(cursor);
+        self
+    }
+
+    /// Sets the cursor style when hovering an element to `default`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_default(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().mouse_cursor = Some(CursorStyle::Arrow);
+        self
+    }
+
+    /// Sets the cursor style when hovering an element to `pointer`.
+    /// [Docs](https://tailwindcss.com/docs/cursor)
+    fn cursor_pointer(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().mouse_cursor = Some(CursorStyle::PointingHand);
+        self
+    }
+
     /// Sets the flex direction of the element to `column`.
     /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
     fn flex_col(mut self) -> Self

crates/gpui2/src/subscription.rs 🔗

@@ -14,7 +14,7 @@ impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
 }
 
 struct SubscriberSetState<EmitterKey, Callback> {
-    subscribers: BTreeMap<EmitterKey, BTreeMap<usize, Callback>>,
+    subscribers: BTreeMap<EmitterKey, Option<BTreeMap<usize, Callback>>>,
     dropped_subscribers: BTreeSet<(EmitterKey, usize)>,
     next_subscriber_id: usize,
 }
@@ -38,12 +38,18 @@ where
         lock.subscribers
             .entry(emitter_key.clone())
             .or_default()
+            .get_or_insert_with(|| Default::default())
             .insert(subscriber_id, callback);
         let this = self.0.clone();
         Subscription {
             unsubscribe: Some(Box::new(move || {
                 let mut lock = this.lock();
-                if let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) {
+                let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else {
+                    // remove was called with this emitter_key
+                    return;
+                };
+
+                if let Some(subscribers) = subscribers {
                     subscribers.remove(&subscriber_id);
                     if subscribers.is_empty() {
                         lock.subscribers.remove(&emitter_key);
@@ -62,34 +68,43 @@ where
 
     pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator<Item = Callback> {
         let subscribers = self.0.lock().subscribers.remove(&emitter);
-        subscribers.unwrap_or_default().into_values()
+        subscribers
+            .unwrap_or_default()
+            .map(|s| s.into_values())
+            .into_iter()
+            .flatten()
     }
 
     pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
     where
         F: FnMut(&mut Callback) -> bool,
     {
-        let entry = self.0.lock().subscribers.remove_entry(emitter);
-        if let Some((emitter, mut subscribers)) = entry {
-            subscribers.retain(|_, callback| f(callback));
-            let mut lock = self.0.lock();
-
-            // Add any new subscribers that were added while invoking the callback.
-            if let Some(new_subscribers) = lock.subscribers.remove(&emitter) {
-                subscribers.extend(new_subscribers);
-            }
-
-            // Remove any dropped subscriptions that were dropped while invoking the callback.
-            for (dropped_emitter, dropped_subscription_id) in
-                mem::take(&mut lock.dropped_subscribers)
-            {
-                debug_assert_eq!(emitter, dropped_emitter);
-                subscribers.remove(&dropped_subscription_id);
-            }
-
-            if !subscribers.is_empty() {
-                lock.subscribers.insert(emitter, subscribers);
-            }
+        let Some(mut subscribers) = self
+            .0
+            .lock()
+            .subscribers
+            .get_mut(emitter)
+            .and_then(|s| s.take())
+        else {
+            return;
+        };
+
+        subscribers.retain(|_, callback| f(callback));
+        let mut lock = self.0.lock();
+
+        // Add any new subscribers that were added while invoking the callback.
+        if let Some(Some(new_subscribers)) = lock.subscribers.remove(&emitter) {
+            subscribers.extend(new_subscribers);
+        }
+
+        // Remove any dropped subscriptions that were dropped while invoking the callback.
+        for (dropped_emitter, dropped_subscription_id) in mem::take(&mut lock.dropped_subscribers) {
+            debug_assert_eq!(*emitter, dropped_emitter);
+            subscribers.remove(&dropped_subscription_id);
+        }
+
+        if !subscribers.is_empty() {
+            lock.subscribers.insert(emitter.clone(), Some(subscribers));
         }
     }
 }

crates/gpui2/src/window.rs 🔗

@@ -1,14 +1,14 @@
 use crate::{
     px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
-    Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
-    Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
-    Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
-    Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
-    MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point,
-    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
-    Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
-    WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
+    Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
+    GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
+    KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
+    PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
+    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
+    Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
+    VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Result};
 use collections::HashMap;
@@ -190,6 +190,7 @@ pub struct Window {
     pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
+    requested_cursor_style: Option<CursorStyle>,
     scale_factor: f32,
     bounds: WindowBounds,
     bounds_observers: SubscriberSet<(), AnyObserver>,
@@ -283,6 +284,7 @@ impl Window {
             focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             default_prevented: true,
             mouse_position,
+            requested_cursor_style: None,
             scale_factor,
             bounds,
             bounds_observers: SubscriberSet::new(),
@@ -669,6 +671,10 @@ impl<'a> WindowContext<'a> {
         self.window.mouse_position
     }
 
+    pub fn set_cursor_style(&mut self, style: CursorStyle) {
+        self.window.requested_cursor_style = Some(style)
+    }
+
     /// Called during painting to invoke the given closure in a new stacking context. The given
     /// z-index is interpreted relative to the previous call to `stack`.
     pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
@@ -981,12 +987,27 @@ impl<'a> WindowContext<'a> {
                     cx.active_drag = Some(active_drag);
                 });
             });
+        } else if let Some(active_tooltip) = self.app.active_tooltip.take() {
+            self.stack(1, |cx| {
+                cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| {
+                    let available_space =
+                        size(AvailableSpace::MinContent, AvailableSpace::MinContent);
+                    active_tooltip.view.draw(available_space, cx);
+                });
+            });
         }
 
         self.window.root_view = Some(root_view);
         let scene = self.window.scene_builder.build();
 
         self.window.platform_window.draw(scene);
+        let cursor_style = self
+            .window
+            .requested_cursor_style
+            .take()
+            .unwrap_or(CursorStyle::Arrow);
+        self.platform.set_cursor_style(cursor_style);
+
         self.window.dirty = false;
     }
 

crates/gpui2_macros/src/derive_component.rs 🔗

@@ -28,9 +28,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
     let (_, ty_generics, _) = ast.generics.split_for_impl();
 
     let expanded = quote! {
-        impl #impl_generics gpui2::Component<#view_type> for #name #ty_generics #where_clause {
-            fn render(self) -> gpui2::AnyElement<#view_type> {
-                (move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, #view_type>| self.render(view_state, cx))
+        impl #impl_generics gpui::Component<#view_type> for #name #ty_generics #where_clause {
+            fn render(self) -> gpui::AnyElement<#view_type> {
+                (move |view_state: &mut #view_type, cx: &mut gpui::ViewContext<'_, #view_type>| self.render(view_state, cx))
                     .render()
             }
         }

crates/gpui2_macros/src/style_helpers.rs 🔗

@@ -123,7 +123,7 @@ fn generate_predefined_setter(
         .iter()
         .map(|field_tokens| {
             quote! {
-                style.#field_tokens = Some((#negation_token gpui2::#length_tokens).into());
+                style.#field_tokens = Some((#negation_token gpui::#length_tokens).into());
             }
         })
         .collect::<Vec<_>>();
@@ -163,7 +163,7 @@ fn generate_custom_value_setter(
 
     let method = quote! {
         #[doc = #doc_string]
-        fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui2::#length_type>) -> Self where Self: std::marker::Sized {
+        fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui::#length_type>) -> Self where Self: std::marker::Sized {
             let style = self.style();
             #(#field_assignments)*
             self

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -170,6 +170,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     #max_retries,
                     #detect_nondeterminism,
                     &mut |cx, foreground_platform, deterministic, seed| {
+                        // some of the macro contents do not use all variables, silence the warnings
+                        let _ = (&cx, &foreground_platform, &deterministic, &seed);
                         #cx_vars
                         cx.foreground().run(#inner_fn_name(#inner_fn_args));
                         #cx_teardowns
@@ -247,6 +249,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     #max_retries,
                     #detect_nondeterminism,
                     &mut |cx, foreground_platform, deterministic, seed| {
+                        // some of the macro contents do not use all variables, silence the warnings
+                        let _ = (&cx, &foreground_platform, &deterministic, &seed);
                         #cx_vars
                         #inner_fn_name(#inner_fn_args);
                         #cx_teardowns

crates/live_kit_client2/Cargo.toml 🔗

@@ -16,14 +16,14 @@ name = "test_app"
 test-support = [
     "async-trait",
     "collections/test-support",
-    "gpui2/test-support",
+    "gpui/test-support",
     "live_kit_server",
     "nanoid",
 ]
 
 [dependencies]
 collections = { path = "../collections", optional = true }
-gpui2 = { package = "gpui2", path = "../gpui2", optional = true }
+gpui = { package = "gpui2", path = "../gpui2", optional = true }
 live_kit_server = { path = "../live_kit_server", optional = true }
 media = { path = "../media" }
 
@@ -41,7 +41,7 @@ nanoid = { version ="0.4", optional = true}
 
 [dev-dependencies]
 collections = { path = "../collections", features = ["test-support"] }
-gpui2 = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 live_kit_server = { path = "../live_kit_server" }
 media = { path = "../media" }
 nanoid = "0.4"

crates/live_kit_client2/examples/test_app.rs 🔗

@@ -1,7 +1,7 @@
 use std::{sync::Arc, time::Duration};
 
 use futures::StreamExt;
-use gpui2::KeyBinding;
+use gpui::KeyBinding;
 use live_kit_client2::{
     LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
 };
@@ -16,7 +16,7 @@ struct Quit;
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
-    gpui2::App::production(Arc::new(())).run(|cx| {
+    gpui::App::production(Arc::new(())).run(|cx| {
         #[cfg(any(test, feature = "test-support"))]
         println!("USING TEST LIVEKIT");
 
@@ -173,6 +173,6 @@ fn main() {
     });
 }
 
-fn quit(_: &Quit, cx: &mut gpui2::AppContext) {
+fn quit(_: &Quit, cx: &mut gpui::AppContext) {
     cx.quit();
 }

crates/live_kit_client2/src/test.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use collections::{BTreeMap, HashMap};
 use futures::Stream;
-use gpui2::BackgroundExecutor;
+use gpui::BackgroundExecutor;
 use live_kit_server::token;
 use media::core_video::CVImageBuffer;
 use parking_lot::Mutex;

crates/storybook2/Cargo.toml 🔗

@@ -14,7 +14,7 @@ anyhow.workspace = true
 backtrace-on-stack-overflow = "0.3.0"
 clap = { version = "4.4", features = ["derive", "string"] }
 chrono = "0.4"
-gpui2 = { path = "../gpui2" }
+gpui = { package = "gpui2", path = "../gpui2" }
 itertools = "0.11.0"
 log.workspace = true
 rust-embed.workspace = true
@@ -29,4 +29,4 @@ ui = { package = "ui2", path = "../ui2", features = ["stories"] }
 util = { path = "../util" }
 
 [dev-dependencies]
-gpui2 = { path = "../gpui2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

crates/storybook2/src/assets.rs 🔗

@@ -1,7 +1,7 @@
 use std::borrow::Cow;
 
 use anyhow::{anyhow, Result};
-use gpui2::{AssetSource, SharedString};
+use gpui::{AssetSource, SharedString};
 use rust_embed::RustEmbed;
 
 #[derive(RustEmbed)]

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

@@ -1,5 +1,5 @@
 use crate::story::Story;
-use gpui2::{px, Div, Render};
+use gpui::{px, Div, Render};
 use theme2::{default_color_scales, ColorScaleStep};
 use ui::prelude::*;
 
@@ -20,7 +20,7 @@ impl Render for ColorsStory {
                     .flex_col()
                     .gap_1()
                     .overflow_y_scroll()
-                    .text_color(gpui2::white())
+                    .text_color(gpui::white())
                     .children(color_scales.into_iter().map(|scale| {
                         div()
                             .flex()

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

@@ -1,4 +1,4 @@
-use gpui2::{
+use gpui::{
     div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteraction,
     StatelessInteractive, Styled, View, VisualContext, WindowContext,
 };
@@ -33,7 +33,7 @@ impl FocusStory {
 impl Render for FocusStory {
     type Element = Div<Self, StatefulInteraction<Self>, FocusEnabled<Self>>;
 
-    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
         let color_1 = theme.styles.git.created;
         let color_2 = theme.styles.git.modified;

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

@@ -1,5 +1,5 @@
 use crate::{story::Story, story_selector::ComponentStory};
-use gpui2::{Div, Render, StatefulInteraction, View, VisualContext};
+use gpui::{Div, Render, StatefulInteraction, View, VisualContext};
 use strum::IntoEnumIterator;
 use ui::prelude::*;
 

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

@@ -1,4 +1,4 @@
-use gpui2::{
+use gpui::{
     div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled,
     View, VisualContext, WindowContext,
 };
@@ -15,7 +15,7 @@ impl ScrollStory {
 impl Render for ScrollStory {
     type Element = Div<Self, StatefulInteraction<Self>>;
 
-    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
         let color_1 = theme.styles.git.created;
         let color_2 = theme.styles.git.modified;

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

@@ -1,4 +1,4 @@
-use gpui2::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext};
+use gpui::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext};
 
 pub struct TextStory;
 
@@ -11,7 +11,7 @@ impl TextStory {
 impl Render for TextStory {
     type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         div().size_full().bg(white()).child(concat!(
             "The quick brown fox jumps over the lazy dog. ",
             "Meanwhile, the lazy dog decided it was time for a change. ",

crates/storybook2/src/story_selector.rs 🔗

@@ -5,7 +5,7 @@ use crate::stories::*;
 use anyhow::anyhow;
 use clap::builder::PossibleValue;
 use clap::ValueEnum;
-use gpui2::{AnyView, VisualContext};
+use gpui::{AnyView, VisualContext};
 use strum::{EnumIter, EnumString, IntoEnumIterator};
 use ui::prelude::*;
 use ui::{AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory};
@@ -19,6 +19,7 @@ pub enum ComponentStory {
     Buffer,
     Button,
     ChatPanel,
+    Checkbox,
     CollabPanel,
     Colors,
     CommandPalette,
@@ -61,6 +62,7 @@ impl ComponentStory {
             Self::Buffer => cx.build_view(|_| ui::BufferStory).into(),
             Self::Button => cx.build_view(|_| ButtonStory).into(),
             Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into(),
+            Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
             Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into(),
             Self::Colors => cx.build_view(|_| ColorsStory).into(),
             Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into(),

crates/storybook2/src/storybook2.rs 🔗

@@ -8,7 +8,7 @@ mod story_selector;
 use std::sync::Arc;
 
 use clap::Parser;
-use gpui2::{
+use gpui::{
     div, px, size, AnyView, AppContext, Bounds, Div, Render, ViewContext, VisualContext,
     WindowBounds, WindowOptions,
 };
@@ -22,7 +22,7 @@ use ui::prelude::*;
 use crate::assets::Assets;
 use crate::story_selector::StorySelector;
 
-// gpui2::actions! {
+// gpui::actions! {
 //     storybook,
 //     [ToggleInspector]
 // }
@@ -51,7 +51,7 @@ fn main() {
     let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string());
 
     let asset_source = Arc::new(Assets);
-    gpui2::App::production(asset_source).run(move |cx| {
+    gpui::App::production(asset_source).run(move |cx| {
         load_embedded_fonts(cx).unwrap();
 
         let mut store = SettingsStore::default();
@@ -116,7 +116,7 @@ impl Render for StoryWrapper {
     }
 }
 
-fn load_embedded_fonts(cx: &AppContext) -> gpui2::Result<()> {
+fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> {
     let font_paths = cx.asset_source().list("fonts")?;
     let mut embedded_fonts = Vec::new();
     for font_path in font_paths {

crates/terminal2/src/mappings/colors.rs 🔗

@@ -1,6 +1,6 @@
 // todo!()
 use alacritty_terminal::term::color::Rgb as AlacRgb;
-// use gpui2::color::Color;
+// use gpui::color::Color;
 // use theme2::TerminalStyle;
 
 ///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent

crates/theme2/src/colors.rs 🔗

@@ -54,18 +54,20 @@ pub struct ThemeColors {
     pub border: Hsla,
     pub border_variant: Hsla,
     pub border_focused: Hsla,
+    pub border_selected: Hsla,
     pub border_transparent: Hsla,
-    pub elevated_surface: Hsla,
-    pub surface: Hsla,
+    pub border_disabled: Hsla,
+    pub elevated_surface_background: Hsla,
+    pub surface_background: Hsla,
     pub background: Hsla,
-    pub element: Hsla,
+    pub element_background: Hsla,
     pub element_hover: Hsla,
     pub element_active: Hsla,
     pub element_selected: Hsla,
     pub element_disabled: Hsla,
     pub element_placeholder: Hsla,
     pub element_drop_target: Hsla,
-    pub ghost_element: Hsla,
+    pub ghost_element_background: Hsla,
     pub ghost_element_hover: Hsla,
     pub ghost_element_active: Hsla,
     pub ghost_element_selected: Hsla,
@@ -80,15 +82,32 @@ pub struct ThemeColors {
     pub icon_disabled: Hsla,
     pub icon_placeholder: Hsla,
     pub icon_accent: Hsla,
-    pub status_bar: Hsla,
-    pub title_bar: Hsla,
-    pub toolbar: Hsla,
-    pub tab_bar: Hsla,
-    pub tab_inactive: Hsla,
-    pub tab_active: Hsla,
-    pub editor: Hsla,
-    pub editor_subheader: Hsla,
+    pub status_bar_background: Hsla,
+    pub title_bar_background: Hsla,
+    pub toolbar_background: Hsla,
+    pub tab_bar_background: Hsla,
+    pub tab_inactive_background: Hsla,
+    pub tab_active_background: Hsla,
+    pub editor_background: Hsla,
+    pub editor_subheader_background: Hsla,
     pub editor_active_line: Hsla,
+    pub terminal_background: Hsla,
+    pub terminal_ansi_bright_black: Hsla,
+    pub terminal_ansi_bright_red: Hsla,
+    pub terminal_ansi_bright_green: Hsla,
+    pub terminal_ansi_bright_yellow: Hsla,
+    pub terminal_ansi_bright_blue: Hsla,
+    pub terminal_ansi_bright_magenta: Hsla,
+    pub terminal_ansi_bright_cyan: Hsla,
+    pub terminal_ansi_bright_white: Hsla,
+    pub terminal_ansi_black: Hsla,
+    pub terminal_ansi_red: Hsla,
+    pub terminal_ansi_green: Hsla,
+    pub terminal_ansi_yellow: Hsla,
+    pub terminal_ansi_blue: Hsla,
+    pub terminal_ansi_magenta: Hsla,
+    pub terminal_ansi_cyan: Hsla,
+    pub terminal_ansi_white: Hsla,
 }
 
 #[derive(Refineable, Clone)]

crates/theme2/src/default_colors.rs 🔗

@@ -205,18 +205,20 @@ impl ThemeColors {
             border: neutral().light().step_6(),
             border_variant: neutral().light().step_5(),
             border_focused: blue().light().step_5(),
+            border_disabled: neutral().light().step_3(),
+            border_selected: blue().light().step_5(),
             border_transparent: system.transparent,
-            elevated_surface: neutral().light().step_2(),
-            surface: neutral().light().step_2(),
+            elevated_surface_background: neutral().light().step_2(),
+            surface_background: neutral().light().step_2(),
             background: neutral().light().step_1(),
-            element: neutral().light().step_3(),
+            element_background: neutral().light().step_3(),
             element_hover: neutral().light().step_4(),
             element_active: neutral().light().step_5(),
             element_selected: neutral().light().step_5(),
             element_disabled: neutral().light_alpha().step_3(),
             element_placeholder: neutral().light().step_11(),
             element_drop_target: blue().light_alpha().step_2(),
-            ghost_element: system.transparent,
+            ghost_element_background: system.transparent,
             ghost_element_hover: neutral().light().step_4(),
             ghost_element_active: neutral().light().step_5(),
             ghost_element_selected: neutral().light().step_5(),
@@ -231,15 +233,32 @@ impl ThemeColors {
             icon_disabled: neutral().light().step_9(),
             icon_placeholder: neutral().light().step_10(),
             icon_accent: blue().light().step_11(),
-            status_bar: neutral().light().step_2(),
-            title_bar: neutral().light().step_2(),
-            toolbar: neutral().light().step_1(),
-            tab_bar: neutral().light().step_2(),
-            tab_active: neutral().light().step_1(),
-            tab_inactive: neutral().light().step_2(),
-            editor: neutral().light().step_1(),
-            editor_subheader: neutral().light().step_2(),
+            status_bar_background: neutral().light().step_2(),
+            title_bar_background: neutral().light().step_2(),
+            toolbar_background: neutral().light().step_1(),
+            tab_bar_background: neutral().light().step_2(),
+            tab_active_background: neutral().light().step_1(),
+            tab_inactive_background: neutral().light().step_2(),
+            editor_background: neutral().light().step_1(),
+            editor_subheader_background: neutral().light().step_2(),
             editor_active_line: neutral().light_alpha().step_3(),
+            terminal_background: neutral().light().step_1(),
+            terminal_ansi_black: black().light().step_12(),
+            terminal_ansi_red: red().light().step_11(),
+            terminal_ansi_green: green().light().step_11(),
+            terminal_ansi_yellow: yellow().light().step_11(),
+            terminal_ansi_blue: blue().light().step_11(),
+            terminal_ansi_magenta: violet().light().step_11(),
+            terminal_ansi_cyan: cyan().light().step_11(),
+            terminal_ansi_white: neutral().light().step_12(),
+            terminal_ansi_bright_black: black().light().step_11(),
+            terminal_ansi_bright_red: red().light().step_10(),
+            terminal_ansi_bright_green: green().light().step_10(),
+            terminal_ansi_bright_yellow: yellow().light().step_10(),
+            terminal_ansi_bright_blue: blue().light().step_10(),
+            terminal_ansi_bright_magenta: violet().light().step_10(),
+            terminal_ansi_bright_cyan: cyan().light().step_10(),
+            terminal_ansi_bright_white: neutral().light().step_11(),
         }
     }
 
@@ -250,18 +269,20 @@ impl ThemeColors {
             border: neutral().dark().step_6(),
             border_variant: neutral().dark().step_5(),
             border_focused: blue().dark().step_5(),
+            border_disabled: neutral().dark().step_3(),
+            border_selected: blue().dark().step_5(),
             border_transparent: system.transparent,
-            elevated_surface: neutral().dark().step_2(),
-            surface: neutral().dark().step_2(),
+            elevated_surface_background: neutral().dark().step_2(),
+            surface_background: neutral().dark().step_2(),
             background: neutral().dark().step_1(),
-            element: neutral().dark().step_3(),
+            element_background: neutral().dark().step_3(),
             element_hover: neutral().dark().step_4(),
             element_active: neutral().dark().step_5(),
             element_selected: neutral().dark().step_5(),
             element_disabled: neutral().dark_alpha().step_3(),
             element_placeholder: neutral().dark().step_11(),
             element_drop_target: blue().dark_alpha().step_2(),
-            ghost_element: system.transparent,
+            ghost_element_background: system.transparent,
             ghost_element_hover: neutral().dark().step_4(),
             ghost_element_active: neutral().dark().step_5(),
             ghost_element_selected: neutral().dark().step_5(),
@@ -276,15 +297,32 @@ impl ThemeColors {
             icon_disabled: neutral().dark().step_9(),
             icon_placeholder: neutral().dark().step_10(),
             icon_accent: blue().dark().step_11(),
-            status_bar: neutral().dark().step_2(),
-            title_bar: neutral().dark().step_2(),
-            toolbar: neutral().dark().step_1(),
-            tab_bar: neutral().dark().step_2(),
-            tab_active: neutral().dark().step_1(),
-            tab_inactive: neutral().dark().step_2(),
-            editor: neutral().dark().step_1(),
-            editor_subheader: neutral().dark().step_2(),
+            status_bar_background: neutral().dark().step_2(),
+            title_bar_background: neutral().dark().step_2(),
+            toolbar_background: neutral().dark().step_1(),
+            tab_bar_background: neutral().dark().step_2(),
+            tab_active_background: neutral().dark().step_1(),
+            tab_inactive_background: neutral().dark().step_2(),
+            editor_background: neutral().dark().step_1(),
+            editor_subheader_background: neutral().dark().step_2(),
             editor_active_line: neutral().dark_alpha().step_3(),
+            terminal_background: neutral().dark().step_1(),
+            terminal_ansi_black: black().dark().step_12(),
+            terminal_ansi_red: red().dark().step_11(),
+            terminal_ansi_green: green().dark().step_11(),
+            terminal_ansi_yellow: yellow().dark().step_11(),
+            terminal_ansi_blue: blue().dark().step_11(),
+            terminal_ansi_magenta: violet().dark().step_11(),
+            terminal_ansi_cyan: cyan().dark().step_11(),
+            terminal_ansi_white: neutral().dark().step_12(),
+            terminal_ansi_bright_black: black().dark().step_11(),
+            terminal_ansi_bright_red: red().dark().step_10(),
+            terminal_ansi_bright_green: green().dark().step_10(),
+            terminal_ansi_bright_yellow: yellow().dark().step_10(),
+            terminal_ansi_bright_blue: blue().dark().step_10(),
+            terminal_ansi_bright_magenta: violet().dark().step_10(),
+            terminal_ansi_bright_cyan: cyan().dark().step_10(),
+            terminal_ansi_bright_white: neutral().dark().step_11(),
         }
     }
 }

crates/theme2/src/theme2.rs 🔗

@@ -17,7 +17,7 @@ pub use syntax::*;
 
 use gpui::{AppContext, Hsla, SharedString};
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, PartialEq, Clone, Copy)]
 pub enum Appearance {
     Light,
     Dark,

crates/ui2/Cargo.toml 🔗

@@ -7,7 +7,7 @@ publish = false
 [dependencies]
 anyhow.workspace = true
 chrono = "0.4"
-gpui2 = { path = "../gpui2" }
+gpui = { package = "gpui2", path = "../gpui2" }
 itertools = { version = "0.11.0", optional = true }
 serde.workspace = true
 settings2 = { path = "../settings2" }

crates/ui2/docs/building-ui.md 🔗

@@ -2,6 +2,16 @@
 
 ## Common patterns
 
+### Method ordering
+
+- id
+- Flex properties
+- Position properties
+- Size properties
+- Style properties
+- Handlers
+- State properties
+
 ### Using the Label Component to Create UI Text
 
 The `Label` component helps in displaying text on user interfaces. It creates an interface where specific parameters such as label color, line height style, and strikethrough can be set.

crates/ui2/docs/hello-world.md 🔗

@@ -40,12 +40,12 @@ impl<V: 'static> TodoList<V> {
 
 All of this is relatively straightforward.
 
-We use [gpui2::SharedString] in components instead of [std::string::String]. This allows us to [TODO: someone who actually knows please explain why we use SharedString].
+We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to [TODO: someone who actually knows please explain why we use SharedString].
 
 When we want to pass an action we pass a `ClickHandler`. Whenever we want to add an action, the struct it belongs to needs to be generic over the view type `V`.
 
 ~~~rust
-use gpui2::hsla
+use gpui::hsla
 
 impl<V: 'static> TodoList<V> {
     // ...
@@ -74,7 +74,7 @@ As you start using the Tailwind-style conventions you will be surprised how quic
 
 **Why `50.0/360.0` in `hsla()`?**
 
-gpui [gpui2::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue.
+gpui [gpui::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue.
 
 This may change in the future, but this is a little trick that let's you use familiar looking values.
 
@@ -98,7 +98,7 @@ impl<V: 'static> TodoList<V> {
 Now we have access to the complete set of colors defined in the theme.
 
 ~~~rust
-use gpui2::hsla
+use gpui::hsla
 
 impl<V: 'static> TodoList<V> {
     // ...
@@ -113,7 +113,7 @@ impl<V: 'static> TodoList<V> {
 Let's finish up some basic styles for the container then move on to adding the other elements.
 
 ~~~rust
-use gpui2::hsla
+use gpui::hsla
 
 impl<V: 'static> TodoList<V> {
     // ...

crates/ui2/src/components.rs 🔗

@@ -1,5 +1,6 @@
 mod avatar;
 mod button;
+mod checkbox;
 mod context_menu;
 mod details;
 mod facepile;
@@ -16,13 +17,17 @@ mod palette;
 mod panel;
 mod player;
 mod player_stack;
+mod slot;
 mod stack;
 mod tab;
 mod toast;
+mod toggle;
 mod tool_divider;
+mod tooltip;
 
 pub use avatar::*;
 pub use button::*;
+pub use checkbox::*;
 pub use context_menu::*;
 pub use details::*;
 pub use facepile::*;
@@ -39,7 +44,10 @@ pub use palette::*;
 pub use panel::*;
 pub use player::*;
 pub use player_stack::*;
+pub use slot::*;
 pub use stack::*;
 pub use tab::*;
 pub use toast::*;
+pub use toggle::*;
 pub use tool_divider::*;
+pub use tooltip::*;

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

@@ -1,4 +1,4 @@
-use gpui2::img;
+use gpui::img;
 
 use crate::prelude::*;
 
@@ -33,7 +33,7 @@ impl Avatar {
         img.uri(self.src.clone())
             .size_4()
             // todo!(Pull the avatar fallback background from the theme.)
-            .bg(gpui2::red())
+            .bg(gpui::red())
     }
 }
 
@@ -44,7 +44,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct AvatarStory;
 

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

@@ -1,9 +1,28 @@
 use std::sync::Arc;
 
-use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext};
+use gpui::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext};
 
-use crate::prelude::*;
 use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle};
+use crate::{prelude::*, IconButton};
+
+/// Provides the flexibility to use either a standard
+/// button or an icon button in a given context.
+pub enum ButtonOrIconButton<V: 'static> {
+    Button(Button<V>),
+    IconButton(IconButton<V>),
+}
+
+impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
+    fn from(value: Button<V>) -> Self {
+        Self::Button(value)
+    }
+}
+
+impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
+    fn from(value: IconButton<V>) -> Self {
+        Self::IconButton(value)
+    }
+}
 
 #[derive(Default, PartialEq, Clone, Copy)]
 pub enum IconPosition {
@@ -22,8 +41,8 @@ pub enum ButtonVariant {
 impl ButtonVariant {
     pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla {
         match self {
-            ButtonVariant::Ghost => cx.theme().colors().ghost_element,
-            ButtonVariant::Filled => cx.theme().colors().element,
+            ButtonVariant::Ghost => cx.theme().colors().ghost_element_background,
+            ButtonVariant::Filled => cx.theme().colors().element_background,
         }
     }
 
@@ -42,7 +61,7 @@ impl ButtonVariant {
     }
 }
 
-pub type ClickHandler<S> = Arc<dyn Fn(&mut S, &mut ViewContext<S>) + Send + Sync>;
+pub type ClickHandler<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) + Send + Sync>;
 
 struct ButtonHandlers<V: 'static> {
     click: Option<ClickHandler<V>>,
@@ -215,7 +234,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{h_stack, v_stack, LabelColor, Story};
-    use gpui2::{rems, Div, Render};
+    use gpui::{rems, Div, Render};
     use strum::IntoEnumIterator;
 
     pub struct ButtonStory;

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

@@ -0,0 +1,229 @@
+use std::sync::Arc;
+
+use gpui::{
+    div, Component, ElementId, ParentElement, StatefulInteractive, StatelessInteractive, Styled,
+    ViewContext,
+};
+use theme2::ActiveTheme;
+
+use crate::{Icon, IconColor, IconElement, Selection};
+
+pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) + Send + Sync>;
+
+/// # Checkbox
+///
+/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
+/// Each checkbox works independently from other checkboxes in the list,
+/// therefore checking an additional box does not affect any other selections.
+#[derive(Component)]
+pub struct Checkbox<V: 'static> {
+    id: ElementId,
+    checked: Selection,
+    disabled: bool,
+    on_click: Option<CheckHandler<V>>,
+}
+
+impl<V: 'static> Checkbox<V> {
+    pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
+        Self {
+            id: id.into(),
+            checked,
+            disabled: false,
+            on_click: None,
+        }
+    }
+
+    pub fn disabled(mut self, disabled: bool) -> Self {
+        self.disabled = disabled;
+        self
+    }
+
+    pub fn on_click(
+        mut self,
+        handler: impl 'static + Fn(Selection, &mut V, &mut ViewContext<V>) + Send + Sync,
+    ) -> Self {
+        self.on_click = Some(Arc::new(handler));
+        self
+    }
+
+    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+        let group_id = format!("checkbox_group_{:?}", self.id);
+
+        let icon = match self.checked {
+            // When selected, we show a checkmark.
+            Selection::Selected => {
+                Some(
+                    IconElement::new(Icon::Check)
+                        .size(crate::IconSize::Small)
+                        .color(
+                            // If the checkbox is disabled we change the color of the icon.
+                            if self.disabled {
+                                IconColor::Disabled
+                            } else {
+                                IconColor::Selected
+                            },
+                        ),
+                )
+            }
+            // In an indeterminate state, we show a dash.
+            Selection::Indeterminate => {
+                Some(
+                    IconElement::new(Icon::Dash)
+                        .size(crate::IconSize::Small)
+                        .color(
+                            // If the checkbox is disabled we change the color of the icon.
+                            if self.disabled {
+                                IconColor::Disabled
+                            } else {
+                                IconColor::Selected
+                            },
+                        ),
+                )
+            }
+            // When unselected, we show nothing.
+            Selection::Unselected => None,
+        };
+
+        // A checkbox could be in an indeterminate state,
+        // for example the indeterminate state could represent:
+        //  - a group of options of which only some are selected
+        //  - an enabled option that is no longer available
+        //  - a previously agreed to license that has been updated
+        //
+        // For the sake of styles we treat the indeterminate state as selected,
+        // but it's icon will be different.
+        let selected =
+            self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
+
+        // We could use something like this to make the checkbox background when selected:
+        //
+        // ~~~rust
+        // ...
+        // .when(selected, |this| {
+        //     this.bg(cx.theme().colors().element_selected)
+        // })
+        // ~~~
+        //
+        // But we use a match instead here because the checkbox might be disabled,
+        // and it could be disabled _while_ it is selected, as well as while it is not selected.
+        let (bg_color, border_color) = match (self.disabled, selected) {
+            (true, _) => (
+                cx.theme().colors().ghost_element_disabled,
+                cx.theme().colors().border_disabled,
+            ),
+            (false, true) => (
+                cx.theme().colors().element_selected,
+                cx.theme().colors().border,
+            ),
+            (false, false) => (
+                cx.theme().colors().element_background,
+                cx.theme().colors().border,
+            ),
+        };
+
+        div()
+            .id(self.id)
+            // Rather than adding `px_1()` to add some space around the checkbox,
+            // we use a larger parent element to create a slightly larger
+            // click area for the checkbox.
+            .size_5()
+            // Because we've enlarged the click area, we need to create a
+            // `group` to pass down interaction events to the checkbox.
+            .group(group_id.clone())
+            .child(
+                div()
+                    .flex()
+                    // This prevent the flex element from growing
+                    // or shrinking in response to any size changes
+                    .flex_none()
+                    // The combo of `justify_center()` and `items_center()`
+                    // is used frequently to center elements in a flex container.
+                    //
+                    // We use this to center the icon in the checkbox.
+                    .justify_center()
+                    .items_center()
+                    .m_1()
+                    .size_4()
+                    .rounded_sm()
+                    .bg(bg_color)
+                    .border()
+                    .border_color(border_color)
+                    // We only want the interaction states to fire when we
+                    // are in a checkbox that isn't disabled.
+                    .when(!self.disabled, |this| {
+                        // Here instead of `hover()` we use `group_hover()`
+                        // to pass it the group id.
+                        this.group_hover(group_id.clone(), |el| {
+                            el.bg(cx.theme().colors().element_hover)
+                        })
+                    })
+                    .children(icon),
+            )
+            .when_some(
+                self.on_click.filter(|_| !self.disabled),
+                |this, on_click| {
+                    this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
+                },
+            )
+    }
+}
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use super::*;
+    use crate::{h_stack, Story};
+    use gpui::{Div, Render};
+
+    pub struct CheckboxStory;
+
+    impl Render for CheckboxStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            Story::container(cx)
+                .child(Story::title_for::<_, Checkbox<Self>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(
+                    h_stack()
+                        .p_2()
+                        .gap_2()
+                        .rounded_md()
+                        .border()
+                        .border_color(cx.theme().colors().border)
+                        .child(Checkbox::new("checkbox-enabled", Selection::Unselected))
+                        .child(Checkbox::new(
+                            "checkbox-intermediate",
+                            Selection::Indeterminate,
+                        ))
+                        .child(Checkbox::new("checkbox-selected", Selection::Selected)),
+                )
+                .child(Story::label(cx, "Disabled"))
+                .child(
+                    h_stack()
+                        .p_2()
+                        .gap_2()
+                        .rounded_md()
+                        .border()
+                        .border_color(cx.theme().colors().border)
+                        .child(
+                            Checkbox::new("checkbox-disabled", Selection::Unselected)
+                                .disabled(true),
+                        )
+                        .child(
+                            Checkbox::new(
+                                "checkbox-disabled-intermediate",
+                                Selection::Indeterminate,
+                            )
+                            .disabled(true),
+                        )
+                        .child(
+                            Checkbox::new("checkbox-disabled-selected", Selection::Selected)
+                                .disabled(true),
+                        ),
+                )
+        }
+    }
+}

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

@@ -8,7 +8,7 @@ pub enum ContextMenuItem {
 }
 
 impl ContextMenuItem {
-    fn to_list_item<V: 'static>(self) -> ListItem<V> {
+    fn to_list_item<V: 'static>(self) -> ListItem {
         match self {
             ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
             ContextMenuItem::Entry(label) => {
@@ -46,18 +46,15 @@ impl ContextMenu {
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         v_stack()
             .flex()
-            .bg(cx.theme().colors().elevated_surface)
+            .bg(cx.theme().colors().elevated_surface_background)
             .border()
             .border_color(cx.theme().colors().border)
-            .child(
-                List::new(
-                    self.items
-                        .into_iter()
-                        .map(ContextMenuItem::to_list_item)
-                        .collect(),
-                )
-                .toggle(ToggleState::Toggled),
-            )
+            .child(List::new(
+                self.items
+                    .into_iter()
+                    .map(ContextMenuItem::to_list_item::<V>)
+                    .collect(),
+            ))
     }
 }
 
@@ -68,7 +65,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::story::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct ContextMenuStory;
 

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

@@ -47,7 +47,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{Button, Story};
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct DetailsStory;
 

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

@@ -33,7 +33,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{static_players, Story};
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct FacepileStory;
 

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

@@ -1,4 +1,4 @@
-use gpui2::{rems, svg, Hsla};
+use gpui::{rems, svg, Hsla};
 use strum::EnumIter;
 
 use crate::prelude::*;
@@ -22,6 +22,7 @@ pub enum IconColor {
     Warning,
     Success,
     Info,
+    Selected,
 }
 
 impl IconColor {
@@ -36,6 +37,7 @@ impl IconColor {
             IconColor::Warning => cx.theme().status().warning,
             IconColor::Success => cx.theme().status().success,
             IconColor::Info => cx.theme().status().info,
+            IconColor::Selected => cx.theme().colors().icon_accent,
         }
     }
 }
@@ -55,6 +57,7 @@ pub enum Icon {
     ChevronRight,
     ChevronUp,
     Close,
+    Dash,
     Exit,
     ExclamationTriangle,
     File,
@@ -112,6 +115,7 @@ impl Icon {
             Icon::ChevronRight => "icons/chevron_right.svg",
             Icon::ChevronUp => "icons/chevron_up.svg",
             Icon::Close => "icons/x.svg",
+            Icon::Dash => "icons/dash.svg",
             Icon::Exit => "icons/exit.svg",
             Icon::ExclamationTriangle => "icons/warning.svg",
             Icon::File => "icons/file.svg",
@@ -182,7 +186,6 @@ impl IconElement {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let fill = self.color.color(cx);
         let svg_size = match self.size {
             IconSize::Small => rems(0.75),
             IconSize::Medium => rems(0.9375),
@@ -192,7 +195,7 @@ impl IconElement {
             .size(svg_size)
             .flex_none()
             .path(self.icon.path())
-            .text_color(fill)
+            .text_color(self.color.color(cx))
     }
 }
 
@@ -201,7 +204,7 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
     use strum::IntoEnumIterator;
 
     use crate::Story;

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

@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use gpui2::{rems, MouseButton};
+use gpui::{rems, MouseButton};
 
 use crate::{h_stack, prelude::*};
 use crate::{ClickHandler, Icon, IconColor, IconElement};
@@ -73,12 +73,12 @@ impl<V: 'static> IconButton<V> {
 
         let (bg_color, bg_hover_color, bg_active_color) = match self.variant {
             ButtonVariant::Filled => (
-                cx.theme().colors().element,
+                cx.theme().colors().element_background,
                 cx.theme().colors().element_hover,
                 cx.theme().colors().element_active,
             ),
             ButtonVariant::Ghost => (
-                cx.theme().colors().ghost_element,
+                cx.theme().colors().ghost_element_background,
                 cx.theme().colors().ghost_element_hover,
                 cx.theme().colors().ghost_element_active,
             ),

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

@@ -1,4 +1,4 @@
-use gpui2::px;
+use gpui::px;
 
 use crate::prelude::*;
 
@@ -14,7 +14,7 @@ impl UnreadIndicator {
         div()
             .rounded_full()
             .border_2()
-            .border_color(cx.theme().colors().surface)
+            .border_color(cx.theme().colors().surface_background)
             .w(px(9.0))
             .h(px(9.0))
             .z_index(2)

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

@@ -59,12 +59,12 @@ impl Input {
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
             InputVariant::Ghost => (
-                cx.theme().colors().ghost_element,
+                cx.theme().colors().ghost_element_background,
                 cx.theme().colors().ghost_element_hover,
                 cx.theme().colors().ghost_element_active,
             ),
             InputVariant::Filled => (
-                cx.theme().colors().element,
+                cx.theme().colors().element_background,
                 cx.theme().colors().element_hover,
                 cx.theme().colors().element_active,
             ),
@@ -111,7 +111,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct InputStory;
 

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

@@ -66,7 +66,7 @@ impl Key {
             .rounded_md()
             .text_sm()
             .text_color(cx.theme().colors().text)
-            .bg(cx.theme().colors().element)
+            .bg(cx.theme().colors().element_background)
             .child(self.key.clone())
     }
 }
@@ -158,7 +158,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
     use itertools::Itertools;
 
     pub struct KeybindingStory;

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

@@ -1,4 +1,4 @@
-use gpui2::{relative, rems, Hsla, WindowContext};
+use gpui::{relative, rems, Hsla, WindowContext};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -194,7 +194,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct LabelStory;
 

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

@@ -1,11 +1,11 @@
-use gpui2::{div, px, relative, Div};
+use gpui::div;
 
 use crate::settings::user_settings;
 use crate::{
-    h_stack, v_stack, Avatar, ClickHandler, Icon, IconColor, IconElement, IconSize, Label,
-    LabelColor,
+    disclosure_control, h_stack, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label,
+    LabelColor, Toggle,
 };
-use crate::{prelude::*, Button};
+use crate::{prelude::*, GraphicSlot};
 
 #[derive(Clone, Copy, Default, Debug, PartialEq)]
 pub enum ListItemVariant {
@@ -29,7 +29,7 @@ pub struct ListHeader {
     left_icon: Option<Icon>,
     meta: Option<ListHeaderMeta>,
     variant: ListItemVariant,
-    toggleable: Toggleable,
+    toggle: Toggle,
 }
 
 impl ListHeader {
@@ -39,17 +39,12 @@ impl ListHeader {
             left_icon: None,
             meta: None,
             variant: ListItemVariant::default(),
-            toggleable: Toggleable::NotToggleable,
+            toggle: Toggle::NotToggleable,
         }
     }
 
-    pub fn toggle(mut self, toggle: ToggleState) -> Self {
-        self.toggleable = toggle.into();
-        self
-    }
-
-    pub fn toggleable(mut self, toggleable: Toggleable) -> Self {
-        self.toggleable = toggleable;
+    pub fn toggle(mut self, toggle: Toggle) -> Self {
+        self.toggle = toggle;
         self
     }
 
@@ -63,30 +58,8 @@ impl ListHeader {
         self
     }
 
-    fn disclosure_control<V: 'static>(&self) -> Div<V> {
-        let is_toggleable = self.toggleable != Toggleable::NotToggleable;
-        let is_toggled = Toggleable::is_toggled(&self.toggleable);
-
-        match (is_toggleable, is_toggled) {
-            (false, _) => div(),
-            (_, true) => div().child(
-                IconElement::new(Icon::ChevronDown)
-                    .color(IconColor::Muted)
-                    .size(IconSize::Small),
-            ),
-            (_, false) => div().child(
-                IconElement::new(Icon::ChevronRight)
-                    .color(IconColor::Muted)
-                    .size(IconSize::Small),
-            ),
-        }
-    }
-
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let is_toggleable = self.toggleable != Toggleable::NotToggleable;
-        let is_toggled = self.toggleable.is_toggled();
-
-        let disclosure_control = self.disclosure_control();
+        let disclosure_control = disclosure_control(self.toggle);
 
         let meta = match self.meta {
             Some(ListHeaderMeta::Tools(icons)) => div().child(
@@ -106,7 +79,7 @@ impl ListHeader {
 
         h_stack()
             .w_full()
-            .bg(cx.theme().colors().surface)
+            .bg(cx.theme().colors().surface_background)
             // TODO: Add focus state
             // .when(self.state == InteractionState::Focused, |this| {
             //     this.border()
@@ -193,12 +166,6 @@ impl ListSubHeader {
     }
 }
 
-#[derive(Clone)]
-pub enum LeftContent {
-    Icon(Icon),
-    Avatar(SharedString),
-}
-
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum ListEntrySize {
     #[default]
@@ -207,44 +174,36 @@ pub enum ListEntrySize {
 }
 
 #[derive(Component)]
-pub enum ListItem<V: 'static> {
+pub enum ListItem {
     Entry(ListEntry),
-    Details(ListDetailsEntry<V>),
     Separator(ListSeparator),
     Header(ListSubHeader),
 }
 
-impl<V: 'static> From<ListEntry> for ListItem<V> {
+impl From<ListEntry> for ListItem {
     fn from(entry: ListEntry) -> Self {
         Self::Entry(entry)
     }
 }
 
-impl<V: 'static> From<ListDetailsEntry<V>> for ListItem<V> {
-    fn from(entry: ListDetailsEntry<V>) -> Self {
-        Self::Details(entry)
-    }
-}
-
-impl<V: 'static> From<ListSeparator> for ListItem<V> {
+impl From<ListSeparator> for ListItem {
     fn from(entry: ListSeparator) -> Self {
         Self::Separator(entry)
     }
 }
 
-impl<V: 'static> From<ListSubHeader> for ListItem<V> {
+impl From<ListSubHeader> for ListItem {
     fn from(entry: ListSubHeader) -> Self {
         Self::Header(entry)
     }
 }
 
-impl<V: 'static> ListItem<V> {
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+impl ListItem {
+    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         match self {
             ListItem::Entry(entry) => div().child(entry.render(view, cx)),
             ListItem::Separator(separator) => div().child(separator.render(view, cx)),
             ListItem::Header(header) => div().child(header.render(view, cx)),
-            ListItem::Details(details) => div().child(details.render(view, cx)),
         }
     }
 
@@ -263,31 +222,29 @@ impl<V: 'static> ListItem<V> {
 
 #[derive(Component)]
 pub struct ListEntry {
-    disclosure_control_style: DisclosureControlVisibility,
+    disabled: bool,
+    // TODO: Reintroduce this
+    // disclosure_control_style: DisclosureControlVisibility,
     indent_level: u32,
     label: Label,
-    left_content: Option<LeftContent>,
-    variant: ListItemVariant,
-    size: ListEntrySize,
-    state: InteractionState,
-    toggle: Option<ToggleState>,
+    left_slot: Option<GraphicSlot>,
     overflow: OverflowStyle,
+    size: ListEntrySize,
+    toggle: Toggle,
+    variant: ListItemVariant,
 }
 
 impl ListEntry {
     pub fn new(label: Label) -> Self {
         Self {
-            disclosure_control_style: DisclosureControlVisibility::default(),
+            disabled: false,
             indent_level: 0,
             label,
-            variant: ListItemVariant::default(),
-            left_content: None,
-            size: ListEntrySize::default(),
-            state: InteractionState::default(),
-            // TODO: Should use Toggleable::NotToggleable
-            // or remove Toggleable::NotToggleable from the system
-            toggle: None,
+            left_slot: None,
             overflow: OverflowStyle::Hidden,
+            size: ListEntrySize::default(),
+            toggle: Toggle::NotToggleable,
+            variant: ListItemVariant::default(),
         }
     }
 
@@ -301,28 +258,23 @@ impl ListEntry {
         self
     }
 
-    pub fn toggle(mut self, toggle: ToggleState) -> Self {
-        self.toggle = Some(toggle);
+    pub fn toggle(mut self, toggle: Toggle) -> Self {
+        self.toggle = toggle;
         self
     }
 
-    pub fn left_content(mut self, left_content: LeftContent) -> Self {
-        self.left_content = Some(left_content);
+    pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
+        self.left_slot = Some(left_content);
         self
     }
 
     pub fn left_icon(mut self, left_icon: Icon) -> Self {
-        self.left_content = Some(LeftContent::Icon(left_icon));
+        self.left_slot = Some(GraphicSlot::Icon(left_icon));
         self
     }
 
     pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self {
-        self.left_content = Some(LeftContent::Avatar(left_avatar.into()));
-        self
-    }
-
-    pub fn state(mut self, state: InteractionState) -> Self {
-        self.state = state;
+        self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
         self
     }
 
@@ -331,63 +283,19 @@ impl ListEntry {
         self
     }
 
-    pub fn disclosure_control_style(
-        mut self,
-        disclosure_control_style: DisclosureControlVisibility,
-    ) -> Self {
-        self.disclosure_control_style = disclosure_control_style;
-        self
-    }
-
-    fn label_color(&self) -> LabelColor {
-        match self.state {
-            InteractionState::Disabled => LabelColor::Disabled,
-            _ => Default::default(),
-        }
-    }
-
-    fn icon_color(&self) -> IconColor {
-        match self.state {
-            InteractionState::Disabled => IconColor::Disabled,
-            _ => Default::default(),
-        }
-    }
-
-    fn disclosure_control<V: 'static>(
-        &mut self,
-        cx: &mut ViewContext<V>,
-    ) -> Option<impl Component<V>> {
-        let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
-            IconElement::new(Icon::ChevronDown)
-        } else {
-            IconElement::new(Icon::ChevronRight)
-        }
-        .color(IconColor::Muted)
-        .size(IconSize::Small);
-
-        match (self.toggle, self.disclosure_control_style) {
-            (Some(_), DisclosureControlVisibility::OnHover) => {
-                Some(div().absolute().neg_left_5().child(disclosure_control_icon))
-            }
-            (Some(_), DisclosureControlVisibility::Always) => {
-                Some(div().child(disclosure_control_icon))
-            }
-            (None, _) => None,
-        }
-    }
-
-    fn render<V: 'static>(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         let settings = user_settings(cx);
 
-        let left_content = match self.left_content.clone() {
-            Some(LeftContent::Icon(i)) => Some(
+        let left_content = match self.left_slot.clone() {
+            Some(GraphicSlot::Icon(i)) => Some(
                 h_stack().child(
                     IconElement::new(i)
                         .size(IconSize::Small)
                         .color(IconColor::Muted),
                 ),
             ),
-            Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
+            Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
+            Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::new(src))),
             None => None,
         };
 
@@ -399,11 +307,8 @@ impl ListEntry {
         div()
             .relative()
             .group("")
-            .bg(cx.theme().colors().surface)
-            .when(self.state == InteractionState::Focused, |this| {
-                this.border()
-                    .border_color(cx.theme().colors().border_focused)
-            })
+            .bg(cx.theme().colors().surface_background)
+            // TODO: Add focus state
             .child(
                 sized_item
                     .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
@@ -425,131 +330,13 @@ impl ListEntry {
                     .gap_1()
                     .items_center()
                     .relative()
-                    .children(self.disclosure_control(cx))
+                    .child(disclosure_control(self.toggle))
                     .children(left_content)
                     .child(self.label),
             )
     }
 }
 
-struct ListDetailsEntryHandlers<V: 'static> {
-    click: Option<ClickHandler<V>>,
-}
-
-impl<V: 'static> Default for ListDetailsEntryHandlers<V> {
-    fn default() -> Self {
-        Self { click: None }
-    }
-}
-
-#[derive(Component)]
-pub struct ListDetailsEntry<V: 'static> {
-    label: SharedString,
-    meta: Option<SharedString>,
-    left_content: Option<LeftContent>,
-    handlers: ListDetailsEntryHandlers<V>,
-    actions: Option<Vec<Button<V>>>,
-    // TODO: make this more generic instead of
-    // specifically for notifications
-    seen: bool,
-}
-
-impl<V: 'static> ListDetailsEntry<V> {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            label: label.into(),
-            meta: None,
-            left_content: None,
-            handlers: ListDetailsEntryHandlers::default(),
-            actions: None,
-            seen: false,
-        }
-    }
-
-    pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
-        self.meta = Some(meta.into());
-        self
-    }
-
-    pub fn seen(mut self, seen: bool) -> Self {
-        self.seen = seen;
-        self
-    }
-
-    pub fn on_click(mut self, handler: ClickHandler<V>) -> Self {
-        self.handlers.click = Some(handler);
-        self
-    }
-
-    pub fn actions(mut self, actions: Vec<Button<V>>) -> Self {
-        self.actions = Some(actions);
-        self
-    }
-
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let settings = user_settings(cx);
-
-        let (item_bg, item_bg_hover, item_bg_active) = (
-            cx.theme().colors().ghost_element,
-            cx.theme().colors().ghost_element_hover,
-            cx.theme().colors().ghost_element_active,
-        );
-
-        let label_color = match self.seen {
-            true => LabelColor::Muted,
-            false => LabelColor::Default,
-        };
-
-        div()
-            .relative()
-            .group("")
-            .bg(item_bg)
-            .px_2()
-            .py_1p5()
-            .w_full()
-            .z_index(1)
-            .when(!self.seen, |this| {
-                this.child(
-                    div()
-                        .absolute()
-                        .left(px(3.0))
-                        .top_3()
-                        .rounded_full()
-                        .border_2()
-                        .border_color(cx.theme().colors().surface)
-                        .w(px(9.0))
-                        .h(px(9.0))
-                        .z_index(2)
-                        .bg(cx.theme().status().info),
-                )
-            })
-            .child(
-                v_stack()
-                    .w_full()
-                    .line_height(relative(1.2))
-                    .gap_1()
-                    .child(
-                        div()
-                            .w_5()
-                            .h_5()
-                            .rounded_full()
-                            .bg(cx.theme().colors().icon_accent),
-                    )
-                    .child(Label::new(self.label.clone()).color(label_color))
-                    .children(
-                        self.meta
-                            .map(|meta| Label::new(meta).color(LabelColor::Muted)),
-                    )
-                    .child(
-                        h_stack()
-                            .gap_1()
-                            .justify_end()
-                            .children(self.actions.unwrap_or_default()),
-                    ),
-            )
-    }
-}
-
 #[derive(Clone, Component)]
 pub struct ListSeparator;
 
@@ -564,20 +351,22 @@ impl ListSeparator {
 }
 
 #[derive(Component)]
-pub struct List<V: 'static> {
-    items: Vec<ListItem<V>>,
+pub struct List {
+    items: Vec<ListItem>,
+    /// Message to display when the list is empty
+    /// Defaults to "No items"
     empty_message: SharedString,
     header: Option<ListHeader>,
-    toggleable: Toggleable,
+    toggle: Toggle,
 }
 
-impl<V: 'static> List<V> {
-    pub fn new(items: Vec<ListItem<V>>) -> Self {
+impl List {
+    pub fn new(items: Vec<ListItem>) -> Self {
         Self {
             items,
             empty_message: "No items".into(),
             header: None,
-            toggleable: Toggleable::default(),
+            toggle: Toggle::NotToggleable,
         }
     }
 
@@ -591,19 +380,16 @@ impl<V: 'static> List<V> {
         self
     }
 
-    pub fn toggle(mut self, toggle: ToggleState) -> Self {
-        self.toggleable = toggle.into();
+    pub fn toggle(mut self, toggle: Toggle) -> Self {
+        self.toggle = toggle;
         self
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let is_toggleable = self.toggleable != Toggleable::NotToggleable;
-        let is_toggled = Toggleable::is_toggled(&self.toggleable);
-
-        let list_content = match (self.items.is_empty(), is_toggled) {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+        let list_content = match (self.items.is_empty(), self.toggle) {
             (false, _) => div().children(self.items),
-            (true, false) => div(),
-            (true, true) => {
+            (true, Toggle::Toggled(false)) => div(),
+            (true, _) => {
                 div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted))
             }
         };
@@ -611,7 +397,7 @@ impl<V: 'static> List<V> {
         v_stack()
             .w_full()
             .py_1()
-            .children(self.header.map(|header| header.toggleable(self.toggleable)))
+            .children(self.header.map(|header| header))
             .child(list_content)
     }
 }

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

@@ -1,4 +1,4 @@
-use gpui2::AnyElement;
+use gpui::AnyElement;
 use smallvec::SmallVec;
 
 use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};

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

@@ -1,4 +1,4 @@
-use gpui2::rems;
+use gpui::rems;
 
 use crate::prelude::*;
 use crate::{h_stack, Icon};
@@ -34,7 +34,7 @@ impl NotificationToast {
             .px_1p5()
             .rounded_lg()
             .shadow_md()
-            .bg(cx.theme().colors().elevated_surface)
+            .bg(cx.theme().colors().elevated_surface_background)
             .child(div().size_full().child(self.label.clone()))
     }
 }

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

@@ -47,7 +47,7 @@ impl Palette {
             .id(self.id.clone())
             .w_96()
             .rounded_lg()
-            .bg(cx.theme().colors().elevated_surface)
+            .bg(cx.theme().colors().elevated_surface_background)
             .border()
             .border_color(cx.theme().colors().border)
             .child(
@@ -56,7 +56,12 @@ impl Palette {
                     .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
                         Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder),
                     )))
-                    .child(div().h_px().w_full().bg(cx.theme().colors().element))
+                    .child(
+                        div()
+                            .h_px()
+                            .w_full()
+                            .bg(cx.theme().colors().element_background),
+                    )
                     .child(
                         v_stack()
                             .id("items")
@@ -148,13 +153,13 @@ impl PaletteItem {
     }
 }
 
-use gpui2::ElementId;
+use gpui::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     use crate::{ModifierKeys, Story};
 

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

@@ -1,4 +1,4 @@
-use gpui2::{AbsoluteLength, AnyElement};
+use gpui::{AbsoluteLength, AnyElement};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -107,7 +107,7 @@ impl<V: 'static> Panel<V> {
                 PanelSide::Right => this.border_l(),
                 PanelSide::Bottom => this.border_b().w_full().h(current_size),
             })
-            .bg(cx.theme().colors().surface)
+            .bg(cx.theme().colors().surface_background)
             .border_color(cx.theme().colors().border)
             .children(self.children)
     }
@@ -126,7 +126,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{Label, Story};
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct PanelStory;
 

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

@@ -1,7 +1,25 @@
-use gpui2::{Hsla, ViewContext};
+use gpui::{Hsla, ViewContext};
 
 use crate::prelude::*;
 
+/// Represents a person with a Zed account's public profile.
+/// All data in this struct should be considered public.
+pub struct PublicPlayer {
+    pub username: SharedString,
+    pub avatar: SharedString,
+    pub is_contact: bool,
+}
+
+impl PublicPlayer {
+    pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
+        Self {
+            username: username.into(),
+            avatar: avatar.into(),
+            is_contact: false,
+        }
+    }
+}
+
 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
 pub enum PlayerStatus {
     #[default]

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

@@ -0,0 +1,14 @@
+use gpui::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(SharedString),
+    PublicActor(SharedString),
+}

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

@@ -1,6 +1,6 @@
 use crate::prelude::*;
 use crate::{Icon, IconColor, IconElement, Label, LabelColor};
-use gpui2::{red, Div, ElementId, Render, View, VisualContext};
+use gpui::{red, Div, ElementId, Render, View, VisualContext};
 
 #[derive(Component, Clone)]
 pub struct Tab {
@@ -109,12 +109,12 @@ impl Tab {
 
         let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
             false => (
-                cx.theme().colors().tab_inactive,
+                cx.theme().colors().tab_inactive_background,
                 cx.theme().colors().ghost_element_hover,
                 cx.theme().colors().ghost_element_active,
             ),
             true => (
-                cx.theme().colors().tab_active,
+                cx.theme().colors().tab_active_background,
                 cx.theme().colors().element_hover,
                 cx.theme().colors().element_active,
             ),

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

@@ -1,4 +1,4 @@
-use gpui2::AnyElement;
+use gpui::AnyElement;
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -54,7 +54,7 @@ impl<V: 'static> Toast<V> {
             .rounded_lg()
             .shadow_md()
             .overflow_hidden()
-            .bg(cx.theme().colors().elevated_surface)
+            .bg(cx.theme().colors().elevated_surface_background)
             .children(self.children)
     }
 }
@@ -70,7 +70,7 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     use crate::{Label, Story};
 

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

@@ -0,0 +1,61 @@
+use gpui::{div, Component, ParentElement};
+
+use crate::{Icon, IconColor, IconElement, IconSize};
+
+/// 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)
+    }
+}
+
+pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
+    match (toggle.is_toggleable(), toggle.is_toggled()) {
+        (false, _) => div(),
+        (_, true) => div().child(
+            IconElement::new(Icon::ChevronDown)
+                .color(IconColor::Muted)
+                .size(IconSize::Small),
+        ),
+        (_, false) => div().child(
+            IconElement::new(Icon::ChevronRight)
+                .color(IconColor::Muted)
+                .size(IconSize::Small),
+        ),
+    }
+}

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

@@ -0,0 +1,31 @@
+use gpui::{div, px, Div, ParentElement, Render, SharedString, Styled, ViewContext};
+use theme2::ActiveTheme;
+
+#[derive(Clone, Debug)]
+pub struct TextTooltip {
+    title: SharedString,
+}
+
+impl TextTooltip {
+    pub fn new(str: SharedString) -> Self {
+        Self { title: str }
+    }
+}
+
+impl Render for TextTooltip {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let theme = cx.theme();
+        div()
+            .bg(theme.colors().background)
+            .rounded(px(8.))
+            .border()
+            .font("Zed Sans")
+            .border_color(theme.colors().border)
+            .text_color(theme.colors().text)
+            .pl_2()
+            .pr_2()
+            .child(self.title.clone())
+    }
+}

crates/ui2/src/prelude.rs 🔗

@@ -1,4 +1,4 @@
-pub use gpui2::{
+pub use gpui::{
     div, Component, Element, ElementId, ParentElement, SharedString, StatefulInteractive,
     StatelessInteractive, Styled, ViewContext, WindowContext,
 };
@@ -7,27 +7,9 @@ pub use crate::elevation::*;
 pub use crate::ButtonVariant;
 pub use theme2::ActiveTheme;
 
-use gpui2::Hsla;
+use gpui::Hsla;
 use strum::EnumIter;
 
-/// Represents a person with a Zed account's public profile.
-/// All data in this struct should be considered public.
-pub struct PublicActor {
-    pub username: SharedString,
-    pub avatar: SharedString,
-    pub is_contact: bool,
-}
-
-impl PublicActor {
-    pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
-        Self {
-            username: username.into(),
-            avatar: avatar.into(),
-            is_contact: false,
-        }
-    }
-}
-
 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum FileSystemStatus {
     #[default]
@@ -172,68 +154,19 @@ impl InteractionState {
     }
 }
 
-#[derive(Default, PartialEq)]
-pub enum SelectedState {
+#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
+pub enum Selection {
     #[default]
     Unselected,
-    PartiallySelected,
+    Indeterminate,
     Selected,
 }
 
-#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
-pub enum Toggleable {
-    Toggleable(ToggleState),
-    #[default]
-    NotToggleable,
-}
-
-impl Toggleable {
-    pub fn is_toggled(&self) -> bool {
+impl Selection {
+    pub fn inverse(&self) -> Self {
         match self {
-            Self::Toggleable(ToggleState::Toggled) => true,
-            _ => false,
-        }
-    }
-}
-
-impl From<ToggleState> for Toggleable {
-    fn from(state: ToggleState) -> Self {
-        Self::Toggleable(state)
-    }
-}
-
-#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
-pub enum ToggleState {
-    /// The "on" state of a toggleable element.
-    ///
-    /// Example:
-    ///     - A collasable list that is currently expanded
-    ///     - A toggle button that is currently on.
-    Toggled,
-    /// The "off" state of a toggleable element.
-    ///
-    /// Example:
-    ///     - A collasable list that is currently collapsed
-    ///     - A toggle button that is currently off.
-    #[default]
-    NotToggled,
-}
-
-impl From<Toggleable> for ToggleState {
-    fn from(toggleable: Toggleable) -> Self {
-        match toggleable {
-            Toggleable::Toggleable(state) => state,
-            Toggleable::NotToggleable => ToggleState::NotToggled,
-        }
-    }
-}
-
-impl From<bool> for ToggleState {
-    fn from(toggled: bool) -> Self {
-        if toggled {
-            ToggleState::Toggled
-        } else {
-            ToggleState::NotToggled
+            Self::Unselected | Self::Indeterminate => Self::Selected,
+            Self::Selected => Self::Unselected,
         }
     }
 }

crates/ui2/src/settings.rs 🔗

@@ -1,6 +1,6 @@
 use std::ops::Deref;
 
-use gpui2::{rems, AbsoluteLength, AppContext, WindowContext};
+use gpui::{rems, AbsoluteLength, AppContext, WindowContext};
 
 use crate::prelude::*;
 

crates/ui2/src/static_data.rs 🔗

@@ -3,17 +3,17 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use chrono::DateTime;
-use gpui2::{AppContext, ViewContext};
+use gpui::{AppContext, ViewContext};
 use rand::Rng;
 use theme2::ActiveTheme;
 
+use crate::HighlightedText;
 use crate::{
     Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
-    HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader,
-    Livestream, MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
-    PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus,
+    HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
+    MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
+    PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
 };
-use crate::{HighlightedText, ListDetailsEntry};
 use crate::{ListItem, NotificationAction};
 
 pub fn static_tabs_example() -> Vec<Tab> {
@@ -345,7 +345,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
             DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z")
                 .unwrap()
                 .naive_local(),
-            PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
+            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
             [
                 NotificationAction::new(
                     Button::new("Decline"),
@@ -374,7 +374,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
             DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z")
                 .unwrap()
                 .naive_local(),
-            PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
+            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
             [
                 NotificationAction::new(
                     Button::new("Decline"),
@@ -403,7 +403,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
             DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z")
                 .unwrap()
                 .naive_local(),
-            PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
+            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
             [
                 NotificationAction::new(
                     Button::new("Decline"),
@@ -432,7 +432,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
             DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z")
                 .unwrap()
                 .naive_local(),
-            PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
+            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
             [
                 NotificationAction::new(
                     Button::new("Decline"),
@@ -461,7 +461,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
             DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z")
                 .unwrap()
                 .naive_local(),
-            PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"),
+            PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
             [
                 NotificationAction::new(
                     Button::new("Decline"),
@@ -478,89 +478,12 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
     ]
 }
 
-pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> {
-    vec![
-        ListItem::Header(ListSubHeader::new("New")),
-        ListItem::Details(
-            ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.")
-                .meta("4 people in stream."),
-        ),
-        ListItem::Details(ListDetailsEntry::new(
-            "nathansobo accepted your contact request.",
-        )),
-        ListItem::Header(ListSubHeader::new("Earlier")),
-        ListItem::Details(
-            ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
-                Button::new("Decline"),
-                Button::new("Accept").variant(crate::ButtonVariant::Filled),
-            ]),
-        ),
-        ListItem::Details(
-            ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
-                .seen(true)
-                .meta("This stream has ended."),
-        ),
-        ListItem::Details(ListDetailsEntry::new(
-            "as-cii accepted your contact request.",
-        )),
-        ListItem::Details(
-            ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true),
-        ),
-        ListItem::Details(ListDetailsEntry::new(
-            "osiewicz accepted your contact request.",
-        )),
-        ListItem::Details(ListDetailsEntry::new(
-            "ConradIrwin accepted your contact request.",
-        )),
-        ListItem::Details(
-            ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.")
-                .seen(true)
-                .meta("This stream has ended."),
-        ),
-        ListItem::Details(ListDetailsEntry::new(
-            "nathansobo accepted your contact request.",
-        )),
-        ListItem::Header(ListSubHeader::new("Earlier")),
-        ListItem::Details(
-            ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
-                Button::new("Decline"),
-                Button::new("Accept").variant(crate::ButtonVariant::Filled),
-            ]),
-        ),
-        ListItem::Details(
-            ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
-                .seen(true)
-                .meta("This stream has ended."),
-        ),
-        ListItem::Details(ListDetailsEntry::new(
-            "as-cii accepted your contact request.",
-        )),
-        ListItem::Details(
-            ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true),
-        ),
-        ListItem::Details(ListDetailsEntry::new(
-            "osiewicz accepted your contact request.",
-        )),
-        ListItem::Details(ListDetailsEntry::new(
-            "ConradIrwin accepted your contact request.",
-        )),
-        ListItem::Details(
-            ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.")
-                .seen(true)
-                .meta("This stream has ended."),
-        ),
-    ]
-    .into_iter()
-    .map(From::from)
-    .collect()
-}
-
-pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
+pub fn static_project_panel_project_items() -> Vec<ListItem> {
     vec![
         ListEntry::new(Label::new("zed"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(0)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new(".cargo"))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
@@ -579,14 +502,14 @@ pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
         ListEntry::new(Label::new("assets"))
             .left_icon(Icon::Folder.into())
             .indent_level(1)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
         ListEntry::new(Label::new("crates"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(1)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("activity_indicator"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
@@ -608,38 +531,38 @@ pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
         ListEntry::new(Label::new("sqlez").color(LabelColor::Modified))
             .left_icon(Icon::Folder.into())
             .indent_level(2)
-            .toggle(ToggleState::NotToggled),
+            .toggle(Toggle::Toggled(false)),
         ListEntry::new(Label::new("gpui2"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(2)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("src"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(3)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("derive_element.rs"))
             .left_icon(Icon::FileRust.into())
             .indent_level(4),
         ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(1)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("docs").color(LabelColor::Default))
             .left_icon(Icon::Folder.into())
             .indent_level(2)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("src").color(LabelColor::Modified))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(3)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("ui").color(LabelColor::Modified))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(4)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("component").color(LabelColor::Created))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(5)
-            .toggle(ToggleState::Toggled),
+            .toggle(Toggle::Toggled(true)),
         ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default))
             .left_icon(Icon::FileRust.into())
             .indent_level(6),
@@ -682,7 +605,7 @@ pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
     .collect()
 }
 
-pub fn static_project_panel_single_items<V: 'static>() -> Vec<ListItem<V>> {
+pub fn static_project_panel_single_items() -> Vec<ListItem> {
     vec![
         ListEntry::new(Label::new("todo.md"))
             .left_icon(Icon::FileDoc.into())
@@ -699,7 +622,7 @@ pub fn static_project_panel_single_items<V: 'static>() -> Vec<ListItem<V>> {
     .collect()
 }
 
-pub fn static_collab_panel_current_call<V: 'static>() -> Vec<ListItem<V>> {
+pub fn static_collab_panel_current_call() -> Vec<ListItem> {
     vec![
         ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"),
         ListEntry::new(Label::new("nathansobo"))
@@ -712,7 +635,7 @@ pub fn static_collab_panel_current_call<V: 'static>() -> Vec<ListItem<V>> {
     .collect()
 }
 
-pub fn static_collab_panel_channels<V: 'static>() -> Vec<ListItem<V>> {
+pub fn static_collab_panel_channels() -> Vec<ListItem> {
     vec![
         ListEntry::new(Label::new("zed"))
             .left_icon(Icon::Hash.into())

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

@@ -1,6 +1,6 @@
 use crate::prelude::*;
 use crate::{Icon, IconButton, Label, Panel, PanelSide};
-use gpui2::{rems, AbsoluteLength};
+use gpui::{rems, AbsoluteLength};
 
 #[derive(Component)]
 pub struct AssistantPanel {
@@ -77,7 +77,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
     pub struct AssistantPanelStory;
 
     impl Render for AssistantPanelStory {

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

@@ -2,7 +2,7 @@ use std::path::PathBuf;
 
 use crate::prelude::*;
 use crate::{h_stack, HighlightedText};
-use gpui2::Div;
+use gpui::Div;
 
 #[derive(Clone)]
 pub struct Symbol(pub Vec<HighlightedText>);
@@ -73,7 +73,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::Render;
+    use gpui::Render;
     use std::str::FromStr;
 
     pub struct BreadcrumbStory;

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

@@ -1,4 +1,4 @@
-use gpui2::{Hsla, WindowContext};
+use gpui::{Hsla, WindowContext};
 
 use crate::prelude::*;
 use crate::{h_stack, v_stack, Icon, IconElement};
@@ -220,7 +220,7 @@ impl Buffer {
             .flex_1()
             .w_full()
             .h_full()
-            .bg(cx.theme().colors().editor)
+            .bg(cx.theme().colors().editor_background)
             .children(rows)
     }
 }
@@ -235,7 +235,7 @@ mod stories {
         empty_buffer_example, hello_world_rust_buffer_example,
         hello_world_rust_buffer_with_status_example, Story,
     };
-    use gpui2::{rems, Div, Render};
+    use gpui::{rems, Div, Render};
 
     pub struct BufferStory;
 

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

@@ -1,4 +1,4 @@
-use gpui2::{Div, Render, View, VisualContext};
+use gpui::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::{h_stack, Icon, IconButton, IconColor, Input};
@@ -30,14 +30,17 @@ impl Render for BufferSearch {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        h_stack().bg(cx.theme().colors().toolbar).p_2().child(
-            h_stack().child(Input::new("Search")).child(
-                IconButton::<Self>::new("replace", Icon::Replace)
-                    .when(self.is_replace_open, |this| this.color(IconColor::Accent))
-                    .on_click(|buffer_search, cx| {
-                        buffer_search.toggle_replace(cx);
-                    }),
-            ),
-        )
+        h_stack()
+            .bg(cx.theme().colors().toolbar_background)
+            .p_2()
+            .child(
+                h_stack().child(Input::new("Search")).child(
+                    IconButton::<Self>::new("replace", Icon::Replace)
+                        .when(self.is_replace_open, |this| this.color(IconColor::Accent))
+                        .on_click(|buffer_search, cx| {
+                            buffer_search.toggle_replace(cx);
+                        }),
+                ),
+            )
     }
 }

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

@@ -108,7 +108,7 @@ pub use stories::*;
 #[cfg(feature = "stories")]
 mod stories {
     use chrono::DateTime;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     use crate::{Panel, Story};
 

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

@@ -1,7 +1,6 @@
-use crate::prelude::*;
+use crate::{prelude::*, Toggle};
 use crate::{
-    static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List,
-    ListHeader, ToggleState,
+    static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, ListHeader,
 };
 
 #[derive(Component)]
@@ -18,7 +17,7 @@ impl CollabPanel {
         v_stack()
             .id(self.id.clone())
             .h_full()
-            .bg(cx.theme().colors().surface)
+            .bg(cx.theme().colors().surface_background)
             .child(
                 v_stack()
                     .id("crdb")
@@ -34,17 +33,17 @@ impl CollabPanel {
                                     .header(
                                         ListHeader::new("CRDB")
                                             .left_icon(Icon::Hash.into())
-                                            .toggle(ToggleState::Toggled),
+                                            .toggle(Toggle::Toggled(true)),
                                     )
-                                    .toggle(ToggleState::Toggled),
+                                    .toggle(Toggle::Toggled(true)),
                             ),
                     )
                     .child(
                         v_stack().id("channels").py_1().child(
                             List::new(static_collab_panel_channels())
-                                .header(ListHeader::new("CHANNELS").toggle(ToggleState::Toggled))
+                                .header(ListHeader::new("CHANNELS").toggle(Toggle::Toggled(true)))
                                 .empty_message("No channels yet. Add a channel to get started.")
-                                .toggle(ToggleState::Toggled),
+                                .toggle(Toggle::Toggled(true)),
                         ),
                     )
                     .child(
@@ -52,9 +51,9 @@ impl CollabPanel {
                             List::new(static_collab_panel_current_call())
                                 .header(
                                     ListHeader::new("CONTACTS – ONLINE")
-                                        .toggle(ToggleState::Toggled),
+                                        .toggle(Toggle::Toggled(true)),
                                 )
-                                .toggle(ToggleState::Toggled),
+                                .toggle(Toggle::Toggled(true)),
                         ),
                     )
                     .child(
@@ -62,9 +61,9 @@ impl CollabPanel {
                             List::new(static_collab_panel_current_call())
                                 .header(
                                     ListHeader::new("CONTACTS – OFFLINE")
-                                        .toggle(ToggleState::NotToggled),
+                                        .toggle(Toggle::Toggled(false)),
                                 )
-                                .toggle(ToggleState::NotToggled),
+                                .toggle(Toggle::Toggled(false)),
                         ),
                     ),
             )
@@ -93,7 +92,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct CollabPanelStory;
 

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

@@ -24,7 +24,7 @@ impl MultiBuffer {
                             .items_center()
                             .justify_between()
                             .p_4()
-                            .bg(cx.theme().colors().editor_subheader)
+                            .bg(cx.theme().colors().editor_subheader_background)
                             .child(Label::new("main.rs"))
                             .child(IconButton::new("arrow_up_right", Icon::ArrowUpRight)),
                     )
@@ -40,7 +40,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{hello_world_rust_buffer_example, Story};
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct MultiBufferStory;
 

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

@@ -1,8 +1,8 @@
 use crate::utils::naive_format_distance_from_now;
 use crate::{
-    h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon,
-    IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator,
-    UnreadIndicator,
+    h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, ButtonOrIconButton,
+    Icon, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator,
+    PublicPlayer, UnreadIndicator,
 };
 use crate::{ClickHandler, ListHeader};
 
@@ -22,7 +22,7 @@ impl NotificationsPanel {
             .flex()
             .flex_col()
             .size_full()
-            .bg(cx.theme().colors().surface)
+            .bg(cx.theme().colors().surface_background)
             .child(
                 ListHeader::new("Notifications").meta(Some(ListHeaderMeta::Tools(vec![
                     Icon::AtSign,
@@ -43,7 +43,7 @@ impl NotificationsPanel {
                             .p_1()
                             // TODO: Add cursor style
                             // .cursor(Cursor::IBeam)
-                            .bg(cx.theme().colors().element)
+                            .bg(cx.theme().colors().element_background)
                             .border()
                             .border_color(cx.theme().colors().border_variant)
                             .child(
@@ -57,23 +57,6 @@ impl NotificationsPanel {
     }
 }
 
-pub enum ButtonOrIconButton<V: 'static> {
-    Button(Button<V>),
-    IconButton(IconButton<V>),
-}
-
-impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
-    fn from(value: Button<V>) -> Self {
-        Self::Button(value)
-    }
-}
-
-impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
-    fn from(value: IconButton<V>) -> Self {
-        Self::IconButton(value)
-    }
-}
-
 pub struct NotificationAction<V: 'static> {
     button: ButtonOrIconButton<V>,
     tooltip: SharedString,
@@ -102,7 +85,7 @@ impl<V: 'static> NotificationAction<V> {
 }
 
 pub enum ActorOrIcon {
-    Actor(PublicActor),
+    Actor(PublicPlayer),
     Icon(Icon),
 }
 
@@ -171,7 +154,7 @@ impl<V> Notification<V> {
         id: impl Into<ElementId>,
         message: impl Into<SharedString>,
         date_received: NaiveDateTime,
-        actor: PublicActor,
+        actor: PublicPlayer,
         click_action: ClickHandler<V>,
     ) -> Self {
         Self::new(
@@ -210,7 +193,7 @@ impl<V> Notification<V> {
         id: impl Into<ElementId>,
         message: impl Into<SharedString>,
         date_received: NaiveDateTime,
-        actor: PublicActor,
+        actor: PublicPlayer,
         actions: [NotificationAction<V>; 2],
     ) -> Self {
         Self::new(
@@ -362,7 +345,7 @@ impl<V> Notification<V> {
 }
 
 use chrono::NaiveDateTime;
-use gpui2::{px, Styled};
+use gpui::{px, Styled};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -370,7 +353,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{Panel, Story};
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct NotificationsPanelStory;
 

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

@@ -1,4 +1,4 @@
-use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
+use gpui::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -113,7 +113,7 @@ impl<V: 'static> PaneGroup<V> {
                 .gap_px()
                 .w_full()
                 .h_full()
-                .bg(cx.theme().colors().editor)
+                .bg(cx.theme().colors().editor_background)
                 .children(self.groups.into_iter().map(|group| group.render(view, cx)));
 
             if self.split_direction == SplitDirection::Horizontal {

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

@@ -18,9 +18,8 @@ impl ProjectPanel {
             .id(self.id.clone())
             .flex()
             .flex_col()
-            .w_full()
-            .h_full()
-            .bg(cx.theme().colors().surface)
+            .size_full()
+            .bg(cx.theme().colors().surface_background)
             .child(
                 div()
                     .id("project-panel-contents")
@@ -30,15 +29,13 @@ impl ProjectPanel {
                     .overflow_y_scroll()
                     .child(
                         List::new(static_project_panel_single_items())
-                            .header(ListHeader::new("FILES").toggle(ToggleState::Toggled))
-                            .empty_message("No files in directory")
-                            .toggle(ToggleState::Toggled),
+                            .header(ListHeader::new("FILES"))
+                            .empty_message("No files in directory"),
                     )
                     .child(
                         List::new(static_project_panel_project_items())
-                            .header(ListHeader::new("PROJECT").toggle(ToggleState::Toggled))
-                            .empty_message("No folders in directory")
-                            .toggle(ToggleState::Toggled),
+                            .header(ListHeader::new("PROJECT"))
+                            .empty_message("No folders in directory"),
                     ),
             )
             .child(
@@ -49,7 +46,7 @@ impl ProjectPanel {
     }
 }
 
-use gpui2::ElementId;
+use gpui::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -57,7 +54,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{Panel, Story};
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct ProjectPanelStory;
 

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

@@ -93,7 +93,7 @@ impl StatusBar {
             .items_center()
             .justify_between()
             .w_full()
-            .bg(cx.theme().colors().status_bar)
+            .bg(cx.theme().colors().status_bar_background)
             .child(self.left_tools(view, cx))
             .child(self.right_tools(view, cx))
     }

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

@@ -31,7 +31,7 @@ impl TabBar {
             .id(self.id.clone())
             .w_full()
             .flex()
-            .bg(cx.theme().colors().tab_bar)
+            .bg(cx.theme().colors().tab_bar_background)
             // Left Side
             .child(
                 div()
@@ -92,7 +92,7 @@ impl TabBar {
     }
 }
 
-use gpui2::ElementId;
+use gpui::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -100,7 +100,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     pub struct TabBarStory;
 

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

@@ -1,4 +1,4 @@
-use gpui2::{relative, rems, Size};
+use gpui::{relative, rems, Size};
 
 use crate::prelude::*;
 use crate::{Icon, IconButton, Pane, Tab};
@@ -24,7 +24,7 @@ impl Terminal {
                 div()
                     .w_full()
                     .flex()
-                    .bg(cx.theme().colors().surface)
+                    .bg(cx.theme().colors().surface_background)
                     .child(
                         div().px_1().flex().flex_none().gap_2().child(
                             div()
@@ -83,7 +83,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
     pub struct TerminalStory;
 
     impl Render for TerminalStory {

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

@@ -1,7 +1,7 @@
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
-use gpui2::{Div, Render, View, VisualContext};
+use gpui::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::settings::user_settings;

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

@@ -1,4 +1,4 @@
-use gpui2::AnyElement;
+use gpui::AnyElement;
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -56,7 +56,7 @@ impl<V: 'static> Toolbar<V> {
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         div()
-            .bg(cx.theme().colors().toolbar)
+            .bg(cx.theme().colors().toolbar_background)
             .p_2()
             .flex()
             .justify_between()
@@ -73,7 +73,7 @@ mod stories {
     use std::path::PathBuf;
     use std::str::FromStr;
 
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol};
 

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

@@ -28,7 +28,7 @@ impl TrafficLight {
             (true, TrafficLightColor::Red) => system_colors.mac_os_traffic_light_red,
             (true, TrafficLightColor::Yellow) => system_colors.mac_os_traffic_light_yellow,
             (true, TrafficLightColor::Green) => system_colors.mac_os_traffic_light_green,
-            (false, _) => cx.theme().colors().element,
+            (false, _) => cx.theme().colors().element_background,
         };
 
         div().w_3().h_3().rounded_full().bg(fill)
@@ -77,7 +77,7 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::{Div, Render};
+    use gpui::{Div, Render};
 
     use crate::Story;
 

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

@@ -1,14 +1,14 @@
 use std::sync::Arc;
 
 use chrono::DateTime;
-use gpui2::{px, relative, Div, Render, Size, View, VisualContext};
+use gpui::{px, relative, Div, Render, Size, View, VisualContext};
 use settings2::Settings;
 use theme2::ThemeSettings;
 
 use crate::prelude::*;
 use crate::{
-    static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, CollabPanel,
-    EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel,
+    static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, Checkbox,
+    CollabPanel, EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel,
     PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
     Toast, ToastOrigin,
 };
@@ -42,6 +42,7 @@ pub struct Workspace {
     show_terminal: bool,
     show_debug: bool,
     show_language_selector: bool,
+    test_checkbox_selection: Selection,
     debug: Gpui2UiDebug,
 }
 
@@ -58,6 +59,7 @@ impl Workspace {
             show_language_selector: false,
             show_debug: false,
             show_notifications_panel: true,
+            test_checkbox_selection: Selection::Unselected,
             debug: Gpui2UiDebug::default(),
         }
     }
@@ -217,6 +219,23 @@ impl Render for Workspace {
             .text_color(cx.theme().colors().text)
             .bg(cx.theme().colors().background)
             .child(self.title_bar.clone())
+            .child(
+                div()
+                    .absolute()
+                    .top_12()
+                    .left_12()
+                    .z_index(99)
+                    .bg(cx.theme().colors().background)
+                    .child(
+                        Checkbox::new("test_checkbox", self.test_checkbox_selection).on_click(
+                            |selection, workspace: &mut Workspace, cx| {
+                                workspace.test_checkbox_selection = selection;
+
+                                cx.notify();
+                            },
+                        ),
+                    ),
+            )
             .child(
                 div()
                     .flex_1()
@@ -354,7 +373,7 @@ pub use stories::*;
 #[cfg(feature = "stories")]
 mod stories {
     use super::*;
-    use gpui2::VisualContext;
+    use gpui::VisualContext;
 
     pub struct WorkspaceStory {
         workspace: View<Workspace>,

crates/workspace2/src/pane.rs 🔗

@@ -26,7 +26,7 @@ use std::{
     },
 };
 use ui::v_stack;
-use ui::{prelude::*, Icon, IconButton, IconColor, IconElement};
+use ui::{prelude::*, Icon, IconButton, IconColor, IconElement, TextTooltip};
 use util::truncate_and_remove_front;
 
 #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
@@ -1359,16 +1359,31 @@ impl Pane {
         cx: &mut ViewContext<'_, Pane>,
     ) -> impl Component<Self> {
         let label = item.tab_content(Some(detail), cx);
-        let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted);
+        let close_icon = || {
+            let id = item.id();
+
+            div()
+                .id(item.id())
+                .invisible()
+                .group_hover("", |style| style.visible())
+                .child(IconButton::new("close_tab", Icon::Close).on_click(
+                    move |pane: &mut Self, cx| {
+                        pane.close_item_by_id(id, SaveIntent::Close, cx)
+                            .detach_and_log_err(cx);
+                    },
+                ))
+        };
 
-        let (tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
+        let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
             false => (
-                cx.theme().colors().tab_inactive,
+                cx.theme().colors().text_muted,
+                cx.theme().colors().tab_inactive_background,
                 cx.theme().colors().ghost_element_hover,
                 cx.theme().colors().ghost_element_active,
             ),
             true => (
-                cx.theme().colors().tab_active,
+                cx.theme().colors().text,
+                cx.theme().colors().tab_active_background,
                 cx.theme().colors().element_hover,
                 cx.theme().colors().element_active,
             ),
@@ -1377,7 +1392,12 @@ impl Pane {
         let close_right = ItemSettings::get_global(cx).close_position.right();
 
         div()
+            .group("")
             .id(item.id())
+            .cursor_pointer()
+            .when_some(item.tab_tooltip_text(cx), |div, text| {
+                div.tooltip(move |_, cx| cx.build_view(|cx| TextTooltip::new(text.clone())))
+            })
             // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
             // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
             // .on_drop(|_view, state: View<DraggedTab>, cx| {
@@ -1397,6 +1417,7 @@ impl Pane {
                     .flex()
                     .items_center()
                     .gap_1p5()
+                    .text_color(text_color)
                     .children(if item.has_conflict(cx) {
                         Some(
                             IconElement::new(Icon::ExclamationTriangle)
@@ -1432,7 +1453,7 @@ impl Pane {
             .id("tab_bar")
             .w_full()
             .flex()
-            .bg(cx.theme().colors().tab_bar)
+            .bg(cx.theme().colors().tab_bar_background)
             // Left Side
             .child(
                 div()
@@ -1457,7 +1478,7 @@ impl Pane {
                     ),
             )
             .child(
-                div().w_0().flex_1().h_full().child(
+                div().flex_1().h_full().child(
                     div().id("tabs").flex().overflow_x_scroll().children(
                         self.items
                             .iter()
@@ -1888,13 +1909,14 @@ impl Render for Pane {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         v_stack()
+            .size_full()
             .child(self.render_tab_bar(cx))
-            .child(div() /* toolbar */)
+            .child(div() /* todo!(toolbar) */)
             .child(if let Some(item) = self.active_item() {
-                item.to_any().render()
+                div().flex_1().child(item.to_any())
             } else {
                 // todo!()
-                div().child("Empty Pane").render()
+                div().child("Empty Pane")
             })
 
         // enum MouseNavigationHandler {}

crates/workspace2/src/pane_group.rs 🔗

@@ -201,7 +201,7 @@ impl Member {
                 //     Some(pane)
                 // };
 
-                div().child(pane.clone()).render()
+                div().size_full().child(pane.clone()).render()
 
                 //         Stack::new()
                 //             .with_child(pane_element.contained().with_border(leader_border))

crates/workspace2/src/status_bar.rs 🔗

@@ -44,7 +44,8 @@ impl Render for StatusBar {
             .items_center()
             .justify_between()
             .w_full()
-            .bg(cx.theme().colors().status_bar)
+            .h_8()
+            .bg(cx.theme().colors().status_bar_background)
             .child(self.render_left_tools(cx))
             .child(self.render_right_tools(cx))
     }

crates/workspace2/src/workspace2.rs 🔗

@@ -2697,7 +2697,7 @@ impl Workspace {
 
     fn render_titlebar(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
         div()
-            .bg(cx.theme().colors().title_bar)
+            .bg(cx.theme().colors().title_bar_background)
             .when(
                 !matches!(cx.window_bounds(), WindowBounds::Fullscreen),
                 |s| s.pl_20(),

crates/zed/src/languages/elixir.rs 🔗

@@ -140,8 +140,8 @@ impl LspAdapter for ElixirLspAdapter {
     ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
         let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
-        let version_dir = container_dir.join(format!("elixir-ls_{}", version.name));
-        let binary_path = version_dir.join("language_server.sh");
+        let folder_path = container_dir.join("elixir-ls");
+        let binary_path = folder_path.join("language_server.sh");
 
         if fs::metadata(&binary_path).await.is_err() {
             let mut response = delegate
@@ -160,13 +160,13 @@ impl LspAdapter for ElixirLspAdapter {
             }
             futures::io::copy(response.body_mut(), &mut file).await?;
 
-            fs::create_dir_all(&version_dir)
+            fs::create_dir_all(&folder_path)
                 .await
-                .with_context(|| format!("failed to create directory {}", version_dir.display()))?;
+                .with_context(|| format!("failed to create directory {}", folder_path.display()))?;
             let unzip_status = smol::process::Command::new("unzip")
                 .arg(&zip_path)
                 .arg("-d")
-                .arg(&version_dir)
+                .arg(&folder_path)
                 .output()
                 .await?
                 .status;
@@ -174,7 +174,7 @@ impl LspAdapter for ElixirLspAdapter {
                 Err(anyhow!("failed to unzip elixir-ls archive"))?;
             }
 
-            remove_matching(&container_dir, |entry| entry != version_dir).await;
+            remove_matching(&container_dir, |entry| entry != folder_path).await;
         }
 
         Ok(LanguageServerBinary {
@@ -285,20 +285,16 @@ impl LspAdapter for ElixirLspAdapter {
 async fn get_cached_server_binary_elixir_ls(
     container_dir: PathBuf,
 ) -> Option<LanguageServerBinary> {
-    (|| async move {
-        let mut last = None;
-        let mut entries = fs::read_dir(&container_dir).await?;
-        while let Some(entry) = entries.next().await {
-            last = Some(entry?.path());
-        }
-        last.map(|path| LanguageServerBinary {
-            path,
+    let server_path = container_dir.join("elixir-ls/language_server.sh");
+    if server_path.exists() {
+        Some(LanguageServerBinary {
+            path: server_path,
             arguments: vec![],
         })
-        .ok_or_else(|| anyhow!("no cached binary"))
-    })()
-    .await
-    .log_err()
+    } else {
+        log::error!("missing executable in directory {:?}", server_path);
+        None
+    }
 }
 
 pub struct NextLspAdapter;

crates/zed2/src/languages/elixir.rs 🔗

@@ -140,8 +140,8 @@ impl LspAdapter for ElixirLspAdapter {
     ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
         let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
-        let version_dir = container_dir.join(format!("elixir-ls_{}", version.name));
-        let binary_path = version_dir.join("language_server.sh");
+        let folder_path = container_dir.join("elixir-ls");
+        let binary_path = folder_path.join("language_server.sh");
 
         if fs::metadata(&binary_path).await.is_err() {
             let mut response = delegate
@@ -160,13 +160,13 @@ impl LspAdapter for ElixirLspAdapter {
             }
             futures::io::copy(response.body_mut(), &mut file).await?;
 
-            fs::create_dir_all(&version_dir)
+            fs::create_dir_all(&folder_path)
                 .await
-                .with_context(|| format!("failed to create directory {}", version_dir.display()))?;
+                .with_context(|| format!("failed to create directory {}", folder_path.display()))?;
             let unzip_status = smol::process::Command::new("unzip")
                 .arg(&zip_path)
                 .arg("-d")
-                .arg(&version_dir)
+                .arg(&folder_path)
                 .output()
                 .await?
                 .status;
@@ -174,7 +174,7 @@ impl LspAdapter for ElixirLspAdapter {
                 Err(anyhow!("failed to unzip elixir-ls archive"))?;
             }
 
-            remove_matching(&container_dir, |entry| entry != version_dir).await;
+            remove_matching(&container_dir, |entry| entry != folder_path).await;
         }
 
         Ok(LanguageServerBinary {
@@ -285,20 +285,16 @@ impl LspAdapter for ElixirLspAdapter {
 async fn get_cached_server_binary_elixir_ls(
     container_dir: PathBuf,
 ) -> Option<LanguageServerBinary> {
-    (|| async move {
-        let mut last = None;
-        let mut entries = fs::read_dir(&container_dir).await?;
-        while let Some(entry) = entries.next().await {
-            last = Some(entry?.path());
-        }
-        last.map(|path| LanguageServerBinary {
-            path,
+    let server_path = container_dir.join("elixir-ls/language_server.sh");
+    if server_path.exists() {
+        Some(LanguageServerBinary {
+            path: server_path,
             arguments: vec![],
         })
-        .ok_or_else(|| anyhow!("no cached binary"))
-    })()
-    .await
-    .log_err()
+    } else {
+        log::error!("missing executable in directory {:?}", server_path);
+        None
+    }
 }
 
 pub struct NextLspAdapter;

crates/zed2/src/languages/language_plugin.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use collections::HashMap;
 use futures::lock::Mutex;
-use gpui2::executor::Background;
+use gpui::executor::Background;
 use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
 use lsp2::LanguageServerBinary;
 use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};

crates/zed2/src/main.rs 🔗

@@ -208,7 +208,6 @@ fn main() {
         if stdout_is_a_pty() {
             cx.activate(true);
             let urls = collect_url_args();
-            dbg!(&urls);
             if !urls.is_empty() {
                 listener.open_urls(urls)
             }

test.rs 🔗

@@ -410,7 +410,7 @@ mod components {
         themes::rose_pine,
     };
     use gpui::{platform::MouseButton, ViewContext};
-    use gpui2_macros::Element;
+    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>)>>,
@@ -535,7 +535,7 @@ mod element {
         platform::{MouseButton, MouseButtonEvent},
         EngineLayout, EventContext, RenderContext, ViewContext,
     };
-    use gpui2_macros::tailwind_lengths;
+    use gpui_macros::tailwind_lengths;
     use std::{
         any::{Any, TypeId},
         cell::Cell,
@@ -4572,7 +4572,7 @@ mod frame {
     };
     use anyhow::{anyhow, Result};
     use gpui::LayoutNodeId;
-    use gpui2_macros::IntoElement;
+    use gpui_macros::IntoElement;
     #[element_crate = "crate"]
     pub struct Frame<V: 'static> {
         style: OptionalStyle,